Back to Blog 

AuditDeFiWeb3 Security
Yearn vault security: V2 vs V3 architecture, exploits, and defense patterns
16 min
Yearn Finance has quietly become one of the most studied vault architectures in DeFi. Two live generations now coexist: V2 (Vyper
Vault.vy with a Solidity BaseStrategy harness) and V3 (Vyper VaultV3.vy plus the chain-wide Solidity TokenizedStrategy proxy at 0xBB51273D6c746910C7C06fe718f30c936170feD0).What makes Yearn a useful case study isn't just the code — it's the track record. Every confirmed loss in Yearn's history traces to legacy v1 contracts, external-protocol oracle dependence, or integrators using
pricePerShare() as collateral valuation. The V2 and V3 cores have survived without a direct exploit.This post is a dense, audit-focused reference for teams reviewing Yearn-based protocols or designing similar vault systems.
Zealynx take: Why Yearn's architecture matters for audit clients
If you're building a yield aggregator, an ERC-4626 wrapper, or anything that integrates with Yearn, the lessons baked into V2 → V3 map directly onto your threat model. Multi-strategy debt isolation, locked-profit degradation, and internal balance tracking aren't academic — they're codified answers to real incidents.
At Zealynx, we audit vault stacks, tokenized strategies, and DeFi aggregators inspired by Yearn's design. If your protocol touches strategy-level code that reads pool state, mints to self, or uses delegatecall proxies, the patterns below are a checklist to run against your own repo.
V2 vault mechanics: contract-level precision

Vault.vy is a monolithic Vyper contract (implements: ERC20, not ERC-4626). Its invariants sit in a small constants block:MAXIMUM_STRATEGIES = 20DEGRADATION_COEFFICIENT = 1e18MAX_BPS = 10_000SECS_PER_YEAR = 31_556_952
StrategyParams holds nine fields per registered strategy: performanceFee, activation, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, lastReport, totalDebt, totalGain, totalLoss. The fixed-length withdrawalQueue: address[20] is iterated on every withdraw call with no per-call override.Share pricing and the anti-sandwich primitive
Share value uses free funds:
1_shareValue(shares) = shares * (totalAssets - _calculateLockedProfit()) / totalSupply
_calculateLockedProfit() linearly unlocks the last reported gain at lockedProfitDegradation (default ~6 hours). A depositor who enters one block after a profitable report() cannot capture the locked portion. The deposit invariant is deliberately deposit-unfavorable when profit is fresh.The report lifecycle
report(gain, loss, _debtPayment) is the strategy → vault accounting entry. Order matters:- Loss reduces
strategy.debtRatioandvault.debtRatioproportionally _assessFeesmints management, strategist, and governance fees as shares at pre-update PPS- Credit is transferred between vault and strategy
self.lockedProfit = gain - totalFeesreplaces any residual locked profit
creditAvailable caps flow by min(strategy_debtLimit - strategy_totalDebt, vault_debtLimit - vault_totalDebt, totalIdle), bounded by minDebtPerHarvest and maxDebtPerHarvest.The maxLoss footgun
withdraw(maxShares, recipient, maxLoss=1) iterates withdrawalQueue, breaks on ZERO_ADDRESS (queue terminator), and calls Strategy.withdraw(amountNeeded) on each. Critically the default maxLoss = 1 bp (0.01%): any strategy with unrealized or near-realized loss ≥ 0.01% causes assert totalLoss <= maxLoss * (value + totalLoss) / MAX_BPS to revert.Code4rena's Popcorn contest (finding #581) documented this as a silent denial-of-service against integrators during Aave bad-debt episodes. If you're building on top of Yearn, this is the first thing to audit.
V3 modular architecture and the Tokenized Strategy proxy pattern

