skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
72 lines (57 loc) • 2.88 kB
Markdown
- 8. Yield Strategy Patterns
- Simple Vault (ERC-4626)
- Strategy Pattern
## 8. Yield Strategy Patterns
### Simple Vault (ERC-4626)
OpenZeppelin **Contracts 5.x** has no `_afterDeposit`/`_beforeWithdraw` hooks — those are
Solmate. The current extension points are the internal **`_deposit`** and **`_withdraw`**
(call `super` first, then move funds to/from the strategy). Two correctness musts: (1) keep
`totalAssets()` consistent with where the assets actually are (idle + deployed + accrued), and
(2) defend against the **inflation/donation attack** — override `_decimalsOffset()` to return a
virtual-shares offset (e.g. `6`), which makes the first-depositor share-price manipulation
economically infeasible. Also account for withdrawal liquidity: if assets are deployed in a
strategy, `_withdraw` must pull enough back, and `maxWithdraw` should reflect available
liquidity.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract YieldVault is ERC4626 {
constructor(IERC20 asset_) ERC4626(asset_) ERC20("Yield Vault", "yVault") {}
// Virtual shares/assets offset -> mitigates the first-deposit inflation attack.
function _decimalsOffset() internal pure override returns (uint8) {
return 6;
}
function totalAssets() public view override returns (uint256) {
// Idle balance + assets currently deployed in the strategy (incl. accrued yield).
return IERC20(asset()).balanceOf(address(this)) + _deployedAssets();
}
// OZ 5.x: hook into _deposit/_withdraw, not _afterDeposit/_beforeWithdraw.
function _deposit(address caller, address receiver, uint256 assets, uint256 shares)
internal override
{
super._deposit(caller, receiver, assets, shares); // pulls assets, mints shares
_deployToStrategy(assets); // e.g. supply to Aave/Compound
}
function _withdraw(
address caller, address receiver, address owner, uint256 assets, uint256 shares
) internal override {
_redeemFromStrategy(assets); // ensure liquidity before super burns/transfers
super._withdraw(caller, receiver, owner, assets, shares);
}
// --- strategy stubs: implement against your yield source ---
function _deployedAssets() internal view virtual returns (uint256) {}
function _deployToStrategy(uint256 assets) internal virtual {}
function _redeemFromStrategy(uint256 assets) internal virtual {}
}
```
```
User deposits → Vault → Strategy A (60% Aave)
→ Strategy B (40% Curve)
Harvest → Compound rewards → Rebalance
```
---