Bonus — Liquid Staking & Restaking Security
“Liquid staking and restaking didn’t invent a new bug class. They invented a new dependency tree. Every old bug class — oracle manipulation, share inflation, withdrawal-queue races, reentrancy — now lives inside a tower of contracts where each layer’s correctness depends on the layer below telling the truth about a value that is, fundamentally, an off-chain artifact (validator balance, slashing event, AVS attestation). The auditor’s job in this domain is to draw that dependency tree precisely, then ask, at every edge: what is the trust assumption, who enforces it, and what happens when it breaks?”
Tags: web3-security defi staking restaking lst lrt eigenlayer lido oracle vault Learner: Past Tuan-08-DeFi-Security-AMM-Lending-Vault §6 (the LST/LRT overview) → wants depth Time: 4–6 days (4–6h/day; lab-heavy; assumes Foundry comfort) Related: Tuan-05-Vulnerability-Classes-Part-1 · Tuan-07-Token-Standards-Integration-Risk · Tuan-08-DeFi-Security-AMM-Lending-Vault · Tuan-09-Oracle-MEV-Economic-Attack · Tuan-Bonus-Stablecoin-Economic-Modeling · Case-Penpie-Pendle-2024
1. Context & Why
1.1 What this bonus chapter is
Week 08 sketched LSTs and restaking in a few hundred words because the protocol-week budget could not absorb the full surface. This chapter is the depth. By the end, you can:
- Read Lido’s
Lido.sol,StETH.sol,WithdrawalQueueERC721.sol, andAccountingOracle.solon mainnet and answer, function-by-function, what each call assumes and what breaks when the assumption fails. - Distinguish rebasing (stETH) from exchange-rate (rETH, cbETH, LsETH) from vault-style (sfrxETH) LSTs and write the share/asset accounting equation for each.
- Articulate EigenLayer’s slashing primitive precisely (operator → AVS → strategy → restaker), the new slashing-redistribution model live in 2025–2026, and the differences vs Symbiotic and Karak.
- Trace a five-layer LRT dependency tree from validator → beacon-chain rewards → LST → restaked LST → AVS attestation → LRT share price → DeFi consumer (lending market collateral, AMM pool, vault) and identify the failure modes at each edge.
- Build a slashing-cascade simulation in Foundry that quantifies bad-debt creation in a lending market holding LRT collateral when one AVS slashes one operator.
Prime directive for this surface: No single contract owns the truth. The truth lives on the beacon chain, in oracle reports, in AVS attestations, in operator slashing decisions. Every on-chain accounting number is a delayed mirror of an off-chain reality, and the delay is the bug surface.
1.2 Why this matters in 2026
By mid-2026, the LST + LRT stack is among the largest TVL surfaces in DeFi:
- Lido stETH: still the dominant LST, ~28% of all staked ETH [verify exact figure at audit time].
- EigenLayer: ~$18–19.5B TVL recovered after the 2024–2025 mercenary-yield exit; ~1,900 active operators; slashing live since April 2025 with first real slashing events recorded in 2026. (BlockEden 2026 report · EigenYields slashing March 2026) [verify]
- Liquid Restaking Tokens (LRTs): ether.fi (~1.5B), Swell — combined LRT TVL around $7–8B, with continued retail demand for “points / restaking yield” products. (The Block)
- Symbiotic + Karak: alternative restaking frameworks competing with EigenLayer; Symbiotic emphasizes modular permissionless networks, Karak emphasizes multi-asset (BTC, stables, LP tokens).
The first production-scale LRT-bridge exploit landed in April 2026: Kelp DAO rsETH suffered a $292M phantom-mint via a forged LayerZero OFT adapter signature (1-of-1 validator stack), then a contested social-layer recovery. The mainnet rsETH backing was unaffected but the cross-chain accounting was upside-down for days. (defiprime post-mortem) [verify] This incident is the canonical 2026 reminder that LRT security ≠ Ethereum security ≠ the bridge that ferries the LRT to L2. Audit each layer.
1.3 Learning goals
By the end of this chapter, you can:
- Read a rebasing-balance ERC-20 (stETH) and explain why naive integrations break.
- Read an exchange-rate ERC-20 (rETH, cbETH) and explain why it’s safer for integrators but not safer for the underlying protocol.
- Read a 4626-style staking vault (sfrxETH) and identify the canonical rounding-direction rules.
- Write the dependency tree for ether.fi eETH / weETH and identify three failure modes.
- Distinguish EigenLayer slashing (operator-scoped, AVS-defined, redistributable) from Symbiotic slashing (network-defined, customizable, vault-isolated) from Karak slashing (multi-asset, KDS-orchestrated).
- Model a slashing cascade quantitatively (% slash → LRT exchange rate → LST oracle price → lending HF → liquidation chain).
1.4 Primary references
| Source | URL | Status / Notes |
|---|---|---|
| Lido docs | https://docs.lido.fi/ | Current; covers V2 + V3 |
| Lido V3 whitepaper | https://docs.lido.fi/lido-v3-whitepaper/ | Current; introduces stVaults + LazyOracle |
| Lido stETH superuser functions | https://docs.lido.fi/token-guides/steth-superuser-functions/ | Current |
| Lido AccountingOracle spec | https://docs.lido.fi/guides/oracle-spec/accounting-oracle/ | Current |
| Lido WithdrawalQueueERC721 | https://docs.lido.fi/contracts/withdrawal-queue-erc721/ | Current |
| ChainSecurity — Lido Staking Router audit | https://www.chainsecurity.com/security-audit/lido-staking-router | Historical; methodology useful |
| ChainSecurity — Lido V3 | https://diligence.security/audits/2025/08/lido-v3/ | 2025 |
| Rocket Pool docs | https://docs.rocketpool.net/ | Current; Saturn I tokenomics rework [verify] |
| Rocket Pool protocol deep-dive (Attestant) | https://www.attestant.io/posts/rocketpool-protocol/ | Excellent reference |
| Frax ETH docs | https://docs.frax.finance/frax-ether/overview · https://docs.frax.finance/frax-ether/technical-specifications | Current |
| frxETH-public repo | https://github.com/FraxFinance/frxETH-public | Current |
| Coinbase cbETH whitepaper | https://www.coinbase.com/cloud/discover/insights-analysis/introducing-cbeth | Historical reference |
| Liquid Collective LsETH docs | https://docs.liquidcollective.io/ | Current [verify] |
| EigenLayer docs | https://docs.eigencloud.xyz/eigenlayer/ (rebranded from docs.eigenlayer.xyz) | Current |
| EigenLayer slashing AVS guide | https://blog.eigencloud.xyz/intro-to-slashing-on-eigenlayer-avs-edition/ | Current |
| Symbiotic docs | https://docs.symbiotic.fi/ | Current [verify] |
| Karak docs | https://docs.karak.network/ | Current [verify] |
| ether.fi docs | https://etherfi.gitbook.io/ | Current |
| Renzo docs | https://docs.renzoprotocol.com/ | Current |
| Puffer Finance docs | https://docs.puffer.fi/ | Current |
| Kelp DAO docs | https://kelpdao.xyz/ | Current |
| Vitalik, “Don’t overload Ethereum’s consensus” | https://vitalik.eth.limo/general/2023/05/21/dont_overload.html | Foundational essay on restaking systemic risk |
| Sigma Prime / ChainSecurity / Spearbit audit publications | Various firm sites | Filter by LST/LRT/restaking |
| Solodit (Cyfrin) | https://solodit.cyfrin.io/ — filter liquid-staking, restaking | Living archive |
| Kelp rsETH bridge exploit post-mortem | https://defiprime.com/kelpdao-rseth-exploit | April 2026; LRT bridge case [verify] |
| Penpie / Pendle 2024 case study | Case-Penpie-Pendle-2024 | LST/LRT-adjacent reentrancy |
2. Why LSTs Exist — the auditor’s framing
2.1 The lock-up problem and what LSTs solve
Ethereum proof-of-stake requires validators to deposit 32 ETH (post-Pectra, validators can consolidate up to 2048 ETH per validator key, but the per-key minimum is still 32 ETH). That capital is locked: it earns staking yield (~3–4% APR depending on participation rate), but it cannot be spent, lent, swapped, or used as DeFi collateral while staked.
LSTs (Liquid Staking Tokens) tokenize the locked position. The protocol holds the 32 ETH (often pooled across thousands of validators); users hold an ERC-20 receipt whose value tracks the staked ETH plus accumulated rewards. They can sell the receipt without un-staking. The protocol absorbs the un-staking delay (the beacon-chain exit queue).
Auditor framing: an LST is a receipt for a yield-bearing claim on a delayed-settlement off-chain asset. Every word matters:
- Receipt: it’s not the stake itself; it’s a claim. The protocol owns the validator keys.
- Yield-bearing: the receipt’s value/balance grows with rewards.
- Delayed-settlement: redemption is not instant; it requires the beacon chain to exit the validator. Queue can stretch from hours to weeks under stress.
- Off-chain asset: the underlying lives on the consensus layer. The execution layer (where your audit happens) only sees what oracles report.
This last point is the heart of LST security. The contract you audit can be flawless; if the oracle that feeds it lies, the contract dutifully prints wrong numbers.
2.2 The four design dimensions
Every LST chooses on four axes:
| Axis | Options | Audit implication |
|---|---|---|
| Balance model | Rebasing (stETH) / exchange-rate (rETH, cbETH, LsETH) / vault-4626 (sfrxETH) | Rebasing breaks naive integrations; 4626 has its own rounding hazards |
| Validator set | Permissioned committee (Lido NOM) / permissionless with bond (Rocket Pool) / single operator (cbETH, LsETH) | Slashing-correlation risk; censorship; key-management trust |
| Withdrawal model | FIFO queue (Lido) / per-minipool exit (Rocket Pool) / two-token split (Frax) | Queue race conditions; depeg under stress |
| Fee structure | % of yield to protocol/operators/DAO | Misaligned fee accumulator = silent leak |
Pick a name and place it on these axes before reading a single line of its code.
2.3 The five LST families we’ll dissect
| Protocol | Token | Balance model | Validator set | Special property |
|---|---|---|---|---|
| Lido | stETH / wstETH | Rebasing (stETH) + wrapped non-rebasing (wstETH) | Permissioned NOM (Node Operators Module), ~30+ operators | Largest by TVL; daily oracle report |
| Rocket Pool | rETH | Exchange-rate | Permissionless minipools, 8/16 ETH bond + RPL | Decentralized; node operator absorbs slashing first |
| Frax ETH | frxETH + sfrxETH | Two-token: peg + 4626 vault | Permissioned validators (Frax-controlled) | Pure separation of medium-of-exchange vs yield |
| Coinbase | cbETH | Exchange-rate | Single operator (Coinbase) | Centralized; KYC; large institutional users |
| Liquid Collective | LsETH | Exchange-rate | Permissioned committee | Enterprise-focused; coordinated by institutions |
3. Lido in Depth
Lido is canonical. Understanding it precisely makes every other LST a variation on themes.
3.1 Contract surface (mainnet)
flowchart TB User --> StETH[StETH / Lido.sol<br>0xae7ab9...] User --> WstETH[WstETH wrapper<br>0x7f39c5...] User --> WQ[WithdrawalQueueERC721<br>0x889edC...] StETH --> Buffer[Buffered ETH<br>not yet deposited] StETH --> NOM[NodeOperatorsRegistry] Buffer --> DSM[DepositSecurityModule] DSM --> Deposit[Beacon DepositContract] AO[AccountingOracle<br>0x852dE...] --> StETH AO --> WQ Oracles[Off-chain oracle daemons<br>~9 members] --> HC[HashConsensus] HC --> AO Mev[ExecutionLayerRewardsVault] --> StETH WC[WithdrawalsVault] --> StETH
Read this diagram closely. Every arrow is a trust seam. The interesting ones for an auditor:
Off-chain oracle daemons → HashConsensus → AccountingOracle → StETH: this is the rebasing path. A wrong oracle report directly mis-prices stETH for every holder and every integration.Buffered ETH → DepositSecurityModule → Beacon DepositContract: a malicious or compromised DSM could front-run with attacker-controlled withdrawal credentials, redirecting user deposits. The DSM guards against pre-deposit front-running and requires a guardian signature.WithdrawalQueueERC721: each withdrawal request is an NFT. Pricing the NFT (how much stETH it represents at finalization) is non-trivial and changes with each oracle report until finalized.
3.2 stETH share math
The single most important fact about stETH: account balances are stored as shares, not as a token quantity. The visible “balance” is a derived view.
// From Lido.sol (simplified)
function balanceOf(address _account) external view returns (uint256) {
return getPooledEthByShares(_sharesOf(_account));
}
function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) {
return _sharesAmount.mul(_getTotalPooledEther()).div(_getTotalShares());
}
function _getTotalPooledEther() internal view returns (uint256) {
// = bufferedEther + transientEther + beaconBalance - withdrawalsVaultPending
// beaconBalance comes from the AccountingOracle report
return BUFFERED_ETHER_POSITION.getStorageUint256()
+ TRANSIENT_BALANCE_POSITION.getStorageUint256()
+ CL_BALANCE_POSITION.getStorageUint256();
}Every balanceOf call performs a multiplication and a division against current totalPooledEther and totalShares. When the oracle reports new beacon-chain balances, CL_BALANCE_POSITION updates, and all balances rebase implicitly — without any user action, every wallet’s stETH balance changes.
3.2.1 Why this is audit-relevant
Three immediate consequences for any contract that holds stETH:
- Rounding loss on every transfer. A
transfer(amount)computessharesToTransfer = amount * totalShares / totalPooledEther. Integer division loses dust. Two transfers of the sameamountbetween the same parties at the same block may produce differentsharesTransferredif the global ratio shifts. Lido absorbs this by always rounding shares toward the sender on transfers, but downstream protocols computing internal accounting againstbalanceOfcan be off by 1–2 wei per operation. Audit reflex: any equality check on a stETH amount must allow ±2 wei tolerance. - Balance changes between blocks without your contract being called. A protocol that snapshots
balanceOf(this)and reads it again later sees a different value even though no one transferred. Naive accounting (“I deposited 100 stETH, my balance is now 100 stETH, so I’m in equilibrium”) breaks the moment the oracle reports. Audit reflex: any internal accounting variable that should equalbalanceOfof stETH is a bug — always assumebalanceOfdrifts. - Negative rebases are possible. A slashing event makes
CL_BALANCE_POSITIONdrop. Every holder’sbalanceOfdecreases. A naive contract holding100stETH at deposit time may, after a slashing, show99.5— and anyassert(balanceOf(this) >= expected)invariant breaks. Audit reflex: never assume rebase is monotonic upward. Lido has hard limits on negative rebase per report (oracleReportSanityChecker), but the limit is non-zero.
3.2.2 wstETH as the integrator’s escape hatch
wstETH is a non-rebasing wrapper. You deposit stETH, it locks the shares, mints wstETH 1:1 with shares. Your wstETH balance never changes; its value in ETH grows over time via the wstETH/stETH exchange rate. This is the form 95% of DeFi integrations use (Aave, Curve, Maker, Pendle all accept wstETH, not stETH).
// WstETH.sol (simplified)
function wrap(uint256 _stETHAmount) external returns (uint256) {
uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount);
_mint(msg.sender, wstETHAmount);
stETH.transferFrom(msg.sender, address(this), _stETHAmount);
return wstETHAmount;
}
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) {
return stETH.getPooledEthByShares(_wstETHAmount);
}Audit reflex: when reviewing an integration, ask which token they hold. If they hold raw stETH but write code assuming balance is monotonic, that’s a finding. If they hold wstETH but quote prices in stETH terms without converting, that’s a finding. Mismatched assumptions about which side of the wrapper you’re on are a common bug class.
3.3 The AccountingOracle pipeline
The oracle is where Lido’s correctness ultimately lives. (Lido AccountingOracle spec)
sequenceDiagram participant CL as Consensus Layer participant Daemon as 9× Oracle Daemons (off-chain) participant HC as HashConsensus contract participant AO as AccountingOracle contract participant Lido as Lido.sol (stETH) participant WQ as WithdrawalQueueERC721 loop every frame (~225 epochs ≈ 24h) CL->>Daemon: read validator balances, exit status, etc. Daemon->>HC: submitReport(hash) Note over HC: quorum = ceil(membersCount/2) + 1 HC->>HC: wait until quorum agrees on same hash Daemon->>AO: submitReportData(report) AO->>AO: verify hash matches consensus AO->>Lido: handleOracleReport(...) AO->>WQ: onOracleReport(...) Lido->>Lido: update CL_BALANCE_POSITION, mint rewards, etc. WQ->>WQ: finalize queue up to checkpoint end
3.3.1 The oracle’s responsibilities (and bug surface)
In a single report, the oracle conveys:
clValidators— count of validators currently on the beacon chain belonging to Lido.clBalanceGwei— total beacon-chain balance of those validators.withdrawalVaultBalance— ETH inWithdrawalsVault(from validator exits).elRewardsVaultBalance— ETH inExecutionLayerRewardsVault(from EL block proposer rewards + MEV).sharesRequestedToBurn— net shares to burn (cover, etc).lastWithdrawalRequestIdToFinalize— checkpoint up to which the queue can finalize.
Bug surface:
| Risk | Mitigation in Lido | Auditor’s question |
|---|---|---|
| Single daemon lies | M-of-N consensus on report hash | Is M actually a majority of the active daemons, or did several go offline reducing effective quorum? |
| Daemons coordinate to lie (Sybil/collusion) | Slashable bonds? No — daemons are protocol-elected committee | Trust assumption: ≥ M of the committee is honest |
| Report is too large a delta from last frame | OracleReportSanityChecker enforces _churnLimit, _oneOffCLBalanceDecreaseBPLimit, _annualBalanceIncreaseBPLimit, etc. | Are the limits tight enough? A 5% one-off CL balance decrease (50 bp per frame in older versions) is huge in absolute terms |
| Withdrawal queue finalization skipped | Queue auto-finalizes up to oracle checkpoint | A bug causes infinite queue extension if reports stop |
| Negative rebase exceeds limit | Report reverts; protocol enters a fallback | Fallback path correctness is its own audit |
| MEV / EL rewards underreported | DSM + oracle separation; EL rewards are queried directly | Coordination between oracle and DSM is a subtle race |
This pipeline is the single most-audited piece of Lido. Audit firms have covered it at least six times (Sigma Prime, ChainSecurity, Oxorio, Hexens, Certora, MixBytes). The current open audit surface is mostly around Lido V3 and the stVaults / LazyOracle model.
3.3.2 Lido V3: stVaults and LazyOracle
Lido V3 introduces stVaults — per-user (or per-strategy) vaults that hold their own validator stake but plug into the Lido oracle for accounting. Instead of every stVault state being written on-chain daily, the AccountingOracle posts a Merkle root of all vault states; individual vault updates happen on-demand via Merkle proofs. (Lido V3 whitepaper · ChainSecurity V3 audit)
Audit-relevant new surfaces:
- Merkle proof verification on stVault state updates. Same hazards as bridge / airdrop Merkle proofs (cross-link Tuan-01-Web3-Blockchain-Crypto-Fundamentals §4.3). Domain separation between leaf and internal nodes? Replay across frames if no frame-id in leaf?
- Stale proofs. A stVault that doesn’t update its accounting for many frames may be deeply stale; integrators reading its price must check
lastReportedFrame. - Permissioned vs permissionless stVaults. The stVault operator may be a third party. The economic incentive of that operator vs the stVault depositor is a new attack surface.
This is a 2025–2026 audit growth area. Most LST-aware tooling does not yet handle V3 properly.
3.4 Withdrawal queue race conditions
WithdrawalQueueERC721 is the FIFO queue. Each request is minted as an NFT (ownership transferable). (WithdrawalQueueERC721 docs)
// User flow (simplified)
function requestWithdrawals(uint256[] calldata _amounts, address _owner)
external returns (uint256[] memory requestIds);
function claimWithdrawals(uint256[] calldata _requestIds, uint256[] calldata _hints) external;3.4.1 Pricing the NFT — the moving-target problem
A request is for X stETH at submission. By the time it’s finalized (oracle reports enough times to cover it), X stETH may correspond to a different ETH amount because of rebasing in either direction. The contract resolves this with checkpoint pricing: at each oracle report, the queue records the share-price that applies to all requests finalized in that batch.
// Conceptually
struct WithdrawalRequest {
uint128 cumulativeStETH;
uint128 cumulativeShares;
address owner;
uint64 timestamp;
bool claimed;
bool finalized;
}
struct Checkpoint {
uint256 fromRequestId;
uint256 maxShareRate; // ← the rate at which requests in this checkpoint are paid
}Audit-relevant subtleties:
- The user gets
min(stETH-at-request, stETH-at-finalization). If a positive rebase happens between request and finalization, the user does not get the new yield — they get what they requested. If a negative rebase happens, they get the (smaller) post-rebase amount. The asymmetry is intentional: rewards keep accruing to active stETH holders, not to the queue. - Hint-based claim. The user supplies a
_hint(which checkpoint applies to their request) at claim time. A wrong hint reverts. A user-supplied hint is OK because reverting is safe — but tooling that auto-fills hints can DoS users if wrong. Audit any wrapper that picks hints automatically. - NFT transferability + claim race. The request is an NFT. If Alice transfers it to Bob mid-finalization, Bob can claim. There’s no race because the NFT owner at claim-time is the only one who can claim. But a sandwich attacker who buys the NFT, lets it finalize, then claims, is just doing normal market behavior.
- Censorship of withdrawal. Lido has no admin pause on
WithdrawalQueueERC721.claimWithdrawals(this is a critical design choice — withdrawals are not censorable). Verify no upgrade can introduce such a pause without time-lock. The trust model “Lido cannot freeze your withdrawal” is a product promise the audit should validate. - Withdrawal queue under stress. If exit queue grows (mass un-stake during a panic), un-staking can stretch to weeks. The on-chain queue can finalize only as fast as the beacon chain exit queue processes validators and the protocol has buffered ETH + EL rewards to cover. Modeling this is an economic analysis, not a code-level one, but auditors should flag any integrator that assumes “stETH redemption is fast”.
3.5 Slashing absorption
When a Lido validator is slashed:
- The CL balance decreases (visible in the next oracle report).
- The
OracleReportSanityCheckerensures the decrease is within the per-report limit (otherwise the report reverts and a sanity-check resolution flow is triggered). - The loss is socialized across all stETH holders proportionally via the rebase.
- The slashed node operator may have a slashing cover (via Lido’s insurance mechanism / Lido DAO treasury / Mellow Vault Cover [verify current state]).
Auditor’s questions:
- What’s the maximum one-report slashing loss the system can absorb without halting?
- If a mass-slashing event exceeds that, what’s the recovery path? Manual intervention via Lido DAO?
- Are insurance funds adequate for the validator-count’s worst plausible single-event slashing? (
max_slashable = validators_in_same_epoch × 1 ETH initial penalty + correlation penalty over the next 36 days) - Does Lido V3 isolate slashing to individual stVaults, or socialize? (Mostly isolates — this is a major V3 design pivot.)
4. Other LST Models
4.1 Rocket Pool: rETH as exchange-rate, decentralized validators
Rocket Pool is the canonical permissionless LST. (Rocket Pool docs · Attestant deep-dive)
4.1.1 Architecture
- Minipools: a Rocket Pool validator. Operated by a node operator who bonds 8 or 16 ETH of their own + accepts 24 or 16 ETH of staker capital. Total 32 ETH; one beacon-chain validator key per minipool. Post-Saturn I, “MEGAPOOLS” reduce this to 4 ETH operator bond + lower-friction node setup.
- rETH: ERC-20 receipt token. Non-rebasing. Exchange rate ETH/rETH grows over time.
- RPL bond: node operators historically had to bond RPL (Rocket Pool’s governance token) at ≥10% of borrowed ETH value, slashable if operator misbehaves badly. Post-Saturn I, RPL bond is optional, reframed as a yield-boost rather than a mandatory bond. [verify current state]
- Deposit pool: staker ETH waits here until matched with a minipool.
4.1.2 rETH exchange-rate formula
// RocketTokenRETH.sol (simplified)
function getExchangeRate() public view returns (uint256) {
uint256 totalEthBalance = rocketNetworkBalances.getTotalETHBalance();
uint256 rethSupply = totalSupply();
if (rethSupply == 0) return 1 ether;
return totalEthBalance * 1 ether / rethSupply;
}
function getRethValue(uint256 _ethAmount) public view returns (uint256) {
return _ethAmount * 1 ether / getExchangeRate();
}totalEthBalance is updated by the rocketNetworkBalances oracle (a separate per-frame consensus mechanism). Same trust assumption shape as Lido: a committee of oracles attests to beacon-chain balances; a quorum is required.
4.1.3 Slashing model — different from Lido
When a Rocket Pool validator is slashed:
- The minipool’s bonded ETH absorbs the loss first (8 or 16 ETH of operator’s own capital).
- If the slashing exceeds the bond, the staker side eats the rest (rare).
- Historically: the operator’s RPL bond could be slashed to cover staker losses if their bond ran out. Saturn I changes this to make RPL bond optional. [verify final design]
Audit-relevant difference vs Lido: Rocket Pool slashing is not socialized across all rETH holders unless the operator’s bond + RPL is exhausted. This makes rETH less depeg-prone in single-operator-slashing events than stETH. But: large correlated slashing across many minipools could still socialize.
4.1.4 Withdrawal model
Rocket Pool offers two withdrawal paths:
- Burn rETH for ETH directly from the deposit pool if it has liquidity. Instant.
- Request a minipool exit if not. Slower; depends on the beacon-chain exit queue.
Audit reflex: any integration that treats “rETH redemption” as instant must guard against the deposit pool draining. The pool’s liquidity is finite. Under stress, expect delays.
4.2 Frax ETH: the cleanest two-token split
Frax separates the two functions of an LST into two tokens. (Frax ETH docs)
| Token | Role | Yield-bearing? |
|---|---|---|
| frxETH | Pegged to ETH 1:1, medium of exchange | No |
| sfrxETH | ERC-4626 vault wrapping frxETH | Yes (yield accrues here) |
4.2.1 The mechanism
flowchart LR User -->|"submit() with ETH"| Minter[frxETHMinter] Minter -->|"1 frxETH per 1 ETH"| User2[User holds frxETH] Minter -->|"32 ETH batches"| Beacon[DepositContract] User2 -->|"deposit"| Vault[sfrxETH Vault] Vault -->|"yield accrual via syncRewards"| Vault Beacon -->|"validator rewards"| Vault
frxETHMinter.submit{value: ethAmount}()mintsfrxETH1:1. When the minter accumulates 32 ETH, it spins up a new validator using pre-credentialed withdrawal addresses (a stack of pre-signed deposits).sfrxETHis an ERC-4626 vault holdingfrxETHas the underlying. Yield is added bysyncRewards()which detects extrafrxETHin the contract and queues it for linear distribution over a reward cycle.- 90% of validator yield → sfrxETH stakers; 8% Frax treasury; 2% slashing insurance.
4.2.2 Auditor focus on Frax ETH
frxETHMintervalidator-key stack. The minter pops pre-signed deposit credentials. If the stack runs out, new submissions buffer until the team pushes more credentials. If the stack contains credentials with attacker-controlled withdrawal addresses (via a compromised key-ceremony or malicious push), all subsequent ETH is redirected. Verify the stack-push function’s access control and any front-running protection.sfrxETHas 4626. Apply standard 4626 audit checks (cross-link Tuan-07-Token-Standards-Integration-Risk §4626): first-depositor inflation, rounding directions, donation attack on share price, virtual-shares mitigation.syncRewards()linearization. The vault distributes yield linearly over a cycle window (e.g., 7 days). A user who deposits just before asyncRewardsand exits just after may miss most of the cycle’s yield (or capture it, depending on timing). Sandwich opportunity aroundsyncRewardscalls. Audit whethersyncRewardsis permissionless (it is, deliberately, for liveness) and whether the cycle window is short enough that the sandwich is unprofitable after gas.- No on-chain redemption peg.
frxETHis softly pegged via Frax-controlled AMOs (Algorithmic Market Operations) buying/selling on Curve. There is no contract-level “burn 1 frxETH for 1 ETH”. Depeg is possible if AMOs fail. Audit-relevant: any protocol treating frxETH as a 1:1 ETH receipt is making a market-maker trust assumption, not a protocol-guarantee.
4.3 Coinbase cbETH and Liquid Collective LsETH
Both are single-operator (or near-single-operator) exchange-rate tokens. Audit angle is largely the same as rETH minus the decentralized validator set:
- cbETH: Coinbase as the sole node operator. The exchange rate is updated by a Coinbase-controlled oracle. Trust is concentrated. Off-chain risk includes regulatory action against Coinbase (key seizure, account freezing).
- LsETH (Liquid Collective): a committee of large institutional operators. Better than single-operator but still a small permissioned set.
Auditor reflex: when you see a single-operator or small-committee LST as collateral in a “decentralized” protocol, the trust assumption inherited from the LST should be flagged in the trust-assumptions section. A protocol can be only as decentralized as its LST inputs.
4.4 Cross-LST audit checklist
□ Balance model: rebasing / exchange-rate / 4626
□ If rebasing: are all integrations using wrapped (non-rebasing) version?
□ If exchange-rate: how often is the rate updated? By whom? Sanity-checked?
□ Withdrawal queue: FIFO / per-position / instant via reserve pool?
□ Maximum withdrawal delay under stress (model it)?
□ Slashing model: socialized / operator-bond-first / insurance-fund-backed?
□ Maximum one-event slashing absorbed before peg breaks?
□ Validator set composition: permissioned / permissionless / single-operator?
□ Oracle for the exchange rate: committee / contract-derived / market-derived?
□ Pause / emergency mechanisms: who can trigger? Time-locked?
□ Upgradeability: who owns proxy admin?
□ Fee accumulator: where does protocol fee go? Who can change rate?
□ Token compatibility: ERC-20 strict / has hooks (ERC-777 hazard)?
5. Restaking — EigenLayer, Symbiotic, Karak
5.1 The concept and why it’s new
In vanilla PoS, your staked ETH secures one protocol: Ethereum. Restaking re-pledges that same stake to secure additional services (AVSs — “Actively Validated Services”). If the operator misbehaves on an AVS, the AVS can slash the operator’s restaked stake.
The economic claim: shared security. New services that would have to bootstrap their own validator set and token to secure themselves can instead borrow Ethereum’s economic security. The promise is “Ethereum security as a service”.
Vitalik’s critique (May 2023, Don’t overload Ethereum’s consensus): if a restaking protocol creates expectations that Ethereum social consensus should fork to bail out a failed AVS, that’s the moment restaking becomes systemically dangerous. The protocol-level slashing is fine; the social expectation is not.
This is a systemic risk lens that doesn’t fit a function-level audit, but it should be in the auditor’s trust-assumptions section: “this protocol’s economic model assumes Ethereum will not socially-coordinate to undo AVS slashing”.
5.2 EigenLayer architecture
flowchart TB R[Restaker] -->|"deposit LST or natively-restake validator"| S[Strategy contract<br>e.g. cbETH Strategy, stETH Strategy] S --> SM[StrategyManager] R -->|"delegateTo"| DM[DelegationManager] DM --> Op[Operator] Op -->|"opt-in to AVS"| AVS[AVSDirectory + AllocationManager] AVS -->|"slashing condition"| Slasher[SlashingManager] Slasher -->|"slash operator on violation"| S R -->|"withdraw via queue"| DM DM -->|"completion delay"| WQ[Withdrawal queue]
Key contracts (mainnet) — names drift, [verify current addresses on docs.eigencloud.xyz]:
- StrategyManager: tracks per-strategy shares for each restaker (an LST or natively-restaked ETH).
- DelegationManager: routes restakers to operators; handles delegate / undelegate.
- AVSDirectory / AllocationManager: tracks operator opt-ins to AVSs; manages stake allocation per AVS post-2024 unique-stake-allocation upgrade.
- SlashingManager: executes slashings on operator misbehavior, scoped per AVS.
5.2.1 Unique stake allocation (post-2024)
Originally, an operator opted into an AVS with their entire restaked stake. After concerns about cumulative slashing risk, EigenLayer introduced unique stake allocation: the operator allocates fractions of their stake to each AVS, with the sum ≤ 100%. Slashing on AVS-X only affects the fraction allocated to X.
This is a critical security primitive — auditors should verify:
- Allocations sum to ≤ 100% in the contract logic, with rounding handled correctly.
- Allocation changes have a delay (so an operator can’t re-allocate post-violation to dodge slashing).
- Reducing allocation also enters a queued state; cannot instantly remove stake from an AVS.
5.2.2 Slashing in production (2025–2026)
Slashing went live April 2025. (Blockdaemon guide) The first real slashing events recorded in 2026 (EigenYields case) demonstrated the new redistributable slashing model: slashed funds can be redirected (not just burned) to designated vaults. [verify exact mechanics]
Auditor implications:
- AVS slashing contracts are now audit targets in their own right. Each AVS writes its own slashing logic — a bug that slashes honest operators is a multi-million dollar incident.
- Redistributable slashing introduces a new attacker incentive. A malicious AVS designer could craft a slashing condition that’s triggerable by a third party (the attacker) with proceeds redirected to a vault the attacker controls. Audit AVS slashing conditions for:
- Who can submit slashing evidence?
- Is evidence cryptographically verifiable on-chain, or does it rely on a committee?
- Where are slashed funds redirected? Time-locked? Reviewable by governance?
- Operator’s reputational risk. An operator opting into a buggy AVS may be slashed for no fault of their own. Operators with high TVL should have strict AVS opt-in policies. Audit a restaking protocol’s operator set’s AVS exposure as a portfolio risk.
5.2.3 Withdrawal in EigenLayer
Restakers withdraw via a two-step queued flow:
queueWithdrawal— request to withdraw shares; enters a delay (default 7 days post-2024 upgrades).completeQueuedWithdrawal— claims after delay.
The delay exists so slashing can be applied retroactively if misbehavior is detected during the withdrawal window. If you withdraw while a slashing is in progress, the slashing applies first.
Audit angle:
- The 7-day window is a trust-the-delay assumption. If a slashing condition is discovered post-window, the restaker is gone — slashing cannot retroactively claw back. AVS slashing-evidence collection must therefore be fast enough that 7 days is sufficient.
- Reentrancy in
completeQueuedWithdrawal: this function transfers underlying assets. Standard CEI applies; verify nonReentrant + state-before-call ordering. Cross-link Tuan-05-Vulnerability-Classes-Part-1 §2.
5.3 Symbiotic and Karak
After EigenLayer’s success, two alternatives launched in 2024.
5.3.1 Symbiotic
- Modular permissionless framework. Anyone can deploy a network (= AVS analog) with custom slashing logic, custom collateral, custom operator selection.
- Each network has its own vaults (collateral pools) and delegators (which route stake to operators per network).
- Slashing is network-defined; vaults can be isolated per network or shared. [verify current state]
Audit angle: with no opinionated framework, every Symbiotic network is its own audit. The vault contract is shared infrastructure (audited), but the slashing-condition contracts are bespoke. Expect 80% of Symbiotic-related findings to live in network-specific slashing code, not in core Symbiotic.
5.3.2 Karak
- Multi-asset. Accepts not just ETH-LSTs but also stablecoins, BTC variants, LP tokens. “Universal restaking layer.”
- KDS (Karak’s distributed service framework) orchestrates AVS-equivalent services with built-in slashing primitives.
- Aims to be friendlier to non-ETH-native protocols.
Audit angle: multi-asset → multi-oracle → multi-failure-mode. A Karak AVS accepting WBTC, USDC, and stETH as collateral must price each correctly under stress; oracle manipulation on any one collateral can subsidize an attack on the AVS.
5.4 Comparing the three
| Dimension | EigenLayer | Symbiotic | Karak |
|---|---|---|---|
| Permissioned core? | Yes (EigenLabs governance over core upgrades) | No (permissionless network creation) | Partial |
| Default collateral | ETH + ETH LSTs | Any ERC-20 (network-defined) | Multi-asset (ETH, BTC, stables, LPs) |
| Slashing model | Operator-scoped, AVS-defined, redistributable | Network-defined, vault-isolated | KDS-orchestrated |
| Withdrawal delay | 7 days (post-2024) | Network-defined | Per-asset |
| Live slashing? | Yes (April 2025) | Partial [verify] | Partial [verify] |
| Market position (mid-2026) | $18–19.5B TVL, ~1,900 operators | ~$1–3B [verify] | smaller |
6. Liquid Restaking Tokens (LRTs)
LRTs are the LST-of-LSTs. They wrap a restaking position into an ERC-20 receipt that’s tradable, lendable, and re-collateralizable.
6.1 The five-layer dependency tree
flowchart TB V[Validator on beacon chain] --> R[Validator rewards on CL] R --> O1[LST oracle <br>e.g. Lido AccountingOracle] O1 --> LST[LST token<br>e.g. stETH] LST --> RS[Restaking strategy<br>EigenLayer Strategy contract] RS --> AVS[AVS attestation /<br>slashing condition] RS --> LRT[LRT token<br>e.g. ezETH, eETH, rsETH, pufETH] LRT --> Bridge[OFT / LayerZero / wormhole bridge] Bridge --> L2LRT[LRT on L2] LRT --> Lending[Lending market<br>uses LRT as collateral] LRT --> AMM[AMM pool<br>LRT/ETH or LRT/stETH] LRT --> Vault[Vault strategy<br>aggregates LRT] Vault --> Lending2[Vault used as collateral elsewhere]
Count the trust seams. From the validator to a DeFi consumer borrowing against a vault-wrapped-LRT on L2, you’re stacking:
- 1 beacon-chain attestation correctness (Ethereum-level trust).
- 2 LST oracle correctness.
- 3 EigenLayer accounting correctness.
- 4 AVS slashing correctness (per AVS).
- 5 LRT protocol accounting correctness.
- 6 Bridge correctness (if cross-chain).
- 7 Consumer protocol pricing correctness (lending / AMM / vault).
A bug at any layer can propagate down. The 2026 Kelp DAO rsETH incident was a layer-6 failure (bridge) that didn’t propagate to layer-5 (mainnet accounting), but it did corrupt all downstream layer-7 integrations on chains that held the phantom-minted rsETH for hours/days. (Kelp post-mortem)
6.2 The four major LRTs
6.2.1 ether.fi — eETH and weETH
- Architecture: ether.fi runs its own node operators (with stakers’ ETH directly, not wrapping stETH) and restakes through EigenLayer.
- eETH: rebasing receipt.
- weETH: non-rebasing wrapped receipt (the integrator-friendly form).
- TVL: ~$4.9B mid-2026 — the largest LRT. (The Block)
Audit-relevant features:
- Native restaking (not stETH-wrapping) means ether.fi is exposed to slashing directly via its own validators, not via Lido’s slashing model.
- Operator set: ether.fi runs a permissioned operator program. Centralization concern.
6.2.2 Renzo — ezETH
- Architecture: aggregator across EigenLayer + Symbiotic; accepts native ETH, stETH, wbETH, and others.
- ezETH: exchange-rate receipt; no rebasing.
- Multi-chain expansion: native ezETH deployments on multiple L2s.
Audit-relevant:
- Multi-source restaking means the oracle pricing ezETH must aggregate across EigenLayer and Symbiotic state. Each is a separate oracle / accounting source.
- The April 2024 ezETH depeg incident (pre-2026 history): ezETH briefly depegged to ~$0.70 on secondary markets when airdrop-eligibility rules were announced, triggering massive looped-leverage liquidations across Morpho, Gearbox, and other lending markets that priced ezETH naively from a thin Curve pool. The exchange rate (canonical price from Renzo) never depegged — but the market price did. Lesson: integrators that use market price of an LRT instead of protocol-reported exchange rate are setting themselves up for forced liquidations on liquidity panics. [verify exact dates / amounts] Cross-link Tuan-09-Oracle-MEV-Economic-Attack.
6.2.3 Puffer — pufETH
- Architecture: focus on “anti-slashing” tech (Secure-Signer enclave to prevent double-signing).
- pufETH: 4626 vault.
- Native restaking with permissionless node operators (small bond).
Audit-relevant:
- The Secure-Signer dependency is a trusted execution environment assumption. Auditors should treat TEE as an “out-of-band trust assumption” — out of the contract’s reach, but in scope for the trust model. (Cross-link Tuan-12-Wallet-AA-Key-Management on TEE risks if covered.)
- 4626 vault audit reflexes apply (rounding, first-depositor, donation).
6.2.4 Kelp DAO — rsETH
- Architecture: multi-LST collateral (stETH, ETHx, sfrxETH) restaked through EigenLayer.
- rsETH: exchange-rate receipt.
- TVL: ~$1.5B mid-2026.
Audit-relevant:
-
Multi-LST input means multi-oracle pricing. Each underlying LST has its own oracle; rsETH’s exchange rate aggregates them.
-
Bridge exploit April 2026 (the canonical 2026 LRT incident): 116,500 phantom rsETH minted on Ethereum mainnet via a forged LayerZero OFT-adapter signature, ~$292M nominal. The OFT was running a 1-of-1 validator stack — a single forged signature unlocked the adapter’s mainnet escrow. Mainnet rsETH backing was unaffected, but L2 holdings briefly disagreed with mainnet supply. (Kelp post-mortem) [verify final disposition]
Lesson for auditors of any LRT crossing chains: the bridge security ≤ LRT security. A bridge with a 1-of-N validator is a single-point-of-failure for the entire LRT, regardless of how well the LRT itself is audited. Insist on multi-sig minimum, prefer canonical bridges (e.g., native rollup bridge) where possible.
6.3 LRT-specific audit checklist
□ What's the underlying? Native ETH, single LST, or aggregated multi-LST?
□ How is the exchange rate computed? On-chain math from accounting, or oracle-fed?
□ Are there per-AVS allocation tracking and slashing pass-through?
□ Withdrawal queue: how deep, what delay, who can finalize?
□ Reward distribution: linear / instant / by-checkpoint?
□ Operator set: permissioned/permissionless? Slashing-correlation?
□ Cross-chain version: which bridge, what validator set, what unlock conditions?
□ Pause / emergency: who controls? Time-locked?
□ Points / loyalty / airdrop hooks: can a "snapshot" be gamed via flash-loan?
□ ERC-4626 rounding rules followed?
□ Are downstream consumers (lending markets, AMM pools) reading exchange-rate
from the LRT contract (canonical) or from a market price (manipulable)?
7. The Slashing Cascade
This is the systemic-risk scenario every LRT auditor must model. Walk through it carefully.
7.1 The setup
Assume:
- A lending protocol (e.g., a Morpho-style market or Aave-fork) accepts an LRT (call it
xETH) as collateral with a 75% LTV. - The LRT’s underlying is stETH restaked via EigenLayer on one AVS (call it
AVS-A). - 30% of the LRT’s stake is allocated to AVS-A.
- A user has deposited 1000 xETH (≈ 1000 ETH at peg) and borrowed 700 ETH (close to max LTV).
7.2 The trigger
AVS-A’s slashing logic activates against the LRT’s operator. Suppose the slashing is 50% of the allocated stake — i.e., 50% × 30% = 15% of the LRT’s total restaked stake is lost.
7.3 The cascade — what auditors must trace
flowchart TB Slash["AVS-A slashes 50% of operator's<br>allocation (30% of LRT stake)"] Slash --> LRTReport["LRT accounting:<br>15% loss on total backing"] LRTReport --> ExchangeRate["LRT exchange rate drops:<br>1 xETH = 0.85 ETH"] ExchangeRate --> LendingOracle["Lending market reads new rate<br>(when? how often?)"] LendingOracle --> HF["User's collateral: 1000 × 0.85 = 850 ETH<br>Borrow: 700 ETH<br>HF = (850 × 0.75) / 700 = 0.91 → underwater"] HF --> Liq["Liquidation triggered<br>liquidator buys 700 ETH worth of xETH<br>at 0.85 ETH/xETH with liquidation bonus"] Liq --> LRTPool["Liquidator dumps xETH into AMM<br>price drops further → 0.80 ETH"] LRTPool --> MoreLiq["More users near-LTV go underwater<br>second wave of liquidations"] MoreLiq --> Sell["xETH market price detaches from<br>canonical rate; lending market<br>using market price now mis-prices"] Sell --> BadDebt["If liquidations fail to fully cover<br>(insurance fund inadequate),<br>protocol takes bad debt"]
7.4 Quantifying the damage
Worked numbers (illustrative, with assumptions):
- LRT TVL: 100,000 ETH equivalent (= 100,000 xETH at peg).
- Slashing: 15,000 ETH lost (15% of stake).
- New exchange rate: 1 xETH = 0.85 ETH.
- Total xETH used as collateral in lending market: assume 20% of supply = 20,000 xETH backing 14,000 ETH borrowed at avg 70% LTV.
- After slashing: 20,000 xETH × 0.85 = 17,000 ETH collateral value.
- 17,000 × 0.75 = 12,750 ETH safe-borrow capacity. Existing 14,000 ETH borrowed → 1,250 ETH instant bad-debt creation if the liquidation auction takes time to clear.
- Liquidations: liquidators buy xETH at a discount and dump into AMM. If AMM has 5,000 xETH depth and liquidators dump 5,000+ xETH, market price drops further → second wave.
Audit-relevant outputs of this model:
- Liquidator profit threshold: is the liquidation bonus high enough to incentivize fast liquidation under stress?
- AMM depth: is there enough on-chain liquidity to absorb forced selling without a death spiral?
- Oracle update frequency: if the lending market reads the LRT rate only every 8h, position-holders have an arbitrage window to deposit / withdraw before the oracle updates.
- Bad-debt absorption: does the lending market have a reserve fund? Does it socialize via index reduction? Who eats the loss?
7.5 What makes this systemic
In isolation, a 15% slash and a thousand ETH of bad debt is unpleasant but not catastrophic. What makes the cascade systemic:
- Same LRT used across multiple lending markets. Each gets bad debt simultaneously.
- The same LRT in pegged AMM pools (e.g., xETH/wstETH on Curve). Slashing breaks the peg in the pool; LPs lose. The pool’s price feed (read by yet other protocols) is now wrong.
- Vaults holding LRT. Yearn / Morpho / Pendle-style vaults aggregate LRT positions. Slashing hits vault share price; users redeem; vault has to liquidate at bad prices; cascade continues.
- Cross-chain echoes. LRT on L2 (or via a cross-chain wrapper) may have stale exchange rate during the cascade; arbitrageurs exploit the lag.
This is why Vitalik’s “don’t overload Ethereum’s consensus” warning matters: at sufficient TVL and dependency-tree depth, a single AVS slashing can cascade into hundreds of DeFi protocols. The protocol-level slashing is honest math; the systemic damage is the externality.
Auditor’s findings shape: when reviewing a protocol that accepts LRT as collateral, this analysis (with quantitative numbers fitted to the actual market) should appear in the trust-assumptions section. The protocol may be code-correct; its risk model may still be off by an order of magnitude.
8. Audit Angles — the checklist
8.1 LST contract audit (Lido-shaped)
- Share/asset math correctness: verify
getPooledEthBySharesandgetSharesByPooledEthagainst the canonical formula. Rounding direction. - Oracle report sanity: limits on per-frame CL balance change, validator count change, withdrawal volume.
- Oracle quorum: M-of-N is enforced; member-set rotation is time-locked.
- Withdrawal queue: FIFO preserved; checkpoint pricing correct; NFT transfer doesn’t break ownership.
- Negative rebase: properly absorbed; oracle sanity check catches; emergency path exists.
- MEV / EL rewards path: separation from withdrawal vault; no double-counting.
- DSM (DepositSecurityModule): guardian set; signature verification; front-run protection on
deposit(). - Operator registry: add/remove operator authorization; key-rotation; stuck-validator handling.
- Pause / emergency: scope, time-lock, recovery.
- Upgradeability: proxy admin; storage layout preservation (see Tuan-05-Vulnerability-Classes-Part-1 §4).
- Reentrancy: every external call from oracle-report / deposit / withdraw paths.
- wstETH wrapper: lossless wrap/unwrap; no minting outside share-deposit path.
8.2 Restaking core audit (EigenLayer-shaped)
- Strategy share accounting: deposit/withdraw conservation across all strategies.
- Operator delegation: delegate/undelegate atomicity; queue-based undelegate with delay.
- AVS opt-in: only operator can opt in; opt-out has delay.
- Allocation sums: per-operator allocation across AVSs ≤ 100%; rounding.
- Slashing accounting: slash decrements strategy shares correctly; pass-through to restakers proportional.
- Redistributable slashing: destination vault verified; time-locked; not arbitrary.
- Withdrawal queue: 7-day delay; can be re-targeted by slashing during queue.
- Reward distribution: per-AVS rewards routed to operators + restakers with correct fee splits.
- Pause / emergency: at the right granularity (per-strategy, per-AVS?).
- Frequency of reallocation: rate-limited to prevent slashing-dodging.
8.3 LRT audit (ether.fi / Renzo / Kelp / Puffer shape)
- Exchange rate computation: derived from canonical sources (LST oracles + EigenLayer state), not from market price.
- Multi-LST aggregation: per-underlying weights tracked correctly; rounding.
- Slashing pass-through: AVS slashing decrements LRT backing proportionally.
- Operator set management: who can add/remove? Time-locked?
- Withdrawal queue: layered on top of EigenLayer’s queue + LST queue; total delay.
- Cross-chain bridge: validator set, signature scheme, emergency procedures.
- 4626 conformance (if applicable): preview functions, virtual shares, rounding.
- Points / loyalty programs: snapshot logic, flash-loan-resistance.
- Fee accumulator: protocol fee, operator fee, restaker yield split.
- Oracle for downstream consumers: does the LRT expose a
getExchangeRate()view that’s hard to manipulate? Is there a TWAP version?
8.4 Downstream-integration audit (lending / AMM / vault accepting LRT)
- Oracle source: canonical LRT exchange rate, NOT secondary-market AMM spot.
- Stale data handling: max staleness for LRT exchange rate; fallback behavior.
- LTV / collateral factor: conservative enough to absorb a slashing event (model slashing-cascade scenario).
- Liquidation parameters: bonus, close factor — does a single liquidator have enough incentive to act during a stress event?
- Bad debt absorption: insurance fund, socialization, or write-off path.
- Pause / emergency: can the protocol disable LRT-backed borrowing rapidly?
- Multi-LRT correlation: protocols that accept multiple LRTs as separate collateral types must model correlated slashing events (same underlying LST = same beacon-chain validator set).
9. Lab — Three exercises
9.1 Lab structure
~/web3-sec-lab/wk-bonus-lst/
├── 01-lido-steth-math/
├── 02-mock-lrt/
└── 03-slashing-cascade/
Each is a Foundry project; complete each before moving on.
9.2 Exercise 1 — Audit Lido’s stETH balanceOf math against mainnet
Goal: Use cast to inspect Lido contracts on mainnet. Confirm the share/balance formula by computing balanceOf of a real holder both with cast call and by manual arithmetic from sharesOf, totalSupply, and getTotalPooledEther.
Setup:
mkdir -p ~/web3-sec-lab/wk-bonus-lst/01-lido-steth-math && cd $_
# Pin an Ethereum mainnet RPC (use Alchemy / Infura / your own node)
export ETH_RPC_URL="https://eth-mainnet.alchemyapi.io/v2/<YOUR_KEY>"
# Lido stETH (canonical proxy)
export STETH=0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84
# A known stETH holder (pick a large one from Etherscan)
export HOLDER=0x... # find one with significant stETH balanceTasks:
- Read core view functions:
cast call $STETH "name()(string)" --rpc-url $ETH_RPC_URL
cast call $STETH "totalSupply()(uint256)" --rpc-url $ETH_RPC_URL
cast call $STETH "getTotalShares()(uint256)" --rpc-url $ETH_RPC_URL
cast call $STETH "getTotalPooledEther()(uint256)" --rpc-url $ETH_RPC_URL- Read the holder’s shares and balance:
cast call $STETH "sharesOf(address)(uint256)" $HOLDER --rpc-url $ETH_RPC_URL
cast call $STETH "balanceOf(address)(uint256)" $HOLDER --rpc-url $ETH_RPC_URL- Compute the balance manually in your shell:
balance = shares × totalPooledEther / totalShares
Use cast arithmetic helpers or bc / Python. Confirm it matches balanceOf within ±1 wei (rounding).
- Inspect the AccountingOracle’s last report:
export AO=0x852dE0526e1F236A39ae1f0Ab92eD0c4F1EC4f1c # verify current address on docs.lido.fi/deployed-contracts/
cast call $AO "getLastProcessingRefSlot()(uint256)" --rpc-url $ETH_RPC_URL- Write up (in
notes.md):- The formula in plain English.
- Why two consecutive
balanceOfcalls at different blocks may return different values for the same holder without any transfer. - One real bug class an integrator could hit by failing to model this.
Stretch: query getCurrentReportData() (if exposed) and show that the next rebase will deliver a specific delta of pooled ETH; predict the new balance of your holder post-rebase.
9.3 Exercise 2 — Build a mock LRT in Foundry; identify the dependency tree
Goal: Build a minimal LRT contract that wraps stETH, opts into a mock AVS, and tracks slashing. Map out the dependency tree.
Setup:
mkdir -p ~/web3-sec-lab/wk-bonus-lst/02-mock-lrt && cd $_
forge init --no-commit mock-lrt
cd mock-lrt
forge install OpenZeppelin/openzeppelin-contracts --no-commitSkeleton — src/MockLRT.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface IMockAVS {
function slashOperator(address operator, uint256 bps) external;
function totalSlashedBps(address operator) external view returns (uint256);
}
/// @notice Minimal LRT wrapping stETH and tracking AVS slashing.
contract MockLRT is ERC4626, Ownable {
address public operator;
IMockAVS public avs;
uint256 public allocationBps; // 0..10000 of total stETH allocated to this AVS
error AllocationTooHigh();
constructor(IERC20 _stETH, address _operator, IMockAVS _avs)
ERC4626(_stETH)
ERC20("Mock LRT", "mLRT")
Ownable(msg.sender)
{
operator = _operator;
avs = _avs;
allocationBps = 3000; // 30% allocation
}
function setAllocation(uint256 bps) external onlyOwner {
if (bps > 10000) revert AllocationTooHigh();
allocationBps = bps;
}
/// @notice Total assets backing the LRT, net of slashing.
function totalAssets() public view override returns (uint256) {
uint256 raw = IERC20(asset()).balanceOf(address(this));
uint256 slashedBps = avs.totalSlashedBps(operator);
// Slashing only applies to the allocated fraction.
// Effective loss bps = slashedBps × allocationBps / 10000
uint256 lossBps = (slashedBps * allocationBps) / 10000;
return raw - (raw * lossBps) / 10000;
}
}Mock AVS — src/MockAVS.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MockAVS {
mapping(address => uint256) public totalSlashedBps;
address public admin;
constructor() { admin = msg.sender; }
function slashOperator(address operator, uint256 bps) external {
require(msg.sender == admin, "not admin");
require(bps <= 10000, "too much");
totalSlashedBps[operator] += bps;
if (totalSlashedBps[operator] > 10000) {
totalSlashedBps[operator] = 10000;
}
}
}Test — test/MockLRT.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {MockLRT} from "../src/MockLRT.sol";
import {MockAVS} from "../src/MockAVS.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockStETH is ERC20 {
constructor() ERC20("Mock stETH", "stETH") {}
function mint(address to, uint256 amt) external { _mint(to, amt); }
}
contract MockLRTTest is Test {
MockStETH stETH;
MockAVS avs;
MockLRT lrt;
address operator = address(0xBEEF);
address alice = address(0xA11CE);
function setUp() public {
stETH = new MockStETH();
avs = new MockAVS();
lrt = new MockLRT(stETH, operator, avs);
stETH.mint(alice, 1000 ether);
vm.startPrank(alice);
stETH.approve(address(lrt), type(uint256).max);
lrt.deposit(1000 ether, alice);
vm.stopPrank();
}
function test_dependency_tree_pre_slash() public view {
assertEq(lrt.totalAssets(), 1000 ether);
assertEq(lrt.convertToAssets(lrt.balanceOf(alice)), 1000 ether);
}
function test_slashing_propagates() public {
// AVS slashes operator by 50%; allocation is 30%; effective loss = 15%.
avs.slashOperator(operator, 5000);
assertEq(lrt.totalAssets(), 850 ether);
assertEq(lrt.convertToAssets(lrt.balanceOf(alice)), 850 ether);
}
function test_partial_slash_compounds() public {
avs.slashOperator(operator, 2000); // 20% slash; effective 6% loss
assertEq(lrt.totalAssets(), 940 ether);
avs.slashOperator(operator, 3000); // another 30% slash; cumulative 50%; effective 15%
assertEq(lrt.totalAssets(), 850 ether);
}
}Run:
forge test -vvTasks:
- Run the tests; verify the slashing math matches the cascade model in §7.
- Draw the dependency tree for
MockLRT.totalAssets(). List every external call and view. - Identify three failure modes:
- What happens if
avs.totalSlashedBpsreturns a wrong value (oracle attack)? - What happens if
allocationBpsis changed mid-deposit bysetOwner? (front-running ofsetAllocation) - What happens if
stETH.balanceOf(address(this))is donation-attacked? (ERC-4626 inflation)
- What happens if
- Write a one-paragraph audit finding for each failure mode, in the style of audit-report-template.
Stretch: extend MockLRT to use a multi-AVS allocation array (sum ≤ 10000 bps); add addAVS / removeAVS with time-locked changes; show the bug if changes are not time-locked.
9.4 Exercise 3 — Model a slashing cascade end-to-end
Goal: Build a minimal lending market that accepts MockLRT as collateral; trigger an AVS slashing; quantify the resulting bad debt.
Skeleton — src/MockLending.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IPriceOracle {
/// @return price of `token` denominated in `quote`, scaled to 1e18
function getPrice(address token, address quote) external view returns (uint256);
}
contract MockLending {
using SafeERC20 for IERC20;
IERC20 public immutable collateral; // mLRT
IERC20 public immutable debt; // mock ETH (use stETH for simplicity)
IPriceOracle public oracle;
uint256 public constant LTV_BPS = 7500; // 75% LTV
uint256 public constant LIQ_BONUS_BPS = 500; // 5% bonus to liquidator
mapping(address => uint256) public collateralOf;
mapping(address => uint256) public debtOf;
uint256 public totalCollateralDeposited;
uint256 public totalDebtOutstanding;
uint256 public badDebt;
constructor(IERC20 _coll, IERC20 _debt, IPriceOracle _oracle) {
collateral = _coll;
debt = _debt;
oracle = _oracle;
}
function deposit(uint256 amount) external {
collateral.safeTransferFrom(msg.sender, address(this), amount);
collateralOf[msg.sender] += amount;
totalCollateralDeposited += amount;
}
function borrow(uint256 amount) external {
uint256 collValue = _collateralValue(msg.sender);
require(debtOf[msg.sender] + amount <= collValue * LTV_BPS / 10000, "exceeds LTV");
debtOf[msg.sender] += amount;
totalDebtOutstanding += amount;
debt.safeTransfer(msg.sender, amount);
}
function _collateralValue(address user) internal view returns (uint256) {
uint256 price = oracle.getPrice(address(collateral), address(debt));
return collateralOf[user] * price / 1e18;
}
function isLiquidatable(address user) public view returns (bool) {
uint256 collValue = _collateralValue(user);
return debtOf[user] * 10000 > collValue * LTV_BPS;
}
function liquidate(address user, uint256 repayAmount) external {
require(isLiquidatable(user), "not liquidatable");
debt.safeTransferFrom(msg.sender, address(this), repayAmount);
uint256 price = oracle.getPrice(address(collateral), address(debt));
uint256 collTaken = repayAmount * 1e18 / price;
collTaken = collTaken * (10000 + LIQ_BONUS_BPS) / 10000;
if (collTaken > collateralOf[user]) {
// not enough collateral - bad debt
uint256 shortfall = (collTaken - collateralOf[user]) * price / 1e18;
badDebt += shortfall;
collTaken = collateralOf[user];
}
collateralOf[user] -= collTaken;
debtOf[user] -= repayAmount;
totalDebtOutstanding -= repayAmount;
collateral.safeTransfer(msg.sender, collTaken);
}
}Mock oracle reading from MockLRT:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {MockLRT} from "./MockLRT.sol";
contract MockOracle {
MockLRT public immutable lrt;
constructor(MockLRT _lrt) { lrt = _lrt; }
function getPrice(address /*token*/, address /*quote*/) external view returns (uint256) {
// Price of 1 mLRT in stETH, scaled to 1e18
return lrt.convertToAssets(1e18);
}
}Cascade test — test/Cascade.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {MockLRT} from "../src/MockLRT.sol";
import {MockAVS} from "../src/MockAVS.sol";
import {MockLending} from "../src/MockLending.sol";
import {MockOracle} from "../src/MockOracle.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockStETH is ERC20 {
constructor() ERC20("Mock stETH", "stETH") {}
function mint(address to, uint256 amt) external { _mint(to, amt); }
}
contract CascadeTest is Test {
MockStETH stETH;
MockAVS avs;
MockLRT lrt;
MockOracle oracle;
MockLending lending;
address operator = address(0xBEEF);
address alice = address(0xA11CE);
address liquidator = address(0xCAFE);
function setUp() public {
stETH = new MockStETH();
avs = new MockAVS();
lrt = new MockLRT(stETH, operator, avs);
oracle = new MockOracle(lrt);
lending = new MockLending(lrt, stETH, oracle);
// Alice: deposit 1000 stETH → 1000 mLRT; deposit 1000 mLRT as collateral; borrow 700 stETH
stETH.mint(alice, 1000 ether);
// seed the lending pool with stETH debt liquidity
stETH.mint(address(lending), 10000 ether);
// liquidator funds
stETH.mint(liquidator, 10000 ether);
vm.startPrank(alice);
stETH.approve(address(lrt), type(uint256).max);
lrt.deposit(1000 ether, alice);
lrt.approve(address(lending), type(uint256).max);
lending.deposit(1000 ether);
lending.borrow(700 ether);
vm.stopPrank();
}
function test_pre_slash_healthy() public view {
assertFalse(lending.isLiquidatable(alice));
}
function test_slash_triggers_cascade() public {
// AVS slashes operator 50%; allocation 30%; effective 15% loss on backing.
avs.slashOperator(operator, 5000);
// Alice's collateral value: 1000 mLRT × 0.85 stETH/mLRT = 850 stETH
// Borrow: 700 stETH
// HF = (850 × 0.75) / 700 = 0.911 → liquidatable
assertTrue(lending.isLiquidatable(alice));
// Liquidator repays 700 stETH; takes collateral at 0.85 stETH/mLRT + 5% bonus
// Expected: 700 / 0.85 = 823.5 mLRT × 1.05 = 864.7 mLRT
// But Alice only has 1000 mLRT — so collTaken ≤ 1000 mLRT (fits)
vm.startPrank(liquidator);
stETH.approve(address(lending), type(uint256).max);
lending.liquidate(alice, 700 ether);
vm.stopPrank();
assertEq(lending.debtOf(alice), 0);
// Some bad debt may have accumulated if collTaken > collateralOf
emit log_named_uint("bad debt accrued (stETH wei)", lending.badDebt());
}
function test_full_slash_creates_bad_debt() public {
// AVS slashes operator 100% on its 30% allocation; effective 30% loss
avs.slashOperator(operator, 10000);
// Collateral value: 1000 × 0.70 = 700 stETH
// Borrow: 700 stETH
// HF = (700 × 0.75) / 700 = 0.75 → very underwater
assertTrue(lending.isLiquidatable(alice));
vm.startPrank(liquidator);
stETH.approve(address(lending), type(uint256).max);
lending.liquidate(alice, 700 ether);
vm.stopPrank();
// Expected bad debt: liquidator needs more collateral than Alice has
// 700 stETH / 0.70 stETH/mLRT × 1.05 = 1050 mLRT requested
// Alice has 1000 mLRT → 50 mLRT shortfall × 0.70 = 35 stETH bad debt
assertGt(lending.badDebt(), 0);
emit log_named_uint("bad debt accrued (stETH wei)", lending.badDebt());
}
}Run:
forge test -vv --match-contract CascadeTestTasks:
- Run the tests; observe the bad-debt numbers.
- Vary the parameters: change LTV to 80%, slashing to 20%, allocation to 50%. Plot the bad debt vs slashing magnitude (script it; Python + matplotlib is fine).
- Add a second user with a different position size; show how their position drags the system into worse bad debt (correlated liquidations).
- Add a second AVS in the MockLRT (modify allocation tracking); show that two simultaneous small slashings (e.g., 20% each on different AVSs) can produce worse damage than one 40% slashing on a single AVS, if allocations don’t overlap correctly.
- Write a one-page report:
- “If I were auditing a protocol that accepts LRT collateral at 75% LTV, what would I require the LRT issuer to guarantee?”
- Concrete quantitative recommendations (slashing buffer, oracle staleness, insurance fund).
Stretch: build a Foundry invariant test for the lending market with an LRT-collateral handler; the invariant is “after any user-callable operation, totalCollateralValue ≥ totalDebtOutstanding - badDebt”. Watch how slashing breaks the invariant, then how a properly-parameterized liquidation closes it again.
10. Anti-patterns (LST/LRT-specific)
Add to your audit checklist:
- Holding raw stETH (rebasing) and treating it as monotonic — balance changes without your contract being called.
- Reading LRT exchange rate from market price (AMM spot) instead of canonical contract view — peg breaks under stress.
- No staleness check on LRT/LST oracle reads — stale price during cascade gives free arbitrage.
- Cross-chain LRT with 1-of-N bridge validator — single forged signature → phantom mint (Kelp 2026 shape).
- Withdrawal queue assumed instant — under stress, days/weeks; integrator’s redemption-as-liquidity model breaks.
- No allocation-sum-≤-100% check in restaking-allocation tracking — double-counted stake.
- Mutable AVS opt-in without delay — operator dodges slashing by opting out before execution.
- Same collateral across multi-AVS without isolating — correlated slashing cascades.
- Hardcoded LTV for LRT collateral without modeling slashing cascade — bad debt under one 15% slash.
-
syncRewards-style permissionless reward injection without rate-limit — sandwich opportunities around the call. - Naive
balanceOfaccounting for stETH-holding contracts — drift on every rebase. - No emergency pause on AVS opt-out / withdrawal — cannot stop bleeding during incident.
- Operator selection without diversity check — slashing-correlation risk concentrated.
- Snapshot-based airdrop/loyalty without flash-loan resistance — gameable point-farming.
11. Trade-offs and Open Debates
| Decision | Option A | Option B | Auditor view |
|---|---|---|---|
| LST integration token | Raw rebasing (stETH) | Wrapped (wstETH) | Wrapped, almost always. Raw only if you genuinely need the rebase semantics. |
| LRT oracle source | Canonical exchange-rate view | Market AMM spot | Canonical. Market spot under stress is the bug. |
| LRT collateral LTV | Same as LST (≈ 75–80%) | Conservative haircut (≈ 60–65%) | Haircut — slashing risk is additional. |
| Restaking framework | EigenLayer (established, slashing live) | Symbiotic / Karak (newer, flexible) | Established for risk-averse audits; newer if the protocol’s economic model fits the modular design — but expect more findings. |
| LRT bridging strategy | Native rollup bridge (CCIP, canonical) | LayerZero OFT / Wormhole | Native bridge if available; OFT with multi-validator minimum if not. 1-of-N OFT = critical finding. |
| Slashing absorption | Socialized across all token holders (Lido) | Operator-bond-first (Rocket Pool) | Depends on token’s design promise; auditor’s job is to verify the implementation matches the promise, and the promise is documented. |
| AVS opt-in policy (for LRT) | Restrictive (only audited AVSs) | Permissive (operator’s choice) | Restrictive for retail-facing LRT; permissive ones must document AVS risk publicly. |
12. Quiz (self-check, ≥80% to advance)
-
Q: Why does
balanceOf(account)of stETH change between blocks even when no transfer occurs? A: stETH stores shares per account;balanceOf=shares × totalPooledEther / totalShares. The AccountingOracle’s daily report updatestotalPooledEther(and possiblytotalShares), implicitly rebasing every balance. -
Q: What’s the difference between rebasing (stETH) and exchange-rate (rETH) LSTs from an integrator’s perspective? A: Rebasing changes the balance dynamically; exchange-rate keeps the balance fixed and grows the ETH-per-token rate. Integrators prefer exchange-rate (and the
wstETHwrapper for stETH) because internal accounting againstbalanceOfis simpler and matches the user’s mental model. -
Q: A protocol accepts wstETH as collateral and reads the wstETH/ETH price from a Curve pool. What’s the audit finding? A: The pool’s spot price is manipulable (especially under stress / low liquidity). The canonical price is
wstETH.stEthPerToken()×stETH.getPooledEthByShares(1e18)— the protocol-internal rate. Reading from the AMM is a depeg-risk amplification. -
Q: In EigenLayer’s “unique stake allocation” model, why is a delay on allocation changes necessary? A: Without a delay, an operator could observe an impending slashing on AVS-A and instantly re-allocate stake to AVS-B before slashing executes, dodging the loss. The delay ensures slashing applies to the allocation in effect at the time of the violation.
-
Q: A LRT protocol uses an off-chain points system that snapshots holders every Monday. What’s the attack? A: Flash-loan-buy the LRT on Sunday night, hold across snapshot, sell Monday morning, harvest points. Mitigation: time-weighted snapshots, deposit lock-up, or non-transferable points accrued on-chain via per-block accumulation.
-
Q: Walk through a slashing cascade: 1 AVS slashes operator 50% on a 30% allocation. LRT TVL is 100,000 ETH; 20,000 ETH worth of LRT is collateral in a lending market at 70% avg LTV. Quantify approximate bad debt. A: Effective loss: 50% × 30% = 15% of backing → 1 LRT now = 0.85 ETH. Collateral value drops from 20,000 → 17,000 ETH; safe-borrow at 75% LTV = 12,750 ETH; existing borrow 14,000 ETH → ~1,250 ETH gap. Liquidations partially close, but liquidator dump on thin AMM pushes price further, and incremental bad debt may stretch to several thousand ETH depending on liquidation parameters and AMM depth.
-
Q: Why is a 1-of-1 LayerZero OFT adapter a critical-severity finding on an LRT contract? A: A single forged signature from the lone validator can instruct the adapter to release tokens not backed by deposits. This is exactly what happened to Kelp DAO rsETH in April 2026 — 116,500 phantom rsETH minted on mainnet via one forged sig. Recommended minimum: multi-sig N-of-M with M ≥ 3; prefer canonical chain-native bridges.
-
Q: Why does Vitalik argue against “overloading Ethereum’s consensus” via restaking? A: If restaking protocols build user expectations that Ethereum social consensus should fork to bail out AVS failures, that politicizes Ethereum’s base layer for application-specific bailouts. Protocol-level slashing is fine; the social expectation of a rescue fork is the systemic risk.
-
Q: In Frax ETH, why does the architecture separate
frxETH(1:1 peg, no yield) fromsfrxETH(4626 vault, accrues yield)? A: It cleanly separates medium-of-exchange from yield-bearing collateral.frxETHis usable in AMMs without breaking peg semantics;sfrxETHholders explicitly opt into yield. The cost:frxETH’s peg depends on Frax-controlled AMOs (algorithmic market operations), not a protocol-level redemption. -
Q: An auditor reviews a lending market that accepts ezETH at 78% LTV with no specific slashing buffer. The protocol points to “audits of EigenLayer and Renzo” as justification. What’s the audit finding? A: Trust-assumption gap: even if EigenLayer and Renzo are bug-free, ezETH is exposed to AVS slashing (cumulative across the AVSs Renzo opts into), which can degrade ezETH backing by single-digit percent in one event. An LTV of 78% leaves only ~22% buffer; a single 15% slashing event creates immediate bad debt. Recommendation: lower LTV to ≤ 65% for LRT collateral, OR add an explicit slashing-buffer parameter that the protocol can update.
13. Bonus Chapter Deliverables
- All three lab exercises reproduced; tests passing.
- Notes file:
~/web3-sec-lab/wk-bonus-lst/notes.mdwith your answers to the quiz in your own words. - Dependency tree (a real Mermaid or hand-drawn diagram) of one LRT of your choice — ether.fi or Renzo — from validator to a real lending-market consumer. Annotate each edge with the failure mode.
- Slashing-cascade quantitative model in a spreadsheet or Python notebook, parameterized on (LRT TVL, % collateral in lending, avg LTV, AVS slashing magnitude, AMM depth). Output: bad debt as a function of slashing magnitude.
- One paragraph: “If a protocol asks me to audit a new LRT this month, what’s the first artifact I demand from them before reading code?“
14. Where this leads
This bonus chapter sits at the intersection of Tuan-08-DeFi-Security-AMM-Lending-Vault (the protocol-economic mindset), Tuan-09-Oracle-MEV-Economic-Attack (oracle and price-feed manipulation), and Tuan-Bonus-Stablecoin-Economic-Modeling (cascading depeg analytics). It’s the highest-TVL, highest-dependency-tree-depth surface in 2026 DeFi.
The auditor who masters this surface has a near-monopoly on a class of work: post-LRT-bridge-exploit triage, pre-launch reviews of new LRTs, lending-market parameter reviews for LRT-collateral markets, and post-incident bad-debt forensics. None of those skills come from reading the LST docs alone — they come from drawing the dependency tree, modeling slashing cascades quantitatively, and recognizing the cross-protocol failure mode at sight.
Cross-link reading order from here: Tuan-09-Oracle-MEV-Economic-Attack for the oracle layer; Tuan-10-Bridge-Cross-Chain-Security for the bridge layer; Case-Penpie-Pendle-2024 for an LST-collateral lending-market exploit; Tuan-Bonus-Stablecoin-Economic-Modeling for the cascade analytics framework that generalizes the §7 model.
Last updated: 2026-05-16 See also: Roadmap · References · MOC-Web3-Security-Mastery