T3trisHistoricalReader
Documentation for eth_defi.erc_4626.vault_protocol.t3tris.vault.T3trisHistoricalReader Python class.
- class T3trisHistoricalReader
Bases:
eth_defi.erc_4626.vault.ERC4626HistoricalReaderRead T3tris historical prices with async settlement gap correction.
T3tris async vaults can mint shares during deposit settlement before the oracle NAV reflects the settled assets. During this stale-NAV window the standard ERC-4626
convertToAssets(1 share)value can show a large phantom drawdown even though no economic loss happened.This reader follows the conservative “hold last good PPS” strategy for async vaults. When supply jumps and raw PPS collapses while the vault is closed for async settlement, it keeps the previous good share price and reports effective total assets as
held_pps * total_supplywhile the same stale-NAV window remains open. The hold ends when raw PPS recovers or a later oracle valuation timestamp indicates that the low PPS is now measured NAV instead of stale accounting.PPS scenarios handled by this reader:
Sync/open vault:
isVaultOpen()returnsTrue. T3tris is using live vault accounting, so the generic ERC-4626 PPS is already the effective PPS. No stale-NAV correction is applied.Normal async vault:
isVaultOpen()returnsFalsebut supply and PPS move without a large supply-driven collapse. The rawconvertToAssets(1 share)value is accepted and stored as the nextlast_good_share_price.Async settlement gap starts: total supply increases and raw PPS falls below
STALE_NAV_SHARE_PRICE_DROP_THRESHOLDof the previous good PPS. This is the T3tris phantom drawdown case where shares were minted before the oracle NAV absorbed the settled assets. The reader holds the previous PPS and recalculates NAV asheld_pps * total_supply.Settlement timestamp advances at gap start: some real T3tris samples show a newer
lastValuationTimestamp()at the settlement sample even though NAV is still stale. A timestamp advance therefore does not block starting a new gap if the supply jump and PPS collapse are present.Gap continues: once a stale-NAV gap is active, the reader keeps holding PPS while raw PPS remains collapsed and the oracle valuation timestamp is still the same timestamp that opened the gap.
Gap ends by fresh valuation or recovery: if raw PPS recovers, or a later valuation timestamp appears after the gap has started, the reader accepts the raw PPS again and clears the stale-NAV latch.
First sample already inside a gap: there is no previous good PPS to hold. The reader returns the raw sample, emits
STALE_NAV_FIRST_SAMPLE_ERROR, and does not seedlast_good_share_pricefrom the suspect value.Protocol-specific call failure: if
isVaultOpen()orlastValuationTimestamp()fails while raw PPS is collapsed, the reader avoids poisoninglast_good_share_pricewith the collapsed value. If a gap was already active, it keeps holding PPS for the failed sample.Real loss ambiguity: a true economic loss that happens in the same sample as a deposit-driven supply increase can look identical to a stale-NAV window. The reader intentionally favours the T3tris accounting correction because the purpose of this protocol-specific reader is to avoid user-facing phantom drawdowns in the historical PPS series.
Attributes summary
addressfirst_blockone_raw_shareGet the T3tris oracle address used for timestamp polling.
Methods summary
__init__(vault, stateful)Polling endpoints defined in ERC-4626 spec.
Get on-chain calls needed for corrected T3tris historical prices.
dictify_multicall_results(block_number, ...)Convert batch of multicalls made for this vault to more digestible dict.
Yield (function_name, callable, contract_call) tuples for warmup testing.
process_core_erc_4626_result(call_by_name)Decode common ERC-4626 calls.
process_result(block_number, timestamp, ...)Process one T3tris historical sample.
should_skip_call(function_name)Check if a specific function call should be skipped.
- __init__(vault, stateful)
- Parameters
vault (eth_defi.erc_4626.vault_protocol.t3tris.vault.T3trisVault) –
stateful (bool) –
- property oracle_address: eth_typing.evm.HexAddress
Get the T3tris oracle address used for timestamp polling.
T3tris’ live ABI exposes
getOracle()even though the minimal oracle interface is usually documented asoracle(). The vault method keeps the ABI-specific lookup in one place.The scanner constructs one multicall set for the whole historical read, so this address is cached for the reader lifetime. This assumes the vault’s oracle address is immutable for the scanned deployment history. If a future T3tris vault can migrate oracle contracts, the reader must be extended to split the historical scan at oracle-change boundaries before polling
lastValuationTimestamp().- Returns
The current oracle contract address.
- construct_multicalls()
Get on-chain calls needed for corrected T3tris historical prices.
In addition to the generic ERC-4626 calls we read
isVaultOpen()to distinguish sync and async vault modes, andlastValuationTimestamp()from the configured oracle to observe valuation refreshes.- Returns
Encoded calls consumed by the multicall historical reader.
- Return type
collections.abc.Iterable[eth_defi.event_reader.multicall_batcher.EncodedCall]
- process_result(block_number, timestamp, call_results)
Process one T3tris historical sample.
The raw ERC-4626 values are decoded first. If the sample is inside a correctable stale-NAV window,
share_priceis replaced by the last known good value andtotal_assetsis recalculated from the corrected price and current total supply. If the first sample already looks like a stale-NAV gap, the reader cannot infer a previous good PPS and therefore only tags the row as suspicious.PPS handling details:
Decode generic ERC-4626 values without letting the parent reader update adaptive scanner state. The raw values may be a known-bad T3tris stale-NAV sample, so state must only be updated after the T3tris correction decision is made.
Decode
isVaultOpen()and oraclelastValuationTimestamp().isVaultOpen() == Truemeans sync/live accounting.Falsemeans async/oracle accounting and may need stale-NAV correction.Reject out-of-order blocks. The stale-NAV detector depends on the previous supply, current gap valuation timestamp, and previous good PPS; processing historical samples backwards would make the state machine unsafe.
If a stale-NAV gap is detected, hold
last_good_share_priceand recomputetotal_assetsfrom the held PPS and the current supply. The row is tagged withSTALE_NAV_CORRECTED_ERRORso downstream consumers can see that the value is protocol-corrected, not the raw ERC-4626 value.If this looks like the first sample inside a gap, emit
STALE_NAV_FIRST_SAMPLE_ERRORand do not updatelast_good_share_price. Without a previous good PPS, any correction would be guesswork.If T3tris-specific protocol reads fail on a collapsed raw PPS, avoid updating
last_good_share_pricefrom the collapsed value. If this is the first low-PPS sample, tag it as a possible uncorrectable gap. When the failed sample is not already part of a confirmed gap, the previous supply baseline is also preserved so the next successful sample can still detect the supply jump and correct the gap.Update adaptive reader state exactly once, using the corrected PPS and corrected total assets when correction was applied. This prevents the adaptive scanner from learning a phantom drawdown and reducing scan quality for the vault.
- Parameters
block_number (int) – Block number of the sample.
timestamp (datetime.datetime) – Naive UTC timestamp of the block.
call_results (list[eth_defi.event_reader.multicall_batcher.EncodedCallResult]) – Results for calls produced by
construct_multicalls().
- Returns
Historical read with raw or T3tris-corrected share price and total assets.
- Return type
- construct_core_erc_4626_multicall()
Polling endpoints defined in ERC-4626 spec.
Does not include fee calls which do not have standard
- 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]
- get_warmup_calls()
Yield (function_name, callable, contract_call) tuples for warmup testing.
Each callable should execute a single contract call. If it raises, the function is marked as broken.
The optional contract_call is used for gas estimation to detect expensive calls before executing them. If provided, calls using excessive gas (>1M gas) will be marked as broken without execution.
Override in subclasses to add protocol-specific calls.
- 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