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

Donate

Section 8 of 18

Build
+15 Lynx

BaseStrategy: Storage, Modifiers, Abstract Methods

What You Are Building

The abstract parent contract that every concrete Yearn V2 strategy inherits from. BaseStrategy holds the strategy's role state (governance, strategist, keeper), the vault binding, the emergencyExit flag, and the access modifiers. It also declares the abstract methods that subclasses (e.g. StrategyAaveLender, StrategyCurveStableLP) must implement.

This is the start of the second inheritance chain in the module. The Vault chain runs sections 2-6, 9-11, 13-14, 16-17. The Strategy chain is section 8 here, then section 15 (harvest, tend).

Why Two Inheritance Chains

The vault and the strategy are deployed as separate contracts and developed by separate parties (the vault is governance-owned, the strategy is strategist-owned). Each one has its own state, its own access control, and its own update lifecycle. Putting them in one inheritance chain would entangle their storage layouts and force coordinated upgrades.

Compound did the same with CToken vs Comptroller, separate chains, joined by minimal interfaces. Yearn V2 does it with Vault vs BaseStrategy.

Storage and Roles

IVaultAPI public vault;
address public want_;
address public governance;
address public strategist;
address public keeper;
bool public emergencyExit;

Five addresses and a flag. vault is the contract this strategy is bound to (set once in the constructor). want_ is the underlying token, cached from vault.token() so reads are cheap. governance mirrors the vault's governance for emergency operations. strategist is the person who designed and runs this specific strategy (receives a share of its fees). keeper is the bot or operator authorized to call harvest() and tend() on cadence.

emergencyExit is a one-way flag. Once set, the next harvest() liquidates everything and returns funds to the vault. There is no un-set.

Modifiers

modifier onlyVault() { ... }
modifier onlyGovernance() { ... }
modifier onlyAuthorized() { ... }       // strategist or governance
modifier onlyKeepers() { ... }          // keeper, strategist, or governance
modifier onlyEmergencyAuthorized() { ... } // strategist or governance

Five modifiers covering every access pattern the strategy uses. The vault is a separate principal (it owns withdraw and migrate). The keeper is the most permissive (lots of bots can be keepers; harvest is meant to run frequently). Emergency-authorized is the wider strategist+governance set so either can pull the brake.

Concrete Methods

The vault calls four functions on the strategy:

function want() external view returns (address) { return want_; }
function withdraw(uint256 _amountNeeded) external onlyVault returns (uint256 loss);
function setEmergencyExit() external onlyEmergencyAuthorized;
function migrate(address _newStrategy) external onlyVault;
function isActive() external view returns (bool);

withdraw calls _liquidatePosition(_amountNeeded) (the abstract method below) and then transfers whatever was freed back to the vault. The reported loss is the gap between what was requested and what was delivered.

migrate calls _prepareMigration(_newStrategy) (subclass moves any non-want assets) and then transfers the strategy's want balance to the new address. The vault's migrateStrategy (section 9 + later) updates the registry.

Abstract Methods Subclasses Implement

function estimatedTotalAssets() public view virtual returns (uint256);
function _prepareReturn(uint256 _debtOutstanding)
    internal virtual returns (uint256 _profit, uint256 _loss, uint256 _debtPayment);
function _adjustPosition(uint256 _debtOutstanding) internal virtual;
function _liquidatePosition(uint256 _amountNeeded)
    internal virtual returns (uint256 _liquidatedAmount, uint256 _loss);
function _liquidateAllPositions() internal virtual returns (uint256 _amountFreed);
function _prepareMigration(address _newStrategy) internal virtual;

Six methods. The strategist's job is to fill in these six per protocol. An Aave lender's _adjustPosition calls aavePool.supply(). A Curve LP's _prepareReturn claims CRV+CVX rewards, swaps them to want, and reports the resulting profit. The harvest cycle (section 15) calls these in the right order; the strategist doesn't write the cycle, only the per-protocol logic.

What This Section Does NOT Cover

  • The harvest() and tend() functions themselves, those live in section 15 (StrategyHarvest extends BaseStrategy).
  • Concrete strategy implementations, those are protocol-specific. Yearn V2 maintains dozens of these in their yearn/strategies repository.
  • The healthCheck integration that some Yearn V2 strategies use to validate harvest results before reporting. Out of scope for the core teaching.

What's Next

Section 9 returns to the Vault chain to add addStrategy, revokeStrategy, and the withdrawal-queue management. Sections 10 and 11 build the credit/debt math and the report() cycle that this section's strategy will eventually call into.

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

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