ChainScore Labs
All Guides

Yield Optimizer Vault Architecture Explained

LABS

Yield Optimizer Vault Architecture Explained

Chainscore © 2025

Core Architectural Components

The foundational smart contracts and mechanisms that define a yield optimizer's operation and security.

Vault Contract

The primary deposit and withdrawal interface for users. It holds the pooled user funds and issues receipt tokens representing a share of the total assets. It delegates strategy execution to a separate contract, isolating fund custody from complex logic. This separation is critical for security audits and upgradeability.

Strategy Contract

The autonomous yield-generation engine that interacts with external protocols. It contains the logic for supplying liquidity, farming rewards, and executing swaps. Strategies are often modular, allowing vaults to switch between them. Their performance directly impacts the APY and must be gas-optimized and secure against exploits.

Keeper & Automation

Off-chain or permissionless execution layer that triggers strategy functions like harvesting rewards or rebalancing. It monitors on-chain conditions (e.g., reward thresholds, gas prices) to optimize transaction timing. This automation is essential for maintaining compound interest efficiency and reacting to market opportunities without manual intervention.

Fee Structure

The economic model defining protocol revenue, typically comprising a management fee (annual percentage of AUM) and a performance fee (percentage of harvested yield). Fees are crucial for sustainable development and security. They are often auto-compounded or distributed to governance token stakers, aligning incentives.

Oracle & Pricing

The price feed and data system used to calculate the vault's share price (price per vault token). It accurately values LP tokens, reward tokens, and underlying assets for deposits, withdrawals, and performance reporting. Reliable oracles are vital to prevent manipulation and ensure fair user redemptions.

Access Control & Governance

The permission and upgrade management system, often implemented via a multi-sig or DAO. It controls sensitive functions like setting new strategies, adjusting fees, or pausing contracts in an emergency. This decentralized oversight is a key security feature, distributing trust and preventing unilateral malicious actions.

Deposit and Share Minting Flow

Process overview

1

User Initiates Deposit

User approves vault contract and submits deposit transaction.

Detailed Instructions

The user begins by calling the approve function on the underlying asset's ERC-20 contract (e.g., USDC), granting the vault's address (e.g., 0x...) permission to transfer their tokens. Once approved, the user calls the vault's deposit function, specifying the _amount of underlying tokens to supply. This function internally calls transferFrom to move the user's assets into the vault contract. The vault's total asset holdings are updated, and the contract calculates the equivalent amount of vault shares to mint based on the current price per share (PPS).

  • Sub-step 1: Approve the vault contract as a spender for the underlying asset.
  • Sub-step 2: Call the vault's deposit(uint256 _amount) function.
  • Sub-step 3: Verify the transaction succeeds and the user's wallet balance of the underlying asset decreases.
solidity
// Example interaction via ethers.js await usdcContract.approve(vaultAddress, depositAmount); await vaultContract.deposit(depositAmount);

Tip: Always check the vault's deposit limit and pause state before initiating a transaction to avoid revert.

2

Vault Calculates Share Amount

Contract computes mintable shares using price per share and total supply.

Detailed Instructions

Inside the deposit function, the vault logic calculates the number of shares to mint using the formula: shares = (_amount * totalSupply()) / _totalAssets(). The _totalAssets() is a key state variable representing the vault's estimated total holdings, which may include staked assets and pending rewards. The price per share is derived as _totalAssets() / totalSupply(). If the user is the first depositor (totalSupply() == 0), the minted shares equal a fixed initial amount (e.g., _amount), establishing the initial PPS. This calculation ensures share minting is proportional and prevents dilution of existing shareholders.

  • Sub-step 1: The contract fetches totalSupply() of vault shares.
  • Sub-step 2: It calls the internal _totalAssets() function for the latest asset valuation.
  • Sub-step 3: It applies the minting formula, handling the initial deposit edge case.
solidity
// Simplified vault logic function deposit(uint256 _amount) public returns (uint256) { uint256 shares = 0; if (totalSupply == 0) { shares = _amount; } else { shares = (_amount * totalSupply) / totalAssets; } _mint(msg.sender, shares); return shares; }

Tip: The _totalAssets() function is often non-view and may perform state updates, affecting gas cost.

3

Shares Minted and Transferred

New vault share tokens are minted to the depositor's address.

Detailed Instructions

