Loading...
4626 is the standard for yield-bearing vaults. Yearn, Aave v3, Morpho — they all speak it.
Before 4626, every vault had a custom deposit/withdraw API. If you wanted to aggregate yield across Yearn + Aave + Compound, you wrote three integrations. 4626 standardized the interface.
solidity// Deposit underlying, get shares function deposit(uint256 assets, address receiver) external returns (uint256 shares); function mint(uint256 shares, address receiver) external returns (uint256 assets); // Redeem shares, get underlying function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); // Previews (view — what WOULD a deposit/withdraw yield right now) function previewDeposit(uint256 assets) external view returns (uint256 shares); function previewWithdraw(uint256 assets) external view returns (uint256 shares); function previewMint(uint256 shares) external view returns (uint256 assets); function previewRedeem(uint256 shares) external view returns (uint256 assets); // Totals function totalAssets() external view returns (uint256); function convertToShares(uint256 assets) external view returns (uint256); function convertToAssets(uint256 shares) external view returns (uint256);
solidityimport "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract YieldVault is ERC4626 { constructor(IERC20 asset_) ERC20("YieldVault", "yUSDC") ERC4626(asset_) {} // Done. You've got a share-based vault. }
Inheriting ERC4626 gives you all the deposit/withdraw/preview logic. To add yield, you override totalAssets() to account for earnings (from an external protocol, swaps, whatever strategy).
On first deposit to an empty vault, an attacker can: 1. Deposit 1 wei → gets 1 share 2. Transfer 1e18 wei directly to the vault (not via deposit) 3. totalAssets = 1 + 1e18, shares = 1 4. Next depositor who deposits 1e18 gets 0 shares (rounded down)
OZ's 4626 mitigates this with a small "virtual share" offset. Always set decimalsOffset() to 6+ when you inherit.
Write a YieldVault that:
ERC4626IERC20 in the constructor