Розробка системи multi-hop свапів
Протокол агрегує ліквідність з трьох DEX, але маршрут USDC→WBTC йде через єдиний пул з глибиною $200k. Результат — проскальз 1.8% на угоді в $50k. Користувач торгує в мінус відносно ринкової ціни, а алгоритм про це мовчить. Multi-hop вирішує це через розділення маршруту: USDC→WETH через Uniswap v3, WETH→WBTC через Curve tricrypto. Сумарне проскальзування — 0.3%. Різниця суттєва, але реалізувати правильно — не тривіально.
Де ломається наївна реалізація multi-hop
Path encoding та stack overflow у Solidity
Uniswap v3 кодує маршрут як bytes path — послідовність address fee address fee address. При трьох хопах це 20+3+20+3+20 = 66 байт. Здається просто. Проблема починається, коли розробник намагається будувати path динамічно у Solidity — abi.encodePacked в циклі з uint24[] fees та address[] tokens. Якщо вхід не валідується, можна зібрати шлях з невідповідністю довжин: 4 токена, 2 fee. Контракт скомпілюється. На swap піде в revert на рівні декодування у UniswapV3Pool, без ясного error message.
Другий вектор — callback manipulation. У uniswapV3SwapCallback контракт повинен перевірити, що викликаючий — легітимний пул, обчислений через PoolAddress.computeAddress. Якщо цієї перевірки нема, будь-хто може викликати callback напрямки, передаючи довільний amount0Delta/amount1Delta, та витягти токени з контракту. Саме так була дренована один з форків агрегатора у 2023 році — відсутність caller validation у callback.
Розрахунок price impact через кілька пулів
Рахувати price impact по multi-hop маршруту складніше, ніж по одному пулу. Наївний підхід — викликати quoteExactInput у Quoter, отримати amountOut, порівняти з spot price. Працює. Але Quoter v2 вимагає симуляцію через eth_call, а при частих запитах це створює навантаження на RPC. Більш правильний шлях — off-chain розрахунок через математику CPMM та CLMM: для кожного пулу рахуємо sqrtPriceX96 після свапу, потім агрегуємо. Це дозволяє рахувати impact без on-chain запитів.
Ще тонкість: при hop через Curve стабільний пул (3pool, Frax) математика інша — StableSwap invariant замість x*y=k. Змішувати розрахунки не можна, інакше оцінка вийде невірною.
MEV та sandwich-атаки на multi-hop маршрути
Довгий маршрут з кількома пулами — лакомий кусок для MEV-ботів. Кожен пул — окрема точка атаки. Класичний sandwich: бот фронтранить перший хоп, підвищує ціну, потім бекранить після виконання транзакції. Захист — жорсткий amountOutMinimum на весь маршрут (не на кожен хоп окремо) та використання приватного mempool: Flashbots Protect, MEV Blocker, або пряма відправка через eth_sendPrivateTransaction.
Як ми будуємо multi-hop систему
Архітектура: off-chain routing + on-chain execution
Розділення обов'язків принципово. Off-chain router рахує оптимальний маршрут — це Python/TypeScript сервіс, який будує граф з пулів Uniswap v2/v3, Curve, Balancer, та запускає Dijkstra або Bellman-Ford для пошуку шляху з мінімальним impact. On-chain контракт тільки виконує: приймає закодований шлях, валідує його, виконує свапи через ISwapRouter/ICurvePool, повертає amountOut.
| Компонент | Інструменти | Завдання |
|---|---|---|
| Graph builder | viem, The Graph, subgraph | Актуальний снімок пулів |
| Path optimizer | TypeScript, custom Dijkstra | Пошук маршруту з min slippage |
| Quote engine | UniswapV3 Quoter v2, Curve calc | Точна оцінка amountOut |
| Executor contract | Solidity 0.8.x, Foundry | On-chain виконання |
| Slippage guard | amountOutMinimum + deadline |
Захист від MEV |
Реалізація executor-контракту
Контракт реалізує IUniversalRouter-подібний інтерфейс. Ключова функція — executeMultiHop(bytes calldata path, uint256 amountIn, uint256 amountOutMin, address recipient). Усередині: декодуємо path, визначаємо тип першого пулу (Uniswap v3 за наявністю fee uint24, або Curve за адресою registry), маршрутизуємо у відповідний адаптер.
Кожен адаптер — окремий контракт, зареєстрований у IAdapterRegistry. Це дозволяє додавати підтримку нових DEX без переписування executor. Паттерн — strategy через інтерфейс ISwapAdapter з методом swap(address tokenIn, address tokenOut, uint256 amountIn, bytes calldata data) returns (uint256 amountOut).
Кеш адрес пулів у mapping(bytes32 => address) — ключ це keccak256(abi.encodePacked(token0, token1, fee)). Дозволяє не звертатися до factory на кожен хоп.
Тестування на fork mainnet
Multi-hop не можна протестувати без реального стану пулів. Використовуємо Foundry fork-тести:
vm.createSelectFork(vm.envString("ETH_RPC_URL"), blockNumber);
Фіксуємо конкретний блок — відтворюваність тестів. Прогоняємо сценарії: USDC→WETH→WBTC через Uniswap v3, DAI→USDC→ETH→stETH через мікс Curve+Uniswap. Перевіряємо, що amountOut збігається з передбаченням Quoter з допуском ±0.01%.
Fuzzing на розмір входових сум — amountIn від 1 до 10^9 одиниць токена. Шукаємо edge cases де path розрахунок дає amountOut = 0 через integer overflow/underflow при проміжних обчисленнях.
Процес роботи
Аналітика (2-3 дні). Інвентаризація пулів: які DEX, які чейни, потрібна ли кросс-чейн підтримка. Визначаємо, потрібен ли власний subgraph або достатньо публічних endpoint.
Проектування (3-5 днів). Схема граф-роутера, інтерфейси адаптерів, storage layout executor-контракту. На цьому етапі вирішуємо питання апгрейдаємості: якщо додавання нових DEX планується, адаптер-реєстр повинен підтримувати registerAdapter з access control.
Розробка (1-2 тижні). Off-chain router + on-chain executor + набір адаптерів під конкретні DEX. Fork-тести на Ethereum та цільових L2 (Arbitrum, Optimism, Base).
Інтеграція. wagmi/viem хуки для frontend: useMultiHopQuote, useMultiHopSwap. WebSocket підписка на оновлення цін через The Graph.
Аудит та деплой. Slither + ручний review callback-функцій. Деплой через Foundry script з Gnosis Safe мультисиг на функції owner.
Орієнтири за строками
MVP з підтримкою Uniswap v2/v3 та одним чейном — 1-2 тижні. Повноцінний агрегатор з Curve, Balancer, кастомним subgraph та підтримкою 3-4 чейнів — 6-8 тижнів. Строки залежать від кількості підтримуваних DEX та вимог до точності quote-рухавика.
Вартість рассчитується після аналізу технічного завдання.







