YearnV3 Base Operations

Yearn V3's core operations focuses on the logic required to implement the ERC4626 standard.

ERC-4626 Core Requirements

As a reminder, the ERC4626 core requirements are reproduced below:

Asset Operations

// Deposit/Withdraw Interface
function deposit(uint256 assets, address receiver) returns (uint256 shares)
function mint(uint256 shares, address receiver) returns (uint256 assets) 
function withdraw(uint256 assets, address receiver, address owner) returns (uint256 shares)
function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets)

// Max Limits
function maxDeposit(address) returns (uint256)
function maxMint(address) returns (uint256)
function maxWithdraw(address) returns (uint256)
function maxRedeem(address) returns (uint256)

// Preview Functions
function previewDeposit(uint256 assets) returns (uint256 shares)
function previewMint(uint256 shares) returns (uint256 assets)
function previewWithdraw(uint256 assets) returns (uint256 shares)
function previewRedeem(uint256 shares) returns (uint256 assets)

Accounting Views

// Core Accounting
function totalAssets() returns (uint256)
function convertToShares(uint256 assets) returns (uint256)
function convertToAssets(uint256 shares) returns (uint256)

// ERC20 Interface
function totalSupply() returns (uint256)
function balanceOf(address) returns (uint256)

// Asset Info
function asset() returns (address)

YearnV3 Asset Operation

Yearn V3's implementation of ERC-4626 follows a strict operation pattern to ensure safety and efficiency:

  1. Pre-operation Validation: Validates limits and state before executing operations.

  2. Asset Transfers and Conversions: Handles the transfer of assets and conversion to/from shares.

  3. Strategy Hooks: Calls strategy-specific hooks to deploy or withdraw funds.

  4. State Updates and Event Emission: Updates internal state and emits relevant events.

Example implementation of deposit():

deposit(uint256 assets, address receiver) external returns (uint256 shares) {
    // Check deposit limits
    require(assets <= _maxDeposit(receiver), "ERC4626: deposit more than max");
    // Calculate shares, revert if zero
    require((shares = _convertToShares(assets, Math.Rounding.Down)) != 0, "ZERO_SHARES");
    
    // Safe transfer assets in first (ERC777 reentrancy protection)
    asset.safeTransferFrom(msg.sender, address(this), assets);
    
    // Deploy funds via strategy hook
    IBaseStrategy(address(this)).deployFunds(asset.balanceOf(address(this)));
    
    // Update total assets
    totalAssets += assets;
    
    // Mint shares 
    _mint(receiver, shares);
    
    emit Deposit(msg.sender, receiver, assets, shares);
}

Share Accounting

Yearn V3 tracks ownership and vault holdings using a dynamic price-per-share (PPS) ratio. This system ensures accurate ownership representation while protecting against manipulation.

Primary State

Field

Description

mapping(address => uint256) balances

Tracks share balances for each address.

uint256 totalSupply

Total shares issued by the vault.

uint256 totalAssets

Total assets managed by the vault.

Conversion Functions

Function

Description

_convertToShares(uint256 assets, Math.Rounding rounding)

Converts assets to shares, considering rounding modes.

_convertToAssets(uint256 shares, Math.Rounding rounding)

Converts shares to assets, considering rounding modes.

Key Features

  • Dynamic PPS calculations based on total assets and supply.

  • Configurable rounding modes for flexible operations.

  • First depositor protection.

  • Share transfer restrictions for security.

  • Balance and allowance management.

Profit Mechanics

Yearn employs a gradual profit realization system to prevent PPS manipulation. When a keeper calls report(), profits are calculated and locked, with fees distributed and profits unlocked linearly.

struct StrategyData {
    uint256 profitUnlockingRate;      // Per-second rate
    uint96 fullProfitUnlockDate;      // Unlock completion time
    uint32 profitMaxUnlockTime;       // Default 3 days
    uint16 performanceFee;            // Default 10%
    uint96 lastReport;                // Last report timestamp
}

function report() external returns (uint256 profit, uint256 loss) {
    // Calculate profit/loss
    uint256 newTotalAssets = _harvestAndReport();
    uint256 oldTotalAssets = totalAssets;
    
    if (newTotalAssets > oldTotalAssets) {
        // Lock profits
        profit = newTotalAssets - oldTotalAssets;
        sharesToLock = _convertToShares(profit);
        
        // Calculate and distribute fees
        totalFees = (profit * performanceFee) / MAX_BPS;
        _mint(feeRecipient, feeShares);
        
        // Setup linear unlocking
        profitUnlockingRate = sharesToLock / profitMaxUnlockTime;
        fullProfitUnlockDate = block.timestamp + profitMaxUnlockTime;
    }
    
    totalAssets = newTotalAssets;
    lastReport = block.timestamp;
}

Key Features

  • Linear Unlocking: Term Strategy Vaults default to a profitMaxUnlockTime of 3 days, during which profits are unlocked gradually in a linear fashion to ensure smooth realization and prevent manipulation.

Performance Fee Structure

Yearn implements gradual profit realization and transparent fee distribution mechanisms to align incentives and prevent PPS manipulation.

Profit Realization:

  • Profits are calculated and locked when report() is called.

  • Unlocks linearly over a configurable window (default: 3 days for Term Strategy vaults), ensuring smooth price appreciation for depositors.

For example:

performanceFee = 1000                  // 10% default
profitMaxUnlockTime = 3 days          // Unlock window
managerAddress = 0x0E14e7...          // Strategy control
keeperAddress = 0x54Eef1...           // Report caller
performanceFeeRecipient = 0x0E14e7... // Fee receiver

Performance Fee Structure:

  • Fee Parameters:

    • Default: 10% of realized profits.

    • 80/20 split between vault curator and Yearn protocol.

  • Fee Distribution:

    • Charged during the report() call.

    • Distributed immediately to performanceFeeRecipient.

Profit Unlocking:

  • Profits locked at report time.

  • Linear unlocking over the chosen window to ensure fairness and smooth price-per-share increases.

Keeper Responsibility: report() should be called at regular intervals, typically every 3 days, to maintain consistent profit unlocking. The frequency can be adjusted to match the unlocking window.

Last updated