Found Academy useful? A $5 donation by May 14 helps us ship more, faster. Every donor counts (QF matching).

Donate

Section 2 of 18

Build
+10 Lynx

Vault: Storage and Constants

What You Are Building

Every later contract in the Vault chain inherits from VaultStorage. Declaring all state up front means each subsequent section can focus on logic instead of bookkeeping, and the cross-section reasoning ("totalDebt must equal the sum of strategies[s].totalDebt across the registry") has a single canonical home.

Compound V2 used the same pattern: CTokenStorage in section 4 declares every state variable the protocol will ever touch; sections 5 through 17 add only behavior. Yearn V2 has fewer contracts than Compound (no separate Comptroller, no separate InterestRateModel), but each contract holds materially more state. By the end of this section the storage layout for the entire vault, including pieces that will not be used until section 16 or 17, is in place.

Why Declare Everything Up Front

Two reasons. First, Solidity storage layout is positional. Adding a state variable in section 7 that section 4 needs to reference forces a refactor (and, worse, would change storage slots in any deployed proxy). Declaring upfront avoids the churn.

Second, the educational intent is that you can read the storage of a Yearn V2 vault as a single document and understand the system. The fields cluster naturally: ERC-20 share-token state, vault-level accounting, locked-profit slots, fees, the strategy registry, roles, emergency state. Each cluster has a single docstring explaining what it is for and which later section gives it meaning.

What Goes Where

Constants (MAXIMUM_STRATEGIES = 20, MAX_BPS = 10_000, SECS_PER_YEAR = 31_556_952, DEGRADATION_COEFFICIENT = 1e18, PERMIT_TYPEHASH). The 20-strategy cap is the most important: it bounds the iteration cost of the withdrawal queue. Any unbounded loop in withdraw() would be a DoS vector; the hard cap is the structural defense.

The StrategyParams struct. Per-strategy bookkeeping. Nine fields: performanceFee, activation, debtRatio, minDebtPerHarvest, maxDebtPerHarvest, lastReport, totalDebt, totalGain, totalLoss. Read this struct top to bottom to internalize the strategy lifecycle: a strategy is registered with an activation timestamp and a debtRatio, gets allocated totalDebt over time (clamped by per-harvest min/max), and accumulates totalGain / totalLoss over its life.

The underlying token and vault activation timestamp. IERC20 token and uint256 activation. We declare IERC20 as a minimal interface inline (six functions: totalSupply, balanceOf, transfer, transferFrom, approve, allowance) so the file compiles without external imports.

ERC-20 share-token state. name, symbol, decimals, totalSupply, balanceOf, allowance, nonces. All used in section 3 when we implement the share-token surface. nonces is for EIP-2612 permit replay protection.

Vault accounting. totalIdle, totalDebt, debtRatio, depositLimit, lastReport. The two important ones are totalIdle (the donation defense in section 6) and totalDebt (the multi-strategy accounting; must equal the sum across the registry).

Locked-profit slots. lockedProfit and lockedProfitDegradation. Built in section 13. Declared here so sections 11 and 13 can both reference them without one importing the other's logic.

Fee state. performanceFee, managementFee, rewards. Built in section 16.

Strategy registry. mapping(address => StrategyParams) strategies and address[MAXIMUM_STRATEGIES] withdrawalQueue. The mapping holds full per-strategy state; the array holds the ordered withdrawal sequence. Note the array is fixed-size (MAXIMUM_STRATEGIES) with address(0) for unused slots. Section 9 maintains both.

Roles and emergency. governance, pendingGovernance, management, guardian, emergencyShutdown. Section 17 implements the role transitions and the shutdown semantics.

Events. Standard ERC-20 Transfer and Approval, plus StrategyReported, StrategyAdded, StrategyRevoked, EmergencyShutdown. Declaring events alongside their logical state keeps the cross-section signal-to-noise high.

Comparison to Compound V2

Compound's CTokenStorage is roughly 30 lines because the CToken is one contract among many; the Comptroller has its own ComptrollerStorage. Yearn's VaultStorage is roughly 200 lines because the vault is the whole thing. Compound spreads the policy engine across CToken + Comptroller; Yearn keeps it in one contract and lets BaseStrategy implementations be the modular layer. Different architectural choice, same separation of state from logic.

Your Task

Open the starter file. Add no logic. Just declare:

  1. The five constants and the PERMIT_TYPEHASH.
  2. The StrategyParams struct with its nine fields.
  3. The IERC20 interface (six functions).
  4. The token and activation fields.
  5. The ERC-20 share-token state (six fields including the two mappings).
  6. The vault accounting state (five fields).
  7. The locked-profit slots (two fields).
  8. The fee state (three fields).
  9. The strategy registry (the mapping and the fixed-size array).
  10. The role and emergency state (five fields).
  11. The five events (ERC-20 standard plus the four vault-specific ones).

Order matters for cleanliness but not for correctness. The reference groups by purpose with section comments; you should match that structure.

The next section adds the ERC-20 surface (transfer, approve, _mint, _burn, plus EIP-2612 permit) on top of the share-token state declared here.

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

Write your implementation, then click Run Tests. Tests execute on the server.