Разработка стратегических vault (strategy vaults)
Yearn Finance в 2021 году потерял $11 миллионов в атаке на vault стратегию DAI. Атакующий использовал flash loan для манипуляции ценой Curve pool, vault выполнил harvest в неправильный момент и зафиксировал убыток вместо прибыли. Это не теоретическая уязвимость — это конкретный production incident, который изменил то, как строятся strategy vaults.
Strategy vault — это контракт-агрегатор, который принимает депозиты пользователей (ERC-4626 стандарт), размещает капитал в одну или несколько DeFi-стратегий, автоматически реинвестирует доходность и позволяет переключать стратегии без участия пользователей. Сложность — в корректном управлении lifecycle стратегий и защите harvest от манипуляций.
Архитектура strategy vault
ERC-4626 как базовый стандарт
ERC-4626 (Tokenized Vault Standard) стандартизирует интерфейс: deposit, withdraw, mint, redeem, previewDeposit, previewWithdraw, totalAssets. Это позволяет агрегаторам (Yearn, Beefy, автоматическим rebalancers) работать с vault без кастомной интеграции.
Критичная деталь ERC-4626: share price manipulation при первом депозите. Если vault пустой, атакующий делает tiny deposit (1 wei), затем donation атакой перечисляет большую сумму напрямую в vault (минуя deposit). Share price резко вырастает, следующий депозитор получает 0 share из-за округления. Защита через virtual shares (OpenZeppelin ERC-4626 добавляет _decimalsOffset()):
function _convertToShares(uint256 assets, Math.Rounding rounding)
internal view virtual override returns (uint256)
{
return assets.mulDiv(
totalSupply() + 10 ** _decimalsOffset(),
totalAssets() + 1,
rounding
);
}
10x decimals offset делает donation attack экономически нецелесообразной.
Pluggable strategy pattern
Ядро архитектуры — разделение vault (хранит капитал, управляет share token) и strategy (размещает капитал, генерирует доходность). Vault держит список одобренных стратегий с allocation weights.
interface IStrategy {
function asset() external view returns (address);
function vault() external view returns (address);
function totalAssets() external view returns (uint256);
function harvest() external returns (uint256 profit, uint256 loss);
function withdraw(uint256 amount) external returns (uint256 withdrawn);
function emergencyExit() external;
}
Каждая стратегия — отдельный контракт. Добавление новой стратегии не требует апгрейда vault. Это критично для расширяемости и снижает риск аудита новых стратегий (меньший scope).
Управление allocation и автоматическое переключение
Vault хранит debtRatio для каждой стратегии — процент от total assets, который стратегия должна держать. При harvest контроллер проверяет:
- Если стратегия держит меньше target — vault даёт больше капитала
- Если стратегия держит больше target — vault изымает излишек
- Если стратегия в убытке — vault уменьшает debtRatio, увеличивает для более прибыльных
Автоматическое переключение строится на двух механиках:
- APY comparison: The Graph subgraph или Chainlink data feeds для APY разных протоколов
- Risk-adjusted scoring: APY / (volatility × risk_factor) — не всегда лучшая стратегия та, что даёт максимум
Защита harvest от манипуляций
Это самое технически сложное место. Yearn-инцидент показал: если harvest выполняется в момент аномальных цен в пуле — vault фиксирует потери.
Решения:
TWAP проверка при harvest: Перед фиксацией прибыли vault сравнивает текущую цену актива с TWAP. Если отклонение >X% — harvest откладывается.
Harvest as privileged operation: harvest() вызывает не кто угодно, а keeper (EOA или Gelato/Keep3r автоматизация) — с дополнительными проверками на аномалии.
Slippage control при swap внутри стратегии: Стратегия, которая продаёт reward токены за base asset (compound стратегия), должна проверять minAmountOut через Uniswap v3 quoter.
function _sellRewards(uint256 rewardAmount) internal returns (uint256 baseReceived) {
uint256 expectedOut = quoter.quoteExactInputSingle(
REWARD_TOKEN, BASE_ASSET, POOL_FEE, rewardAmount, 0
);
// Не продаём, если цена аномально плохая (>2% от quote)
uint256 minOut = expectedOut * 9800 / 10000;
baseReceived = router.exactInputSingle(
ISwapRouter.ExactInputSingleParams({
tokenIn: REWARD_TOKEN,
tokenOut: BASE_ASSET,
fee: POOL_FEE,
recipient: address(this),
amountIn: rewardAmount,
amountOutMinimum: minOut,
sqrtPriceLimitX96: 0
})
);
}
Контроль рисков
Emergency exit — функция, доступная guardian multisig, которая изымает весь капитал из активных стратегий и возвращает в vault idle. Используется при обнаружении уязвимости или рыночной аномалии. Стратегия при этом переходит в emergency mode и блокирует новые вложения.
Debt limit per strategy: Vault никогда не размещает >X% капитала в одну стратегию, даже если debtRatio выставлен выше. Hard cap на уровне контракта.
Withdrawal queue: При крупном выводе vault сначала забирает из idle баланса, потом из наименее прибыльных стратегий, потом из основных. Это минимизирует disruption для остальных депозиторов.
Стек разработки
- Solidity 0.8.x + OpenZeppelin 5.x — core vault и ERC-4626
- Foundry — тесты, fork-тесты на mainnet (Ethereum, Arbitrum, Polygon)
- Echidna — invariant testing: total assets ≥ total debt, share price монотонно растёт
- The Graph — индексация APY, deposit/withdraw событий для фронтенда
- Gelato Network / Keep3r — автоматизированные keeper вызовы для harvest
- Tenderly — мониторинг и алерты на аномалии
Процесс работы
Аналитика (3-5 дней). Определяем целевые протоколы для стратегий (Aave, Compound, Curve, Balancer), базовый актив vault, допустимые уровни риска, fee structure (management fee, performance fee).
Архитектура (3-5 дней). Storage layout vault + strategy интерфейсы + governance механизм для добавления стратегий.
Разработка (4-8 недель). Vault core + 2-3 начальных стратегии + keeper automation. Приоритет — сначала полный цикл одной простой стратегии (Aave deposit), потом усложнение.
Тестирование (1-2 недели). Fork-тесты, invariant testing, симуляция атак (donation attack, harvest manipulation, flash loan).
Аудит + деплой. Для vault с реальным TVL — внешний аудит обязателен.
Ориентиры по срокам
Базовый vault с одной стратегией и ERC-4626 — 2-3 недели. Полноценная мульти-стратегийная система с автоматическим переключением и keeper — 2-3 месяца. Стоимость рассчитывается индивидуально.