VaultV3.vy (Vyper 0.3.7, API_VERSION = "3.0.4") is an ERC-4626-native allocator whose scope shrank dramatically. StrategyParams is only four fields — activation, last_report, current_debt, max_debt — because fees live in an external Accountant, debt allocation in an external Debt Allocator, and per-strategy performance logic inside each Tokenized Strategy.total_idle is manually tracked ("Replacing balanceOf(this) to avoid price_per_share manipulation") — the explicit anti-donation attack invariant.The 14-role bitmap
V3 replaces V2's five-address governance with a 14-bit Vyper
enum Flag covering roles like ADD_STRATEGY_MANAGER, DEBT_MANAGER, REPORTING_MANAGER, EMERGENCY_MANAGER, and PROFIT_UNLOCK_MANAGER. The role-manager API exposes set_role, add_role, remove_role, plus set_open_role(role) that makes a single role permissionless.yAudit's June-2023 report explicitly warns: "a mistake, such as opening a role that should remain closed, could potentially result in irreversible and damaging effects on the entire vault." In practice only
REPORTING_MANAGER is a defensible candidate for opening.Profit unlocking via mint-to-self
V3 replaces V2's locked-profit scalar with share-level accounting. On
process_report the vault mints shares_to_lock = convertToShares(gain + refunds - fees) to itself, and vests them via:1_unlocked_shares() = profit_unlocking_rate * (block.timestamp - last_profit_update) / MAX_BPS_EXTENDED
_total_supply() = total_supply - _unlocked_shares(), so PPS increases monotonically over the unlock window. Losses burn locked shares first before touching realized PPS.The TokenizedStrategy delegatecall pattern
This is the V3 innovation auditors must internalize. A tokenized strategy is itself a fully ERC-4626-compliant vault, deployed directly by a strategist, that delegates all standard plumbing to a single immutable
TokenizedStrategy implementation deployed at the same deterministic CREATE2 address on every chain (0xBB51273D6c746910C7C06fe718f30c936170feD0).Mechanism:
BaseStrategy.fallback() delegatecalls to that address. Storage is namespaced under bytes32(uint256(keccak256("yearn.base.strategy.storage")) - 1) — an ERC-7201-adjacent pattern.The strategist's only job is to implement three callbacks:
_deployFunds(uint256)_freeFunds(uint256)_harvestAndReport() → uint256
Optional:
_tend, _tendTrigger, availableDepositLimit, availableWithdrawLimit, _emergencyWithdraw.V3 footguns auditors must catch
From storm0x's "V3 Strategies GOTCHAS":
- Selector collisions between strategy-author functions and TokenizedStrategy's ABI silently route the wrong way through the fallback
- Inheriting contracts that declare their own ERC-20 / Ownable state (OpenZeppelin bases are notably dangerous) corrupts
StrategyDatavia slot collision - Direct
_mintfrom strategy code breaks the profit-locking invariant - Assembly writes to the namespaced storage slot must be categorically forbidden in review
Action item: run a selector-collision linter against every strategy plus grep for
sstore with the reserved slot constant.Risk surface: what actually breaks vaults

