Розробка протоколу стабільного обміну
Curve Finance утримує $3-5 млрд TVL не тому що перший DEX, а тому що вирішив конкретну математичну проблему: Uniswap V2 з кривою x*y=k дає 1% slippage вже при 0.1% обміну пула. Для активів, які повинні коштувати одинаково (USDC/USDT, stETH/ETH, WBTC/renBTC), це неприйнятно. Stable swap інваріант концентрує ліквідність біля паритету і зменшує slippage в рази.
Якщо ваш проект працює з pegged активами — LST (liquid staking tokens), stablecoins, wrapped assets — вам потрібна саме ця математика.
Математика stable swap: навіщо це не просто "AMM для стейблкоинів"
Інваріант StableSwap Curve
Curve використовує гібридний інваріант, комбінуючи constant sum (x+y=k, нульовий slippage) та constant product (x*y=k, нескінченна ліквідність). Формула для двох активів:
A * n^n * sum(x_i) + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i))
де A — коефіцієнт посилення, D — інваріант, n — кількість активів.
Коефіцієнт посилення — ключовий параметр. При A=0 система поводиться як Uniswap (constant product). При A→∞ — як constant sum. Curve використовує A=100-2000 залежно від пула. Для пар USDC/USDT — високий A (100-200), тому що активи рідко відхиляються від паритету. Для stETH/ETH спочатку — нижче, тому що stETH торгувався з дисконтом і високий A привів би до дисбалансу пула.
Важливо: A можна змінювати. Але зміна A повинна бути поступовою — Curve реалізує ramp_A/stop_ramp_A з мінімальним timelock 7 днів та обмеженням не більше 10x за раз. Різка зміна A при дисбалансованому пулі — по суті зміна цін активів, еквівалентна маніпуляції.
Рішення D через ітерації Ньютона
Рівняння інварианту не має аналітичного рішення для D — використовується метод Ньютона-Рафсона. Типова реалізація сходиться за 4-8 ітерацій при нормальних балансах. Проблема: при сильно дисбалансованому пулі або екстремальних значеннях ітерації можуть не сходитися.
У Solidity це виглядає як цикл з обмеженням на 255 ітерацій та перевіркою |D_new - D_prev| <= 1. Якщо не збіглося — revert. Рідкий випадок, але без нього контракт може зависнути в infinite loop при атаці зі спеціально підібраними параметрами.
Точність вичислень та PRECISION_MUL
Різні stablecoins мають різні decimals: USDC — 6, DAI — 18, USDT — 6. Всередині контракту всі балан нормалізуються до 18 decimals через PRECISION_MUL = [1e12, 1e12, 1] (для пула USDC/USDT/DAI). Забути цю нормалізацію — означає отримати арифметичну уразливість, яка дозволяє витягнути несправедливу частку активів через remove_liquidity_one_coin.
Реальний випадок: саме помилка в нормалізації decimals стала одним з векторів у кількох форках Curve, де атакуючий міг витягнути більше токенів з 18 decimals, ніж вніс токенів з 6 decimals.
Що будуємо: компоненти протоколу
Базовий пул (2-3 активи)
Контракт зберігає балан, реалізує exchange(), add_liquidity(), remove_liquidity(), remove_liquidity_one_coin(). LP-токен — ERC-20 з mintом при додаванні ліквідності. Комісія (зазвичай 0.04%) ділиться між LP та протоколом.
get_dy() (view функція розрахунку выходу) повинна реплікувати логіку exchange() з точністю до 1 wei. Якщо розбіжність існує — арбітражники використовуватимуть це.
Архітектура meta-pool
Curve використовує meta-pools для пар типу newStable/3CRV. Новий стейблкоин пулюється не з трьома активами напряму, а з LP-токеном базового 3pool. Це:
- Зменшує фрагментацію ліквідності
- Дає доступ до всієї ліквідності 3pool через одну торгову пару
Реалізація складніша: exchange_underlying() повинна розпакувати вкладений LP-токен в базові активи. Математика залишається тією ж, але з'являються додаткові виклики до базового пула.
Rate providers для non-pegged активів (Curve V2 / Balancer-style)
stETH/ETH не торгується рівно 1:1 — stETH накопичує yield. Потрібен rate provider: контракт, який повертає поточний обменний курс. Пул використовує rate при розрахунку інварианту, що дозволяє торгувати без slippage навіть коли активи не 1:1.
Критично важлива точка: якщо rate provider маніпулюєм (наприклад, використовує spot price з пула) — вся математика пула стає експлуатуємою. Rate provider повинен використовувати зовнішній trusted oracle або накопиченою rate (як stETH/ETH з контракту Lido), не залежний від поточних угод.
Стек та інструменти
Solidity 0.8.x для основних контрактів. Для високоточної математики використовуємо mulDiv з OpenZeppelin Math — уникаємо переповнення при проміжних обчисленнях типу a * b / c, де a * b може перевищити uint256.
Foundry для тестування: fork-тесты на Ethereum mainnet дозволяють відтворити реальні балан пулів Curve та порівняти вихід нашого контракту з оригіналом. Розбіжність >1 wei на однакових вхідних даних — червоний прапор.
Vyper (як Curve) проти Solidity: оригінальний Curve написаний на Vyper. Ми пишемо Solidity для кращої сумісності toolchain (Foundry, Slither, Hardhat-екосистема). Математика — ідентична при правильній реалізації.
| Компонент | Інструмент | Причина |
|---|---|---|
| Контракти | Solidity 0.8.x | Сумісність toolchain |
| Математика | OpenZeppelin Math.mulDiv | Overflow-safe |
| Тесты | Foundry + fork mainnet | Порівняння з оригіналом Curve |
| Статичний аналіз | Slither + Aderyn | Виявлення арифметичних проблем |
| Fuzzing | Echidna | Інварианти D-константи |
Ризики при форку Curve
Форкнути Curve — спокусливо, код відкритий. Але:
Vyper→Solidity трансляція містить пастки. У Vyper функції @view не можуть змінювати стан на рівні компілятора. У Solidity це контролює тільки модифікатор view, не завжди правильно застосований при трансляції.
Reentrancy в read-only контексті: атака read-only reentrancy на Curve (2023) дозволила маніпулювати ціною LP-токена через виклик під час remove_liquidity. Протоколи, що використовували ціну LP-токена Curve як oracle, були вразливі. Якщо ваш пул планується як oracle для інших протоколів — реалізуйте reentrancy lock на всіх state-changing функціях та окремий view-safe price feed.
Процес розробки
Специфікація (1 тиждень). Визначаємо: кількість активів, потрібні rate providers, архітектура (базовий пул vs meta-pool), модель комісій, параметри governance (хто змінює A та комісії).
Розробка математичного ядра (1-2 тижні). Інваріант, метод Ньютона для D, get_y() для розрахунку виходу. Покриття тестами: порівняння з Python reference implementation Curve.
Контракти пула та LP-токена (1-2 тижні). exchange, add/remove liquidity, admin функції з timelock.
Інтеграційні тесты (1 тиждень). Fork-тесты, fuzzing інваріантів, stress testing екстремальних сценаріїв дисбалансу.
Аудит. Для пулів з реальними середствами — обов'язковий зовнішній аудит. Математика stable swap нетривіальна та містить неочевидні edge cases.
Сроки: 2-3 місяці від специфікації до готовності до аудиту. Залежить від складності архітектури (базовий пул vs meta-pool з rate providers). Вартість — після фіналізації вимог.







