March 12, 2025 · Read time: 8 min
How to Build a Yield Aggregator with ERC-4626: Architecture Deep Dive
ERC-4626 standardized the vault interface. Building a production-grade yield aggregator on top of it is still non-trivial. Here's the full architecture.
Introduction
ERC-4626, the Tokenized Vault Standard, was introduced to solve a fragmentation problem: every DeFi protocol had its own vault interface, making integrations painful. The standard defines a common interface for yield-bearing vaults — deposit, withdraw, mint, redeem — with accounting done in shares.
But implementing a multi-strategy yield aggregator on top of ERC-4626 involves decisions the standard doesn't make for you: strategy management, rebalancing logic, fee mechanics, emergency controls, and security invariants. The interface is clean; the production system around it is where most bugs live.
A serious aggregator is not just a vault that deposits into another vault. It is a capital allocation engine. It must know where funds are, why they are there, when they should move, and how to unwind positions without harming depositors.
Why ERC-4626 Matters
Before ERC-4626, integrating a yield vault meant reading bespoke documentation for each protocol. Aave's aTokens, Compound's cTokens, Yearn's yVaults — all different. ERC-4626 creates a universal interface. Your aggregator can treat every underlying strategy as the same type of module, as long as each exposes the standard interface.
This is what makes the Strategy Pattern work cleanly. The core vault is responsible for accounting and policy. Strategies are responsible for interacting with external protocols. That split reduces blast radius and makes each integration easier to test in isolation.
Vault Architecture
The core vault contract holds two responsibilities: accounting, meaning share and asset conversion, and strategy allocation, meaning which strategies hold how much capital. Keep those responsibilities explicit in the codebase. Do not let strategy-specific logic leak back into the vault.
The key accounting invariant is simple to say and hard to preserve: totalAssets() must always equal the sum of assets held by all active strategies plus the buffer balance. Any deviation indicates an accounting bug, an integration mismatch, or an exploit in progress.
// Simplified vault deposit flow
function deposit(uint256 assets, address receiver)
external returns (uint256 shares) {
shares = previewDeposit(assets);
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
_allocateToStrategies(assets);
emit Deposit(msg.sender, receiver, assets, shares);
}Strategy Pattern
Each strategy is a separate contract implementing a simple interface: deposit(uint256), withdraw(uint256), totalAssets() returns uint256. The vault holds a mapping of strategy addresses to allocation percentages. When capital enters the vault, it is split across strategies per their allocations.
This pattern has a critical benefit: strategies can be added or removed without touching the core vault. A governance vote changes the allocation mapping, and the next rebalance redistributes capital accordingly. In practice, this is what lets a protocol adapt to changing yields without redeploying the accounting core.
The cost is operational complexity. Strategy adapters need versioning, pause controls, withdrawal limits, and a clear deprecation path. If a strategy loses liquidity, the vault must degrade gracefully instead of blocking every withdrawal.
Rebalancing Logic
Rebalancing is the highest-risk operation in a yield aggregator. It involves moving capital between strategies, which means multiple external calls in a single transaction — classic reentrancy territory.
Our implementation uses a two-phase pattern. Phase 1 withdraws target amounts from over-allocated strategies. Phase 2 deposits capital into under-allocated strategies. Between phases, all strategy balances are validated against expected amounts. If any validation fails, the entire transaction reverts.
The rebalancer is a Gelato keeper task triggered when any strategy drifts more than 5% from its target allocation. That threshold matters. Rebalance too often and gas eats APY. Rebalance too rarely and the vault underperforms its advertised policy.
Security Considerations
Three invariants must hold at all times: totalShares multiplied by pricePerShare must not exceed totalAssets, no strategy can be called during a deposit or withdrawal, and pricePerShare must be monotonically non-decreasing unless governance has explicitly accepted a loss event.
We enforce these with a custom ReentrancyGuard that tracks cross-contract call depth and a share price oracle that reverts if the price ever decreases by more than the loss threshold. The default threshold is 0.1%, but it should be tunable by governance because different strategies have different dust behavior.
Admin controls deserve the same paranoia as user flows. A compromised allocator should not be able to route 100% of funds into an unvetted strategy. Timelocks, caps, emergency pauses and monitoring are part of the product, not optional extras.
Testing Approach
Fork testing is non-negotiable for yield aggregators. Unit tests can verify logic, but only fork tests reveal how your contracts behave with real Aave, Compound, Curve or GMX state.
Our test suite for NexVault included 340 unit tests, 12 fork tests on an Arbitrum mainnet fork, 3 invariant tests using Foundry fuzzing, and 2 full scenario tests covering deposit, rebalance and withdrawal cycles.
The fork tests caught two integration bugs that unit tests completely missed. Both were related to how Compound v3 handles dust amounts during withdrawals. That is the kind of issue you only see against real protocol state.
The conclusion is not subtle: ERC-4626 makes the interface clean. The hard part is everything else: rebalancing safely, handling edge cases in integrated protocols, and maintaining share price integrity under all conditions. Build with paranoia. Test with real state.