Smart contract and accounting risks
Reentrancy at the V2 Vault level is blocked by Vyper's keyed
@nonreentrant locks; V3 uses a single-byte entered flag in StrategyData on TokenizedStrategy. The live attack surface is cross-contract reentrancy: a strategist override that calls into another tokenized strategy during _deployFunds / _freeFunds can bypass the per-slot guard because each strategy has its own entered flag.Rounding and double-rounding are the most common audit finding class. yAudit's review caught double-rounding in
mint(), off-by-one in _redeem's loss share, and boundary rounding in withdraw(). Recurring lesson: incorrect estimatedTotalAssets / _harvestAndReport is the #1 root cause of silent loss.Donation and inflation attacks
V2's early versions read
token.balanceOf(self) in totalAssets(), making donations inflate PPS — mitigated from API 0.4.4 forward by internal totalIdle tracking. V3 extends this to both the vault and each TokenizedStrategy.Crucially, V3 does not use OpenZeppelin's virtual-shares-and-decimals-offset approach and does not burn dead shares. Defense is operational: deployment playbook sets
deposit_limit = 0 at init, governance seeds a non-trivial first deposit, then opens deposits in a batched multisig transaction.Permissionless factory vaults break this assumption — anyone can deploy an empty vault, and the first naïve depositor is exposed to the classical 1-wei-deposit-plus-donation skew. See our deeper treatment of this pattern in AMM security foundations.
Oracle and read-only reentrancy
Yearn's core contracts impose no TWAP enforcement; oracle choice is entirely strategy-author discretion. The most dangerous pattern is
ICurvePool.get_virtual_price() or ICurvePool.get_dy(i, j, dx) inside _harvestAndReport on pools that transfer raw ETH or ERC-777 tokens during remove_liquidity.ChainSecurity's April-2022 disclosure established that Curve pool state is transiently inconsistent inside the ETH callback. Yearn's mitigation pattern: a reentrancy "poke" via
pool.remove_liquidity(0, [0,0]) immediately before reading get_virtual_price. Our deep dive on Curve Finance core mechanics covers this in detail.The reusable heuristic: never derive
totalAssets from any function whose return value is a function of live pool state readable during a callback.Governance risk
Yearn's main multisig
ychad.eth is a 6-of-9 Gnosis Safe. Under Governance 2.0 power distributes across yTeams. Yearn does not interpose an on-chain Timelock between the multisig and core vault parameters — strategy additions, debt-ratio changes, and registry endorsements all execute atomically on multisig approval.This is a trust assumption auditors and integrators should surface explicitly. The catastrophic path: compromise six signers →
addStrategy(evilStrategy, 10000, 0, MAX, 0) → harvest() on existing strategies → evilStrategy._deployFunds exfiltrates. V3's requirement that ADD_STRATEGY_MANAGER plus MAX_DEBT_MANAGER both act mitigates single-role compromise but not full multisig takeover.Historical exploits: three lessons, three different failure modes
yDAI v1 — February 2021 (~$11M pool loss)
StrategyDAICurve on yDAI v1 deposited into Curve 3pool. Root cause in one sentence: v1's publicly-callable earn() pushed vault balances into Curve using a 1% slippage bound and a 0% withdrawal fee, so a flash-loan-driven 3pool imbalance could sandwich the deposit path.Attack: flash-loan ~116,000 ETH from dYdX → imbalance Curve 3pool → call
earn() on yDAI → reverse the imbalance → redeem yDAI shares. Five iterations. Yearn multisig halted in ~11 minutes, saving 24M of 35M DAI.Architectural response: V2's whole design — non-public harvest, multi-strategy isolation, locked-profit degradation,
totalIdle airdrop protection, emergencyShutdown — is the codified post-mortem.Cream Finance — October 2021 (0 Yearn loss)
Cream priced yUSD collateral using
yUSDVault.pricePerShare(), a share-based oracle manipulable via donation. Attacker donated the underlying crvY LP to the yUSD vault (boosting totalAssets without increasing totalSupply), roughly doubling PPS. Cream instantly marked the collateral up; attacker borrowed ~$130M against the inflated valuation.Yearn lost $0; the yUSD vault functioned as designed but was used as a composability oracle by another protocol without donation-resistance. Lesson for integrators: never treat
pricePerShare as an oracle without TWAP, min/max caps, or donation-resistance.Get the DeFi Protocol Security Checklist
15 vulnerabilities every DeFi team should check before mainnet. Used by 30+ protocols.
No spam. Unsubscribe anytime.
yUSDT v1 — April 2023 (~1.6M realized)
yUSDT v1 was deployed in 2020 with a misconfigured Fulcrum iUSDC token address where iUSDT was intended. The bug sat dormant for ~3 years because vault TVL was negligible. Attacker used flash-loan capital to interact with the legacy iUSDC, inflating the vault's internal accounting. A small USDT deposit produced disproportionate yUSDT shares.
V2 vaults were unaffected. Lesson: deprecated contracts remain attack surfaces indefinitely if not emptied and paused; config errors can hibernate for years. Relevant if you're running a pre-audit checklist on an older repo.
Defense patterns: what to bake in
Rate limits by exact value
- V2:
performanceFee ≤ MAX_BPS/2 = 5000,sum(strategy.debtRatio) ≤ MAX_BPS, defaultlockedProfitDegradation≈ 6-hour unlock - V3 TokenizedStrategy:
MAX_FEE = 5000,profitMaxUnlockTime ≤ SECONDS_PER_YEAR, default 10 days - V3 vault:
minimum_total_idlefloor, pluggableIDepositLimitModule/IWithdrawLimitModule
maxLoss semantics auditors must memorize
| Function | Default maxLoss | Behavior |
|---|---|---|
V2 Vault.withdraw | 1 bp | Revert on exceed |
V3 Vault.withdraw | 0 bps | Revert on any loss |
V3 Vault.redeem | MAX_BPS = 10_000 | Accept any loss silently |
Integrators calling
redeem() with defaults can eat 100% loss without reverting — a silent-loss bug class in vaults-of-vaults wrappers. If you're building a defense-in-depth workflow, this is a must-check.Emergency mechanisms
V2
setEmergencyShutdown(bool) is callable by governance in either direction and by guardian only for true. V3 shutdown_vault() is EMERGENCY_MANAGER-only and irreversible — coupled with open roles, a compromised EMERGENCY_MANAGER permanently bricks write operations.Strategy risk scoring
Yearn's Strategy Risk Score is a 1–5 scale across eight dimensions: audit coverage, code review frequency, complexity, protocol safety, team knowledge, testing score, TVL impact, and longevity. Higher-risk strategies are capped at lower debt ratios until they demonstrate in-production safety. The framework explicitly acknowledges Yearn "cannot rely on a traditional waterfall process" due to deploy cadence — the score is the compensating control.
This mirrors how we think about audit ROI: security as a continuous lifecycle rather than a one-time gate.
Comparative posture: Yearn vs Beefy, Morpho, Sommelier
Yearn V3 sits at a specific point on the yield-aggregator design axis: maximum on-chain flexibility + internal-balance anti-donation + immutable strategies + opt-in timelocks.
- Beefy: simpler (single-strategy per vault, keeper-compounded). "Harvest on deposit" removes the MEV-sandwich window
- Morpho MetaMorpho: hardwires timelocks (≥24h V1; 0–3 weeks configurable V2) with Owner/Curator/Allocator/Guardian separation — stronger user-visible safety than Yearn's opt-in role separation
- Sommelier: off-chain strategist-signed instructions relayed through a Cosmos validator set, guarded by
allowedRebalanceDeviationandshareLockPeriod
Architectural correspondence: Yearn's
DEBT_MANAGER ≈ Morpho's Allocator ≈ Sommelier's Strategist; Yearn's MAX_DEBT_MANAGER ≈ Morpho's Curator; EMERGENCY_MANAGER ≈ Morpho's Guardian/Sentinel.Conclusion: heuristics distilled
Yearn's multi-year defense evolution is a readable record of how yield-aggregator security thinking matured. V1's monolithic public-
earn() contracts with spot-DEX pricing were replaced by V2's multi-strategy debt-allocation model, then V3 decomposed the remaining logic into an immutable shared Solidity TokenizedStrategy implementation plus periphery modules.Concrete heuristics for auditors and designers:
- Never derive
totalAssetsfrom live AMM/pool state readable during a callback - Never use
pricePerShareas an oracle without donation-resistance and TWAP - Verify selector collisions between strategy code and TokenizedStrategy
- Forbid raw
sstoreto the namespaced storage slot - Seed any fresh 4626 vault before opening deposits
- Treat
set_open_roleon anything exceptREPORTING_MANAGERas catastrophic - Validate
maxLossdefaults in every withdraw/redeem call path - Recognize that every confirmed Yearn-ecosystem loss originated in legacy v1, integrator oracle misuse, or strategy-author oracle choice — not the vault core itself
The V3 architecture codifies the lessons, but the weakest link moves to wherever an author's
_harvestAndReport reads a manipulable price, or to wherever an integrator reads PPS without caveat.Partner with Zealynx
At Zealynx, we audit DeFi vault stacks, ERC-4626 wrappers, and tokenized strategy systems inspired by Yearn's design. Whether you're forking V3 patterns, integrating with existing Yearn vaults, or building a novel aggregator, we map the threat model onto your specific code — including oracle choice in
_harvestAndReport, selector collisions in delegatecall proxies, and silent-loss maxLoss paths in integrator wrappers.Browse our writeups on Balancer's protocol architecture, Liquity protocol internals, and our broader security engine of DeFi writeup — then reach out.
Get in touch
Planning a vault audit, a fork of V3 patterns, or an integration that reads
pricePerShare? We've reviewed enough of these stacks to know where the silent bugs live. Book a scoping call and we'll walk your specific threat model.FAQ: Yearn vault security
1. What is a tokenized strategy in Yearn V3, and why does it matter for audits?
A tokenized strategy is an ERC-4626-compliant strategy contract that delegates all standard plumbing (ERC-20 logic, profit-locking, fee accrual, reporting, access control) to a single immutable
TokenizedStrategy implementation deployed at the same deterministic CREATE2 address on every chain (0xBB51273D6c746910C7C06fe718f30c936170feD0). The strategist only writes three callbacks: _deployFunds, _freeFunds, and _harvestAndReport.For auditors this matters because the shared implementation is audited once and inherited by every strategy — but strategists can accidentally break the model through selector collisions with the TokenizedStrategy ABI, inheritance that introduces colliding storage slots, or direct
_mint calls that violate profit-locking invariants.2. What is locked-profit degradation and how does it prevent sandwich attacks?
Locked-profit degradation is Yearn V2's mechanism for vesting a strategy's reported gain over time instead of realizing it instantly. After a profitable
report(), the gain is stored in lockedProfit and linearly unlocked at a rate of ~6 hours (default DEGRADATION_COEFFICIENT). Share price is calculated as (totalAssets - lockedProfit) / totalSupply, so a depositor entering one block after a profitable report cannot sandwich the share price.V3 replaces the scalar with share-level accounting: profit shares are minted to the vault itself and vested via an unlock rate, producing the same anti-sandwich property but with cleaner accounting.
3. Why is using pricePerShare as an oracle dangerous?
pricePerShare can change within a single block during process_report, and in V2 versions prior to API 0.4.4 it could be inflated via direct token donations to the vault (since totalAssets read balanceOf(self)). An integrator using pricePerShare as collateral valuation — as Cream Finance did with yUSD in October 2021 — can be manipulated via flash-loan donations into marking collateral at 2x its real value, enabling massive over-borrowing.Safe integration requires TWAP enforcement, min/max caps, and donation-resistant valuation. Yearn's own documentation explicitly warns against
pricePerShare as an oracle.4. What is the maxLoss footgun in Yearn vault withdrawals?
maxLoss is the maximum loss (in basis points) a user will tolerate when withdrawing. The footgun is that defaults vary across functions: V2 Vault.withdraw defaults to 1 bp (revert on any real loss), V3 Vault.withdraw defaults to 0 bps (revert on any loss at all), but V3 Vault.redeem defaults to MAX_BPS = 10_000 — meaning it silently accepts up to 100% loss.For integrators building vaults-of-vaults or wrappers that call
redeem() without passing an explicit maxLoss, this is a silent-loss bug class. Always set maxLoss explicitly at integration boundaries.5. What is read-only reentrancy and how does it affect Yearn strategies?
Read-only reentrancy occurs when a contract can be called back during a state-mutating operation while its storage is in a transiently inconsistent state. In Curve pools that transfer raw ETH during
remove_liquidity, an external contract can reenter during the ETH callback and read get_virtual_price() while pool balances and totalSupply are temporarily out of sync.If a Yearn strategy reads
get_virtual_price() inside _harvestAndReport, an attacker with permissionless report() access can produce a phantom-profit report. The mitigation is a reentrancy "poke" like pool.remove_liquidity(0, [0,0]) before reading, which reverts if the pool is mid-callback. Balancer's equivalent is balancerVault.reentrancyGuardEntered().6. Does Yearn have an on-chain timelock protecting vault parameters?
No. This is a frequently misunderstood property. The 6-of-9
ychad.eth Gnosis Safe can execute strategy additions, debt-ratio changes, and registry endorsements atomically once six signers approve. V3's role separation (requiring both ADD_STRATEGY_MANAGER and MAX_DEBT_MANAGER to act) mitigates single-role compromise but not a full multisig takeover.Integrators should surface this trust assumption explicitly. Contrast with Morpho's MetaMorpho, which hardwires timelocks of ≥24 hours (V1) or 0–3 weeks configurable (V2) between curator actions and execution — a stronger user-visible safety property.
Glossary
| Term | Definition |
|---|---|
| ERC-4626 | Tokenized vault standard providing a unified API for yield-bearing vaults with deposit, withdrawal, and share accounting. |
| Vault accounting | Internal tracking of token balances within a protocol's central contract, separate from actual ERC-20 transfers. |
| Locked profit degradation | Yearn V2 mechanism that vests strategy gains linearly over time to prevent PPS sandwich attacks. |
| Inflation attack | First-depositor exploit that manipulates share price via direct donations to an empty vault. |
| Strategy drift | Divergence between a strategy's intended behavior and its real on-chain execution. |
Get the DeFi Protocol Security Checklist
15 vulnerabilities every DeFi team should check before mainnet. Used by 30+ protocols.
No spam. Unsubscribe anytime.