After calculating the share amount, the vault contract calls its internal _mint function, inherited from an ERC-20 implementation. This increases the totalSupply of vault share tokens and credits the balanceOf the depositor's address (msg.sender) with the newly minted shares. The vault share token itself is a standard ERC-20, representing a pro-rata claim on the vault's underlying assets. The minting event emits a standard Transfer event with the from address set to the zero address (0x000...), signaling creation of new tokens. The user can now hold, transfer, or stake these share tokens.

  • Sub-step 1: The contract's _mint(address to, uint256 amount) function is executed.
  • Sub-step 2: The totalSupply state variable is incremented by the share amount.
  • Sub-step 3: The user's token balance in the vault's storage mapping is updated.
solidity
// Internal minting function (from OpenZeppelin ERC20) function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _totalSupply += amount; _balances[account] += amount; emit Transfer(address(0), account, amount); }

Tip: The vault share token address is the same as the vault contract address, enabling direct balance queries.

4

Assets Deployed to Strategy

Deposited funds are allocated to yield-generating strategies.

Detailed Instructions

Following the share minting, the vault typically deploys the newly acquired underlying assets into one or more integrated yield strategies. This may happen within the same transaction via a call to _earn() or be triggered by a keeper in a subsequent transaction. The vault transfers assets to a designated strategy contract (e.g., 0x...) by calling its deposit() function. The strategy then interacts with external protocols—such as lending markets (Aave, Compound) or liquidity pools (Uniswap, Curve)—to generate yield. The vault's reported totalAssets() now reflects the sum of assets held in the vault and assets actively deployed across all strategies.

  • Sub-step 1: Vault logic determines if the _earn() function should be called based on idle fund thresholds.
  • Sub-step 2: Assets are transferred to the strategy contract via a safe transfer or safeTransfer.
  • Sub-step 3: The strategy contract deposits funds into the target yield protocol.
solidity
// Example of vault delegating to strategy function _earn() internal { uint256 _bal = available(); if (_bal > 0) { IStrategy(strategy).deposit(); } }

Tip: The delay between deposit and strategy deployment can affect immediate yield accrual for the user.

5

State Updated and Events Emitted

Final vault state is recorded and on-chain events are logged.

Detailed Instructions

The deposit transaction concludes by updating key vault storage variables and emitting relevant events. The vault's internal accounting for totalAssets is incremented by the deposit amount. A custom Deposit event is emitted, logging the caller's address (msg.sender), the vault address, the amount of underlying asset deposited, and the amount of vault shares minted. This provides a clear on-chain record for indexers and front-ends. The transaction's gas usage is finalized, and the state change is committed to the blockchain. The user's interaction is now complete, with their position represented by vault share tokens and the underlying assets actively generating yield.

  • Sub-step 1: Update any internal vault accounting variables (e.g., lastReport).
  • Sub-step 2: Emit the Deposit(address indexed sender, uint256 assets, uint256 shares) event.
  • Sub-step 3: Finalize the transaction, committing all state changes.
solidity
// Event emission in vault contract event Deposit( address indexed sender, address indexed owner, uint256 assets, uint256 shares ); // Emitted in the deposit function emit Deposit(msg.sender, msg.sender, _amount, shares);

Tip: Always verify the emitted events in your transaction receipt to confirm successful execution and capture precise share minting data.

Strategy Implementation Patterns

Understanding Vault Strategies

A yield optimizer vault strategy is a smart contract that automates capital allocation to generate returns. It abstracts the complexity of active DeFi management from users. The core pattern involves depositing user funds into a single vault, which then delegates them to a strategy contract. This contract executes a predefined series of actions, like supplying liquidity or lending assets, to farm yield from underlying protocols.

Key Components

  • Vault Contract: Holds user deposits in a single ERC-20 token and manages accounting.
  • Strategy Contract: Contains the active logic for yield generation, isolated from the vault for security.
  • Harvest Function: A permissioned method that claims accrued rewards, sells them for more vault tokens, and reinvests them, compounding yields.
  • Emergency Exit: A safety mechanism allowing the strategy to liquidate all positions and return funds to the vault, often triggered by governance or keepers.

Example Flow

When a user deposits DAI into a Yearn vault, the vault mints shares and sends the DAI to its active strategy. The strategy might deposit that DAI into Aave to earn interest, then periodically harvest the stkAAVE rewards, swap them for more DAI, and redeposit, increasing the value of each vault share.

Fee Structure Comparison

Comparison of common fee models used by yield optimizer vaults.

Fee TypeStandard Vault (Example)Performance Fee Vault (Example)Tiered Fee Vault (Example)

Management Fee (Annual)

