Auto-Compounding Yield Vault Development
Yield vault — a contract taking user funds, deploying them in a protocol (Aave, Compound, Curve, Convex), collecting accumulated reward tokens and reinvesting without user participation. Sounds simple. In practice — several non-trivial problems: who pays for compound, how to not lose on slippage selling rewards, and how to avoid reentrancy through tokens with hooks.
Harvest and compound: architectural decision that determines everything
Who calls harvest and at whose cost
Auto-compounding requires periodic harvest() function — collecting rewards and reinvesting. Someone must pay for this transaction.
Model 1: Permissionless harvest. Anyone can call harvest() and get bounty (usually 0.5-1% of collected rewards). Economics work when rewards are large enough to cover gas + bounty. At low TVL — harvest happens rarely, APY lower than stated.
Model 2: Keeper-based harvest. Chainlink Automation or Gelato Network call harvest() on schedule or condition (accumulated rewards > threshold). More reliable, but requires keeper funding. Gelato takes payment from rewards themselves via IAutomate interface — elegant.
Model 3: Harvest on each deposit/withdraw. Simplest: _harvest() auto-called on every user operation. No keeper needed. Problem: on first deposit to new vault you pay gas for harvesting zero rewards.
We usually use hybrid: keeper on schedule + permissionless harvest with bounty as fallback.
Compounding math and accumulation errors
APY with compounding: (1 + r/n)^n - 1, where r is annual rate, n is compounds per year. At daily compounding (n=365) and 20% APR — APY is 22.13%. At weekly (n=52) — 21.94%. Small difference, but frontend APY must reflect actual frequency, otherwise users disappointed.
Rounding error: on each compound shares * pricePerShare doesn't always divide evenly into totalAssets. Cumulative error over thousands of compounds can become significant. Solution: store totalAssets as exact value (not compute from balances each time), update atomically on each operation.
Strategy — vault core
Curve + Convex integration
Classic strategy: deposit USDC → Curve 3pool (USDC/USDT/DAI) → get 3CRV → stake in Convex Finance → get CRV + CVX rewards → sell CRV/CVX for USDC → add back to 3pool.
Interaction curves in Solidity:
// Deposit to Curve
ICurvePool(POOL_3CRV).add_liquidity([amount, 0, 0], minLpTokens);
// Stake LP in Convex
IConvexBooster(BOOSTER).deposit(pid, lpAmount, true);
// Claim rewards
IConvexRewardPool(REWARD_POOL).getReward(address(this), true);
// Sell CRV via Uniswap V3
ISwapRouter(UNISWAP_ROUTER).exactInputSingle(params);
Slippage risk selling rewards: CRV/CVX not most liquid tokens. If vault accumulated 50,000 CRV and sells all at once — 2-5% price impact. Solution: split sell into pieces (slicedSell) or use 1inch for optimal route across pools.
Fee management
Standard fee structure for yield vault:
- Performance fee: 10-20% of accumulated yield on each harvest. Goes to protocol treasury.
- Management fee: 0.5-2% annually on TVL. Accrued continuously via increasing totalAssets to treasury.
- Withdrawal fee: 0-0.1%. Optional, discourages short-term deposits.
Management fee implementation without separate transactions: on each totalAssets() call compute elapsed_seconds * annualFeeRate / SECONDS_IN_YEAR * tvl and subtract from returned value. Treasury shares minted on harvest.
Error we saw: performance fee calculated from gross yield without subtracting management fee. This double-counts: user pays management fee plus performance fee on amount already reduced by management fee.
Automation security
Reentrancy via ERC-777 and callback tokens
Harvest does several external calls: claim → swap → deposit. ERC-777 tokens (CRV isn't but occur in exotic strategies) call tokensReceived hook on recipient. If vault accepts ERC-777, during claim hook can call deposit or withdraw in vault before harvest completes.
Protection pattern: nonReentrant on harvest, deposit, withdraw. For vault working with multiple tokens — nonReentrant via single _locked flag at contract level, not per-function.
Sandwich attacks on harvest swap
Public harvest() visible in mempool. MEV bots sandwich: buy CRV before harvest swap, sell after, taking part of reinvested yield.
Protection: deadline on swap (maximum 1-2 blocks), strict minAmountOut via TWAP oracle instead of spot. amountOutMin = twapPrice * amount * (1 - maxSlippage). 30-minute TWAP — Uniswap V3 observe(). Makes sandwich unprofitable: attacker can't shift TWAP in one transaction.
Alternative: Flashbots Private Transactions for harvest — transaction doesn't land in public mempool, MEV bots don't see.
Price manipulation via flash loan
Strategy reading spot price from AMM for compound ratio is vulnerable: flash loan temporarily shifts price, harvest calculates wrong ratio, part of TVL goes suboptimal.
Solution: never use spot AMM price for decision making. Only TWAP or Chainlink for calculations. Spot price only for slippage protection level on minimum output, not business logic.
Development stack
Solidity 0.8.x + OpenZeppelin 5.x (ERC-4626, ReentrancyGuard, Pausable, AccessControl). Foundry for testing: fork tests on Ethereum mainnet with real Curve/Convex states. Chainlink Automation for keeper. 1inch Fusion API for optimal reward swaps.
| Risk | Vector | Protection |
|---|---|---|
| Reentrancy | ERC-777 hooks in claim | nonReentrant on all state-changing functions |
| Sandwich | Public harvest | TWAP minAmountOut + Flashbots |
| Price manipulation | Spot AMM prices | Chainlink / TWAP for calculations |
| Accumulation error | Imprecise totalAssets | Exact accounting, update on every operation |
| Rare compound | Low TVL | Keeper + permissionless with bounty |
Working process
Analytics (2-3 days). Target protocols for strategy, fee structure, compound frequency, keeper requirements.
Design (3-5 days). ERC-4626 vault architecture, strategy contract (separate from vault for strategy upgrade without migration), fee accounting.
Development (2-4 weeks). Vault → strategy → keeper integration → frontend (wagmi + viem).
Testing. Foundry fork tests: full cycle deposit → earn → harvest → withdraw on mainnet state. Fuzz on amounts, timing, operation sequence.
Audit. Yield vault high priority for audit. External audit mandatory at TVL > $500k.
Timeline estimates
Simple vault on one protocol with Chainlink Automation — 1-2 weeks. Multi-strategy vault with optimal reward routing and sandwich protection — 4-8 weeks.
Cost calculated after defining strategy and security requirements.







