erc_4626.vault
Documentation for eth_defi.erc_4626.vault Python module.
Generic ECR-4626 vault reader implementation.
Module Attributes
Known error messages that indicate that share() accessor function is not accessible and contract is ERC-4626, not ERC-7540. |
Classes
A reader that reads the historcal state of one specific vaults. |
|
ERC-4626 vault adapter |
|
Capture information about ERC- vault deployment. |
|
Adaptive reading frequency for vaults. |
- UNKNOWN_EXCHANGE_RATE = Decimal('0.9899999999999999911182158029987476766109466552734375')
The exchange rate we use for all unknown denomination tokens
- KNOWN_SHARE_TOKEN_ERROR_MESSAGES = frozenset({'Bad Request', 'Execution reverted', 'InvalidTransaction', 'VM execution error', 'execution reverted', 'out of gas'})
Known error messages that indicate that share() accessor function is not accessible and contract is ERC-4626, not ERC-7540. Because all EVM clones have different behavior on execution reverted, this is a bit of a shitshow.
- class ERC4626VaultInfo
Bases:
eth_defi.vault.base.VaultInfoCapture information about ERC- vault deployment.
- address: eth_typing.evm.HexAddress
The ERC-20 token that nominates the vault assets
- asset: Optional[eth_typing.evm.HexAddress]
The address of the underlying token used for the vault for accounting, depositing, withdrawing.
Some broken vaults do not expose this, and may be None. e.g. https://arbiscan.io/address/0x9d0fbc852deccb7dcdd6cb224fa7561efda74411#code
E.g. USDC.
- __init__(*args, **kwargs)
- __new__(**kwargs)
- clear() None. Remove all items from D.
- copy() a shallow copy of D
- fromkeys(value=None, /)
Create a new dictionary with keys from iterable and values set to value.
- get(key, default=None, /)
Return the value for key if key is in the dictionary, else default.
- items() a set-like object providing a view on D's items
- keys() a set-like object providing a view on D's keys
- pop(k[, d]) v, remove specified key and return the corresponding value.
If the key is not found, return the default if given; otherwise, raise a KeyError.
- popitem()
Remove and return a (key, value) pair as a 2-tuple.
Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.
- setdefault(key, default=None, /)
Insert key with a value of default if key is not in the dictionary.
Return the value for key if key is in the dictionary, else default.
- update([E, ]**F) None. Update D from mapping/iterable E and F.
If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]
- values() an object providing a view on D's values
- VaultPollFrequency
What is the reason how often we poll this
alias of
Literal[‘peaked’, ‘faded’, ‘large_tvl’, ‘small_tvl’, ‘tiny_tvl’, ‘first_read’, ‘not_started’, ‘early’]
- class VaultReaderState
Bases:
eth_defi.event_reader.multicall_batcher.BatchCallStateAdaptive reading frequency for vaults.
This class maintains the per-vault state of reading between different eth_call reads over time
Most vaults are uninteresting, but we do not know ahead of time which ones
We need 1h data for interesting vaults to make good trade decisions
We switch to 1h scanning if the TVL is above a threshold, otherwise we read it once per day
Note
Due to filtering, only handles stablecoin vaults correctly at the moment. Lacks exchange rate support.
- Parameters
vault – The vault we are reading historical data for
tvl_threshold_1d_read – If the TVL is below this threshold, we will not read it more than once per day, otherwise hourly.
down_hard – Stop reading the vault if the TVL is down by this percentage from the peak.
min_tvl_threshold – If the vault never reaches this TVL, we stop reading it after the traction period.
traction_period – How long we wait for the vault to get traction before we stop reading it.
- Parm peaked_tvl_threshold
The TVL value we first need to reach to trigger down hard condition.
- SERIALISABLE_ATTRIBUTES = ('last_tvl', 'last_share_price', 'max_tvl', 'first_seen_at_block', 'first_block', 'first_read_at', 'last_call_at', 'last_block', 'peaked_at', 'peaked_tvl', 'faded_at', 'entry_count', 'chain_id', 'vault_address', 'denomination_token_address', 'share_token_address', 'one_raw_share', 'reading_restarted_count', 'vault_poll_frequency', 'token_symbol', 'unsupported_token', 'invoke_count_passed', 'invoke_count_first_read', 'invoke_count_missing_freq', 'invoke_count_throttled', 'write_filtered', 'write_done', 'rpc_error_count', 'last_rpc_error')
All attributes we store when we serialise the read state between runs
- __init__(vault, tvl_threshold_1d_read=Decimal('10000'), tiny_tvl_threshold_rare_read=Decimal('1000'), peaked_tvl_threshold=Decimal('200000'), min_tvl_threshold=Decimal('1500'), down_hard=0.98, traction_period=datetime.timedelta(days=60))
- Parameters
vault (eth_defi.erc_4626.vault.ERC4626Vault) – The vault we are reading historical data for
tvl_threshold_1d_read – If the TVL is below this threshold, we will not read it more than once per day, otherwise hourly.
down_hard – Stop reading the vault if the TVL is down by this percentage from the peak.
min_tvl_threshold – If the vault never reaches this TVL, we stop reading it after the traction period.
traction_period (datetime.timedelta) – How long we wait for the vault to get traction before we stop reading it.
- Parm peaked_tvl_threshold
The TVL value we first need to reach to trigger down hard condition.
- first_seen_at_block
Passed from the vault discovery reader, pass the block number as args when we know this vault popped in to the existing
- last_tvl: decimal.Decimal
TVL from the last read
- first_read_at: datetime.datetime
Timestamp of the block of the first successful read of this vault.
- max_tvl: decimal.Decimal
Start with zero TVL
Start with zero share price
- last_call_at: datetime.datetime | None
When this vault received its last eth_call update
- peaked_at: datetime.datetime
Disable reading if the vault has peaked (TVL too much down) and is no longer active
- faded_at: datetime.datetime
Disable reading if the vault has never gotten any traction
- traction_period
How much time after deployment we allow to get traction
- min_tvl_threshold
Minimum TVL traction threshold to start reading the vault
- tiny_tvl_threshold_rare_read
Vaults we do no really care about
- entry_count
How many on_called() invocations have we had
- invoke_count_passed
How many should_invoke() invocations have we had
- invoke_count_first_read
How many should_invoke() invocations have we had
- invoke_count_missing_freq
How many should_invoke() invocations have we had
- invoke_count_throttled
How many should_invoke() invocations have we had
- rpc_error_count
Track RPCc errors
- denomination_token_address
Cache denomination token address when preparing readers
Cache share token address when preparing readers
One share in its raw units
- one
Cache denomination token address when preparing readers
- chain_id
Copy for state debuggin
- vault_poll_frequency
Cache for how often we are polling this vault, the mode name
- token_symbol
Cache for debuggin
- unsupported_token
Cache for debuggin
- property exchange_rate: decimal.Decimal
Get the exchange rate for TVL estimation
- should_invoke(call, block_identifier, timestamp)
Check the condition if this multicall is good to go.
- Parameters
call (eth_defi.event_reader.multicall_batcher.EncodedCall) –
block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –
timestamp (datetime.datetime) –
- Return type
- get_frequency()
How fast we are reading this vault or should the further reading be skipped.
- on_called(result, total_assets=None, share_price=None)
- Parameters
result (eth_defi.event_reader.multicall_batcher.EncodedCallResult) – Result of convertToAssets() call
total_assets (decimal.Decimal | None) –
share_price (decimal.Decimal | None) –
- class ERC4626HistoricalReader
Bases:
eth_defi.vault.base.VaultHistoricalReaderA reader that reads the historcal state of one specific vaults.
Generate a list of multicall instances that is needed to capture the vault state in a specific block height
All calls share the same state object which we use to track disabling reads for inactive vaults
Share price (returns), supply, NAV
For performance fees etc. there are no standards so you need to subclass this for each protocol
All calls for this reader share the same
- __init__(vault, stateful)
- Parameters
vault (eth_defi.erc_4626.vault.ERC4626Vault) –
stateful (bool) –
- construct_multicalls()
Get the onchain calls that are needed to read the share price.
- construct_core_erc_4626_multicall()
Polling endpoints defined in ERC-4626 spec.
Does not include fee calls which do not have standard
- process_core_erc_4626_result(call_by_name)
Decode common ERC-4626 calls.
- Parameters
call_by_name (dict[str, eth_defi.event_reader.multicall_batcher.EncodedCallResult]) –
- Return type
- dictify_multicall_results(block_number, call_results, allow_failure=True)
Convert batch of multicalls made for this vault to more digestible dict.
Assert that all multicalls succeed
- Returns
Dictionary where each multicall is keyed by its
EncodedCall.extra_data["function"]- Parameters
block_number (int) –
call_results (list[eth_defi.event_reader.multicall_batcher.EncodedCallResult]) –
- Return type
dict[str, eth_defi.event_reader.multicall_batcher.EncodedCallResult]
- process_result(block_number, timestamp, call_results)
Process the result of mult
Calls are created in
construct_multicalls()This method combines result of this calls to a easy to manage historical record
VaultHistoricalRead
- Parameters
block_number (int) –
timestamp (datetime.datetime) –
call_results (list[eth_defi.event_reader.multicall_batcher.EncodedCallResult]) –
- Return type
- class ERC4626Vault
Bases:
eth_defi.vault.base.VaultBaseERC-4626 vault adapter
Handle vault operations:
Metadata
Deposit and redeem from the vault
Vault historical price reader
Also partial support for ERC-7575 extensions
More info:
- Parameters
web3 – Connection we bind this instance to
spec – Chain, address tuple
token_cache –
Cache used with
fetch_erc20_details()to avoid multiple calls to the same token.Reduces the number of RPC calls when scanning multiple vaults.
features – Pass vault feature flags along, externally detected.
default_block_identifier –
Override block identifier for on-chain metadata reads.
When
None, useget_safe_cached_latest_block_number()(the default, safe for broken RPCs). Set to"latest"for freshly deployed vaults whose contracts do not exist at the safe-cached block.
- __init__(web3, spec, token_cache=None, features=None, default_block_identifier=None)
- Parameters
web3 (web3.main.Web3) – Connection we bind this instance to
spec (eth_defi.vault.base.VaultSpec) – Chain, address tuple
token_cache (dict | None) –
Cache used with
fetch_erc20_details()to avoid multiple calls to the same token.Reduces the number of RPC calls when scanning multiple vaults.
features (set[eth_defi.erc_4626.core.ERC4626Feature] | None) – Pass vault feature flags along, externally detected.
default_block_identifier (Optional[Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]]) –
Override block identifier for on-chain metadata reads.
When
None, useget_safe_cached_latest_block_number()(the default, safe for broken RPCs). Set to"latest"for freshly deployed vaults whose contracts do not exist at the safe-cached block.
- is_valid()
Check if this vault is valid.
Call a known smart contract function to verify the function exists
- Return type
- property address: eth_typing.evm.HexAddress
Get the vault smart contract address.
- property vault_contract: web3.contract.contract.Contract
Get vault deployment.
- property underlying_token: eth_defi.token.TokenDetails
Alias for
denomination_token()
- property erc_7540: bool
Is this ERC-7540 vault with asynchronous deposits.
For example
previewDeposit()function and other functions will revert
- fetch_denomination_token_address()
Get the address for the denomination token.
Triggers RCP call
- Return type
- fetch_denomination_token()
Read denomination token from onchain.
Use
denomination_token()for cached access.- Return type
eth_defi.token.TokenDetails | None
Get share token of this vault.
Vault itself (ERC-4626)
share() accessor (ERc-7575)
Read share token details onchain.
Use
share_token()for cached access.- Return type
- fetch_vault_info()
Get all information we can extract from the vault smart contracts.
- Return type
- fetch_total_assets(block_identifier)
What is the total NAV of the vault.
Example:
assert vault.denomination_token.symbol == "USDC" assert vault.share_token.symbol == "ipUSDCfusion" assert vault.fetch_total_assets(block_identifier=test_block_number) == Decimal("1437072.77357") assert vault.fetch_total_supply(block_identifier=test_block_number) == Decimal("1390401.22652875")
- Parameters
block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –
Block number to read.
Use web3.eth.block_number for the last block.
- Returns
The vault value in underlyinh token
- Return type
decimal.Decimal | None
- fetch_total_supply(block_identifier)
What is the current outstanding shares.
Example:
- Parameters
block_identifier (Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]) –
Block number to read.
Use web3.eth.block_number for the last block.
- Returns
The vault value in underlyinh token
- Return type
Get the current share price.
- fetch_portfolio(universe, block_identifier=None, allow_fallback=True)
Read the current token balances of a vault.
SHould be supported by all implementations
- Parameters
universe (eth_defi.vault.base.TradingUniverse) –
block_identifier (Optional[Union[Literal['latest', 'earliest', 'pending', 'safe', 'finalized'], eth_typing.evm.BlockNumber, eth_typing.evm.Hash32, eth_typing.encoding.HexStr, hexbytes.main.HexBytes, int]]) –
allow_fallback (bool) –
- Return type
Fetch the most recent onchain NAV value.
In the case of Lagoon, this is the last value written in the contract with updateNewTotalAssets() and ` settleDeposit()`
TODO: updateNewTotalAssets() there is no way to read pending asset update on chain
- Returns
Vault NAV, denominated in
denomination_token()- Return type
- get_flow_manager()
Get flow manager to read indiviaul settle events.
Only supported if
has_block_range_event_support()is True
- Return type
- get_deposit_manager()
Get deposit manager to deposit/redeem from the vault.
- has_block_range_event_support()
Does this vault support block range-based event queries for deposits and redemptions.
If not we use chain balance polling-based approach
- property denomination_token: eth_defi.token.TokenDetails | None
Get the token which denominates the vault valuation
Used in deposits and redemptions
Used in NAV calculation
Used in profit benchmarks
Usually USDC
- Returns
Token wrapper instance.
Maybe None for broken vaults like https://arbiscan.io/address/0x9d0fbc852deccb7dcdd6cb224fa7561efda74411#code
- property deposit_manager: eth_defi.vault.deposit_redeem.VaultDepositManager
Deposit manager assocaited with this vault
- property flow_manager: eth_defi.vault.base.VaultFlowManager
Flow manager associated with this vault
- get_deposit_fee(block_identifier)
Deposit fee is set to zero by default as vaults usually do not have deposit fees.
Internal: Use
get_fee_data().
- get_fee_data()
Get fee data structure for this vault.
- Raises
ValueError – In the case of broken or unimplemented fee reading methods in the smart contract
- Return type
- get_fee_mode()
Get how this vault accounts its fees.
- Return type
- get_link(referral=None)
Get a link to the vault dashboard on its native site.
By default, give RouteScan link
- get_management_fee(block_identifier)
Get the current management fee as a percent.
Internal: Use
get_fee_data().
- get_notes()
Get a human readable message if we know somethign special is going on with this vault.
- Return type
str | None
- get_performance_fee(block_identifier)
Get the current performance fee as a percent.
Internal: Use
get_fee_data().
- get_risk()
Get risk profile of this vault.
- Return type
- get_withdraw_fee(block_identifier)
Withdraw fee is set to zero by default as vaults usually do not have withdraw fees.
Internal: Use
get_fee_data().
- has_custom_fees()
Does this vault have custom fee structure reading methods.
Causes risk in the vault comparison.
E.g.
Withdraw fee
Deposit fee
- Returns
True if custom fee reading methods are implemented
- Return type
- has_deposit_distribution_to_all_positions()
Deposits go automatically to all open positions.
Deposits do not land into the vault as cash
Instead, smart contracts automatically increase all open positions
The behaviour of Velvet Capital
- property info: eth_defi.vault.base.VaultInfo
Get info dictionary related to this vault deployment.
Get cached data on the various vault parameters
- Returns
Vault protocol specific information dictionary
ERC-20 that presents vault shares.
User gets shares on deposit and burns them on redemption
- first_seen_at_block: int | None
Block number hint when this vault was deployed.
Must be set externally, as because of shitty Ethereum RPC we cannot query this. Allows us to avoid unnecessary work when scanning historical price data.
- get_historical_reader(stateful)
Get share price reader to fetch historical returns.
- Parameters
stateful – If True, use a stateful reading strategy.
- Returns
None if unsupported
- Return type
- get_estimated_lock_up()
ERC-4626 vaults do not have a lock up by fault.
Note
Because of so many protocol specific lockups, this must be explicitly set to zero.
- Return type
datetime.timedelta | None