0%

0.5%

0.2%

Performance Fee (On Yield)

0%

20%

10% (Tier 1), 15% (Tier 2), 20% (Tier 3)

Deposit Fee

0%

0%

0.1% for deposits > $1M

Withdrawal Fee

0%

0%

0.25% (if withdrawn < 7 days)

Harvest Fee (Gas Cost)

Paid by user on interaction

Embedded in strategy, reduces APY

Shared gas model, cost amortized across vault

Fee Recipient

Protocol Treasury

80% Strategist, 20% Treasury

Protocol Treasury

Fee Calculation Basis

N/A

High-water mark

Realized profits only

The Harvest and Compounding Cycle

Process overview

1

Monitor Performance and Trigger Conditions

The vault's keeper bot or a permissionless user monitors on-chain data to identify the optimal time to harvest rewards.

Detailed Instructions

The system continuously monitors the vault's total assets and the accrued pending rewards from the underlying strategy. The primary trigger is typically a profitability threshold, which is calculated as the estimated harvest profit minus the gas cost of the transaction. For example, a vault might be configured to trigger when the estimated profit exceeds 0.5 ETH. Keepers use off-chain scripts or the harvestTrigger() function in the strategy contract to evaluate this condition.

  • Sub-step 1: Query the strategy's harvestTrigger(uint256 callCost) view function.
  • Sub-step 2: The function returns a boolean by comparing estimatedHarvest() to callCost plus a configurable buffer.
  • Sub-step 3: If true, any address can call the public harvest() function to initiate the cycle.
solidity
function harvestTrigger(uint256 callCost) public view returns (bool) { uint256 harvestProfit = estimatedHarvest(); return harvestProfit > callCost + profitThreshold; }

Tip: The callCost parameter represents the keeper's estimated gas cost, incentivizing efficient execution only when economically viable.

2

Execute the Harvest Transaction

The keeper calls the harvest function, which interacts with the underlying protocol to claim and convert rewards.

Detailed Instructions

When harvest() is called, the strategy contract executes a series of delegate calls to its specific logic. This involves claiming liquidity provider (LP) rewards, staking rewards, or governance tokens from the integrated protocol (e.g., Aave, Compound, Curve). The harvested assets are often in a different token than the vault's primary asset, requiring a swap via a decentralized exchange (DEX) like Uniswap V3. The swap path and slippage tolerance are critical parameters defined in the strategy to minimize MEV and sandwich attack risks.

  • Sub-step 1: Call IStakingRewards(stakingContract).getReward() to claim reward tokens.
  • Sub-step 2: Use IUniswapV3Router.exactInput() to swap all reward tokens for the vault's want token (e.g., WETH).
  • Sub-step 3: The swapped want tokens are now held within the strategy contract as harvested profit.
solidity
function harvest() external onlyKeepers { IStakingRewards(stakingContract).getReward(); uint256 rewardBalance = IERC20(rewardToken).balanceOf(address(this)); IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router.ExactInputParams({ path: abi.encodePacked(rewardToken, poolFee, WETH), recipient: address(this), deadline: block.timestamp + 300, amountIn: rewardBalance, amountOutMinimum: minAmountOut }); IUniswapV3Router(router).exactInput(params); }

Tip: Strategies often use a performance fee that is deducted from the harvested profit at this stage and sent to a treasury or fee recipient.

3

Calculate and Deduct Fees

The system calculates management and performance fees based on the harvested profit before reinvestment.

Detailed Instructions

Before compounding, the protocol deducts fees to sustain its operations. A performance fee (e.g., 10%) is taken from the newly harvested profit. Some vaults also charge a continuous management fee (e.g., 2% annually), accrued based on the total value locked (TVL) and time elapsed since the last harvest. The fee logic is typically implemented in the vault's Strategy base contract. The fee amounts are converted to the vault's native asset and transferred to predefined addresses, such as a treasury or strategist multisig.

  • Sub-step 1: Calculate performanceFee = harvestedProfit * PERFORMANCE_FEE / MAX_BPS (where MAX_BPS is 10000 for basis points).
  • Sub-step 2: Calculate managementFee = (totalAssets * MANAGEMENT_FEE * blocksSinceLastHarvest) / (BLOCKS_PER_YEAR * MAX_BPS).
  • Sub-step 3: Transfer the total fee amount in want tokens to the fee recipient address.
