LazyOracle
Oracle adapter for stVaults. Stores per-vault reports, applies sanity checks, and forwards vault updates to VaultHub.
What is LazyOracle?β
LazyOracle is a lightweight oracle for stVaults:
- stores the latest report metadata (timestamp, ref slot, tree root, CID)
- validates vault proofs against a report tree root
- applies per-vault accounting updates to VaultHub
- quarantines vaults with suspicious value deltas
It is called lazy because it stores only the report root and metadata each round; per-vault data is expanded on-demand via Merkle proofs only when a vault operation needs it.
How it worksβ
AccountingOraclepublishes a report root and metadata viaupdateReportData().- Anyone can submit per-vault updates with Merkle proofs via
updateVaultData(). - LazyOracle validates proofs and checks reward/fee bounds.
- Sanity-checked data is forwarded to VaultHub via
applyVaultReport().
Per-vault report submissions are permissionless: any account can call updateVaultData
with a valid Merkle proof from the latest report root.
Report freshnessβ
A vault report freshness is determined by VaultHub based on the report timestamp stored in LazyOracle. When stale, the vault cannot perform operations like withdrawals, mints, beacon chain deposits or disconnect.
Quarantine mechanicsβ
LazyOracle applies a quarantine buffer for sudden total value jumps that cannot be
verified immediately via inOutDelta. Value increases beyond the expected reward threshold
are quarantined for a configurable period before being released to VaultHub.
Time 0: Total Value = 100 ETH
ββββββββββββββββββββββββββββββββββββββ
β 100 ETH Active β
ββββββββββββββββββββββββββββββββββββββ
Time 1: Sudden jump of +50 ETH β start quarantine for 50 ETH
ββββββββββββββββββββββββββββββββββββββ
β 100 ETH Active β
β 50 ETH Quarantined β
ββββββββββββββββββββββββββββββββββββββ
Time 2: Another jump of +70 ETH β wait for current quarantine to expire
ββββββββββββββββββββββββββββββββββββββ
β 100 ETH Active β
β 50 ETH Quarantined β
β 70 ETH Quarantine Queue β
ββββββββββββββββββββββββββββββββββββββ
Time 3: First quarantine expires β add 50 ETH to active value, start new quarantine for 70 ETH
ββββββββββββββββββββββββββββββββββββββ
β 150 ETH Active β
β 70 ETH Quarantined β
ββββββββββββββββββββββββββββββββββββββ
Time 4: Second quarantine expires β add 70 ETH to active value
ββββββββββββββββββββββββββββββββββββββ
β 220 ETH Active β
ββββββββββββββββββββββββββββββββββββββ
Quarantine state machineβ
States:
β’ NO_QUARANTINE: No active quarantine, all value is immediately available
β’ QUARANTINE_ACTIVE: Total value increase is quarantined, waiting for expiration
β’ QUARANTINE_EXPIRED: Quarantine period passed, quarantined value can be released
βββββββββββββββββββ ββββββββββββββββββββ
β NO_QUARANTINE β reported > threshold βQUARANTINE_ACTIVE β
β βββββββββββββββββββββββββββββββΊβ β
β quarantined=0 β β quarantined>0 β
β startTime=0 ββββββββββββββββββββββββββββββββ€ startTime>0 β
β | β time<expiration |
βββββββββββββββββββ reported β€ threshold βββββ¬βββββββββββββββ
β² (early release) β β²
β β β increase > quarantined + rewards
β time β₯ β β (release old, start new)
β quarantine period β β
β βΌ β
β βββββββββββββββ΄βββββββββ
β reported β€ threshold OR β QUARANTINE_EXPIRED β
β increase β€ quarantined + rewards β β
β β quarantined>0 β
β β startTime>0 β
ββββββββββββββββββββββββββββββββββββββββ€ time>=expiration β
ββββββββββββββββββββββββ
Legend:
β’ threshold = onchainTotalValue * (100% + maxRewardRatio)
β’ increase = reportedTotalValue - onchainTotalValue
β’ quarantined = total value increase that is currently quarantined
β’ rewards = expected EL/CL rewards based on maxRewardRatio
β’ expiration = quarantine.startTimestamp + quarantinePeriod
Normal top-ups via fund() do not go through quarantine since they can be verified on-chain via inOutDelta. Only consolidations or deposits that bypass the vault's balance are quarantined.
Constantsβ
| Constant | Value | Description |
|---|---|---|
MAX_QUARANTINE_PERIOD | 30 days | Maximum allowed quarantine period |
MAX_REWARD_RATIO | 65535 (type(uint16).max) | Maximum reward ratio (~655%) |
MAX_LIDO_FEE_RATE_PER_SECOND | 10 ether | Maximum Lido fee rate per second |
Structsβ
QuarantineInfoβ
struct QuarantineInfo {
bool isActive; // Whether quarantine is active
uint256 pendingTotalValueIncrease; // Amount quarantined
uint256 startTimestamp; // When quarantine started
uint256 endTimestamp; // When quarantine expires
uint256 totalValueRemainder; // Additional value waiting in queue
}
VaultInfoβ
Aggregated vault information returned by view methods:
struct VaultInfo {
address vault; // Vault address
uint256 aggregatedBalance; // availableBalance + stagedBalance
int256 inOutDelta; // Current in/out delta
bytes32 withdrawalCredentials; // Vault withdrawal credentials
uint256 liabilityShares; // Current liability shares
uint256 maxLiabilityShares; // Maximum liability shares
uint256 mintableStETH; // Remaining mintable stETH
uint96 shareLimit; // Share limit from connection
uint16 reserveRatioBP; // Reserve ratio in basis points
uint16 forcedRebalanceThresholdBP; // Forced rebalance threshold
uint16 infraFeeBP; // Infrastructure fee
uint16 liquidityFeeBP; // Liquidity fee
uint16 reservationFeeBP; // Reservation fee
bool pendingDisconnect; // Whether vault is pending disconnect
}
View methodsβ
latestReportData()β
function latestReportData() external view returns (
uint256 timestamp,
uint256 refSlot,
bytes32 treeRoot,
string memory reportCid
)
Returns latest report metadata.
latestReportTimestamp()β
function latestReportTimestamp() external view returns (uint256)
Returns latest report timestamp.
quarantinePeriod()β
function quarantinePeriod() external view returns (uint256)
Returns quarantine period duration in seconds.
maxRewardRatioBP()β
function maxRewardRatioBP() external view returns (uint256)
Returns max reward ratio in basis points. Used to determine quarantine threshold.
maxLidoFeeRatePerSecond()β
function maxLidoFeeRatePerSecond() external view returns (uint256)
Returns max Lido fee rate per second in wei.
quarantineValue(address _vault)β
function quarantineValue(address _vault) external view returns (uint256)
Returns total value pending in quarantine for a vault (includes both pendingTotalValueIncrease and totalValueRemainder).
vaultQuarantine(address _vault)β
function vaultQuarantine(address _vault) external view returns (QuarantineInfo memory)
Returns detailed quarantine info for a vault. Returns zeroed struct if no active quarantine.
vaultsCount()β
function vaultsCount() external view returns (uint256)
Returns number of vaults connected to VaultHub.
batchVaultsInfo(uint256 _offset, uint256 _limit)β
function batchVaultsInfo(uint256 _offset, uint256 _limit) external view returns (VaultInfo[] memory)
Returns vault info for a range of vaults. Offset is 0-indexed from VaultHub vault list.
vaultInfo(address _vault)β
function vaultInfo(address _vault) external view returns (VaultInfo memory)
Returns aggregated info for a specific vault.
batchValidatorStatuses(bytes[] _pubkeys)β
function batchValidatorStatuses(bytes[] calldata _pubkeys)
external
view
returns (IPredepositGuarantee.ValidatorStatus[] memory batch)
Returns validator statuses from PredepositGuarantee for multiple pubkeys.
Methodsβ
initialize(address _admin, uint256 _quarantinePeriod, uint256 _maxRewardRatioBP, uint256 _maxLidoFeeRatePerSecond)β
function initialize(
address _admin,
uint256 _quarantinePeriod,
uint256 _maxRewardRatioBP,
uint256 _maxLidoFeeRatePerSecond
) external initializer
Initializes LazyOracle with admin and sanity parameters.
updateSanityParams(...)β
function updateSanityParams(
uint256 _quarantinePeriod,
uint256 _maxRewardRatioBP,
uint256 _maxLidoFeeRatePerSecond
) external
Updates sanity bounds. Requires UPDATE_SANITY_PARAMS_ROLE.
updateReportData(...)β
function updateReportData(
uint256 _vaultsDataTimestamp,
uint256 _vaultsDataRefSlot,
bytes32 _vaultsDataTreeRoot,
string memory _vaultsDataReportCid
) external
Publishes the report root and metadata. Only callable by AccountingOracle.
updateVaultData(...)β
function updateVaultData(
address _vault,
uint256 _totalValue,
uint256 _cumulativeLidoFees,
uint256 _liabilityShares,
uint256 _maxLiabilityShares,
uint256 _slashingReserve,
bytes32[] calldata _proof
) external
Applies a per-vault update with a Merkle proof. Permissionless - anyone can call with valid proof.
Sanity checks performed:
- Report must be newer than vault's previous report
- Total value increase is quarantined if above reward threshold
- Dynamic total value calculation must not underflow
- Cumulative Lido fees must be monotonically increasing
- Cumulative Lido fees increase must not exceed max rate
maxLiabilitySharesmust be >=liabilitySharesand <= on-chain value
removeVaultQuarantine(address _vault)β
function removeVaultQuarantine(address _vault) external
Removes quarantine for a vault. Only callable by VaultHub.
Permissionsβ
| Role | Description |
|---|---|
DEFAULT_ADMIN_ROLE | Admin role for granting/revoking other roles |
UPDATE_SANITY_PARAMS_ROLE | Can update sanity parameters (quarantine period, reward ratio, fee rate) |