Разработка yield vault с автокомпаундингом
Yield vault — это контракт, который берёт пользовательские средства, размещает их в протоколе (Aave, Compound, Curve, Convex), собирает накопленные reward-токены и реинвестирует их обратно без участия пользователя. Звучит просто. На практике — несколько нетривиальных проблем: кто платит за compound, как не потерять на проскальзывании при продаже rewards, и как не попасть под reentrancy через токены с хуками.
Harvest и compound: архитектурное решение, которое определяет всё
Кто вызывает harvest и за чей счёт
Автокомпаундинг требует периодического вызова функции harvest() — сбор rewards и реинвестирование. Эту транзакцию кто-то должен оплатить.
Модель 1: Permissionless harvest. Любой может вызвать harvest() и получить bounty (обычно 0.5-1% от collected rewards). Экономика работает, когда rewards достаточно велики, чтобы покрыть gas + bounty. При низком TVL vault-а — harvest случается редко, APY ниже заявленного.
Модель 2: Keeper-based harvest. Chainlink Automation или Gelato Network вызывают harvest() по расписанию или условию (accumulated rewards > threshold). Надёжнее, но требует финансирования keeper-а. Gelato берёт оплату из самих rewards через IAutomate интерфейс — элегантно.
Модель 3: Harvest при каждом deposit/withdraw. Самый простой вариант: _harvest() вызывается автоматически при каждой пользовательской операции. Нет нужды в keeper-е. Проблема: при первом депозите в новый vault вы платите gas за harvest нулевых rewards.
Мы обычно используем гибрид: keeper по расписанию + permissionless harvest с bounty как fallback.
Математика компаундинга и ошибки накопления
APY при компаундинге: (1 + r/n)^n - 1, где r — годовая ставка, n — количество компаундингов в год. При ежедневном компаундинге (n=365) и 20% APR — APY составит 22.13%. При еженедельном (n=52) — 21.94%. Разница небольшая, но маркетинговый APY на frontend должен отражать реальную частоту, иначе пользователи разочарованы.
Ошибка округления: при каждом compound shares * pricePerShare не всегда делится нацело на totalAssets. Накопительная ошибка за тысячи compound-операций может стать значимой. Решение: хранить totalAssets как точное значение (не вычислять каждый раз из балансов), обновлять его атомарно при каждой операции.
Стратегия — ядро vault
Интеграция с Curve + Convex
Классическая стратегия: депозит USDC → Curve 3pool (USDC/USDT/DAI) → получение 3CRV → стейкинг в Convex Finance → получение CRV + CVX rewards → продажа CRV/CVX за USDC → добавление обратно в 3pool.
Кривые взаимодействия в Solidity:
// Deposit в Curve
ICurvePool(POOL_3CRV).add_liquidity([amount, 0, 0], minLpTokens);
// Stake LP в 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);
Риск проскальзывания при продаже rewards: CRV/CVX — не самые ликвидные токены. Если vault накопил 50,000 CRV и продаёт всё за один своп — price impact 2-5%. Решение: разбить продажу на несколько частей (slicedSell) или использовать 1inch для оптимального маршрута через несколько пулов.
Управление комиссиями
Стандартная структура fee для yield vault:
- Performance fee: 10-20% от накопленного yield при каждом harvest. Идёт treasury протокола.
- Management fee: 0.5-2% годовых от TVL. Начисляется непрерывно через увеличение totalAssets в пользу treasury.
- Withdrawal fee: 0-0.1%. Опционально, discourage краткосрочных депозитов.
Реализация management fee без отдельных транзакций: при каждом totalAssets() call вычислять elapsed_seconds * annualFeeRate / SECONDS_IN_YEAR * tvl и вычитать из возвращаемого значения. Treasury shares минтятся при harvest.
Ошибка, которую мы видели: performance fee считалась от gross yield без вычета management fee. Это double-counting: пользователь платит management fee плюс performance fee с суммы, из которой уже вычтена management fee.
Безопасность автокомпаундинга
Reentrancy через ERC-777 и callback-токены
Harvest функция делает несколько внешних вызовов: claim → swap → deposit. ERC-777 токены (CRV не является, но встречаются в экзотических стратегиях) вызывают tokensReceived хук у получателя. Если vault принимает ERC-777, во время claim хук может вызвать deposit или withdraw в vault до завершения harvest.
Паттерн защиты: nonReentrant на harvest, deposit, withdraw. Для vault, который работает с несколькими токенами — nonReentrant через единый _locked флаг на уровне контракта, не функции.
Sandwich-атаки на harvest своп
Публичный harvest() видим в mempool. MEV-боты делают sandwich: покупают CRV до harvest своп-а, продают после, забирая часть реинвестируемого yield.
Защита: deadline на своп (максимум 1-2 блока), жёсткий minAmountOut через TWAP oracle вместо spot price. amountOutMin = twapPrice * amount * (1 - maxSlippage). TWAP за 30 минут — Uniswap V3 observe(). Это делает sandwich непрофитабельным: атакующий не может сдвинуть TWAP за 1 транзакцию.
Альтернатива: Flashbots Private Transactions для harvest — транзакция не попадает в публичный mempool, MEV-боты не видят.
Price manipulation через flash loan
Стратегия, которая читает spot price из AMM для расчёта compound ratio, уязвима: flash loan временно сдвигает цену, harvest считает неверный ratio, часть TVL уходит в неоптимальную сторону.
Решение: никогда не использовать spot AMM price для принятия решений о суммах. Только TWAP или Chainlink для ценовых расчётов. Spot price — только для slippage protection на уровне минимального вывода, не для бизнес-логики.
Стек разработки
Solidity 0.8.x + OpenZeppelin 5.x (ERC-4626, ReentrancyGuard, Pausable, AccessControl). Foundry для тестирования: fork-тесты на Ethereum mainnet с реальными Curve/Convex состояниями. Chainlink Automation для keeper. 1inch Fusion API для оптимальных свапов rewards.
| Риск | Вектор | Защита |
|---|---|---|
| Reentrancy | ERC-777 хуки в claim | nonReentrant на все state-changing функции |
| Sandwich | Публичный harvest | TWAP minAmountOut + Flashbots |
| Price manipulation | Spot AMM цены | Chainlink / TWAP для расчётов |
| Накопительная ошибка fee | Неточный totalAssets | Exact accounting, обновление при каждой операции |
| Слишком редкий compound | Недостаточный TVL | Keeper + permissionless с bounty |
Процесс работы
Аналитика (2-3 дня). Целевые протоколы для стратегии, структура fee, частота compound, требования к keeper-у.
Проектирование (3-5 дней). ERC-4626 vault архитектура, стратегия контракт (отдельный от vault для апгрейдаемости стратегии без миграции пользователей), fee accounting.
Разработка (2-4 недели). Vault → стратегия → keeper интеграция → frontend (wagmi + viem).
Тестирование. Foundry fork-тесты: полный цикл deposit → earn → harvest → withdraw на mainnet состоянии. Fuzz на суммы, timing, последовательность операций.
Аудит. Yield vault — высокоприоритетная цель для аудита. Внешний аудит обязателен при TVL > $500k.
Ориентиры по срокам
Простой vault на один протокол с Chainlink Automation — 1-2 недели. Мультистратегийный vault с оптимальным роутингом rewards и sandwich-защитой — 4-8 недель.
Стоимость рассчитывается после определения стратегии и требований к безопасности.