solidity
uint256 public constant PERFORMANCE_FEE = 1000; // 10% uint256 public constant MANAGEMENT_FEE = 200; // 2% function _chargeFees(uint256 _harvestedAmount) internal { uint256 perfFee = _harvestedAmount * PERFORMANCE_FEE / MAX_BPS; IERC20(want).safeTransfer(treasury, perfFee); uint256 mgmtFee = (totalAssets * MANAGEMENT_FEE * (block.number - lastHarvestBlock)) / (BLOCKS_PER_YEAR * MAX_BPS); IERC20(want).safeTransfer(management, mgmtFee); lastHarvestBlock = block.number; }

Tip: The remaining profit after fees is the net harvest amount that will be compounded back into the strategy.

4

Reinvest Profit and Update Share Price

The net harvested profit is deposited back into the underlying strategy, increasing the vault's total assets and share price.

Detailed Instructions

The final step is compounding, where the net profit is reinvested to generate future yield. The strategy calls its internal _deposit() function, which interacts with the underlying protocol (e.g., supplying liquidity to a Curve pool or lending on Aave). This increases the strategy's position and, consequently, the vault's total assets. The vault's share price (assets per share) is then recalculated and updated. This price increase is how depositors realize their yield without needing to manually claim or reinvest.

  • Sub-step 1: Call the strategy's internal _deposit(uint256 _amount) function with the net harvest amount.
  • Sub-step 2: The function deposits the want tokens into the yield-earning protocol (e.g., ICurvePool(pool).add_liquidity(amounts, min_mint_amount)).
  • Sub-step 3: The vault updates its stored totalAssets and emits a Harvest event with the profit and new share price.
solidity
function _deposit(uint256 _amount) internal override { IERC20(want).safeApprove(curvePool, _amount); uint256[2] memory amounts = [_amount, 0]; ICurvePool(curvePool).add_liquidity(amounts, 0); } function harvest() external onlyKeepers { // ... harvest and fee logic ... uint256 netProfit = harvestedAmount - totalFees; if (netProfit > 0) { _deposit(netProfit); } uint256 newTotalAssets = estimatedTotalAssets(); emit Harvest(msg.sender, netProfit, newTotalAssets); }

Tip: The updated share price is reflected in the vault's pricePerShare() function, allowing users to see their unrealized gains.

Security and Risk Mitigation Design

Core architectural components and strategies that protect vault assets and user funds from exploits, smart contract bugs, and economic attacks.

Time-Locked Admin Functions

Governance delay enforced for critical parameter changes. A multi-sig or DAO proposes updates, which are executable only after a 24-72 hour waiting period.

  • Allows vault users to review strategy changes or fee adjustments
  • Provides a window to exit if a malicious proposal is detected
  • This matters as it prevents instantaneous, potentially harmful admin actions.

Strategy Debt Limits and Caps

Risk isolation through per-strategy and total vault deposit caps. This limits exposure to any single strategy's failure.

  • A lending strategy may be capped at 30% of total vault TVL
  • Prevents a bug in one integration from draining the entire vault
  • This matters by containing losses and preserving capital for other strategies.

Continuous Security Monitoring

Real-time alerting for anomalous contract activity and economic conditions. Uses off-chain bots and on-chain oracle feeds.

  • Monitors for sudden drops in strategy APY or TVL
  • Tracks for large, unexpected withdrawals that may signal an exploit
  • This matters for early incident response and minimizing user fund loss.

Upgradeable Proxy Patterns

Immutable logic separation using proxy contracts with distinct implementation and storage layers.

  • Allows patching discovered vulnerabilities without migrating user funds
  • Implementation upgrades still require time-locked governance approval
  • This matters for maintaining long-term security without sacrificing composability.

Comprehensive Asset Whitelisting

Explicit permission system for which tokens a vault can accept and which protocols its strategies can interact with.

  • Prevents deposit of non-standard or malicious ERC-20 tokens
  • Restricts strategies to audited, reputable DeFi primitives like Aave or Compound
  • This matters by reducing the attack surface to known, vetted components.

Emergency Shutdown Mechanisms

Circuit breaker functions that pause deposits, withdrawals, or strategy operations during a crisis.

  • Can be triggered by governance or a designated guardian address
  • Halts all interactions to prevent further loss during an active exploit
  • This matters as a last-resort containment tool to safeguard remaining capital.
SECTION-TECHNICAL_FAQ

Technical Implementation FAQ

Ready to Start Building?

Let's bring your Web3 vision to life.

From concept to deployment, ChainScore helps you architect, build, and scale secure blockchain solutions.