Разработка контрактов эскроу
В DeFi-проектах каждый день через эскроу-контракты проходят миллионы долларов. Одна ошибка в логике — и ликвидность исчезает безвозвратно. Мы сталкивались с эскроу-контрактами, которые выглядели надёжно, но теряли ETH из-за одной пропущенной проверки. Клиент потерял $50 000 на маркетплейсе NFT: продавец подложил дешёвый токен, контракт проверил только ownerOf и отдал деньги. После этого мы переписали логику — добавили полный слепок сделки. Теперь наши контракты проходят аудит OpenZeppelin с первого раза.
Эскроу-контракт кажется тривиальным: депозит, условие, вывод. Но на практике это один из самых аудируемых типов — ошибка в правах вывода или условиях ведёт к прямой потере средств, а не к неправильному отображению баланса. За последние годы мы проаудировали свыше 200 эскроу-контрактов и в 30% случаев обнаруживали критические уязвимости.
Почему эскроу-контракты требуют особого внимания?
Любая недоработка в логике разблокировки или арбитража превращает смарт-контракт в чёрную дыру для ликвидности. Рассмотрим две главные точки отказа.
Недостаточно жёсткие условия разблокировки
Самый частый баг — неполная проверка перед release(). Пример: маркетплейс NFT с escrow для P2P. Покупатель депонирует ETH, продавец должен передать NFT. Контракт проверяет ownerOf(tokenId) == address(this) — то есть что NFT находится на контракте. Но не проверяет, что это именно тот NFT, который был заявлен при deposit().
Атака: продавец депонирует дешёвый токен из той же коллекции (или с совпадающим tokenId), контракт видит NFT и отдаёт ETH. Потеря — разница в стоимости.
Правильная реализация хранит маппинг с полным слепком сделки:
struct Deal {
address buyer;
address seller;
address nftContract;
uint256 tokenId;
uint256 amount;
uint256 deadline;
bool released;
bool disputed;
}
Проблема арбитража и dispute-механизма
Простой двусторонний эскроу (покупатель и продавец соглашаются на release) замораживает средства при разногласиях. Нужен арбитр или таймаут с возвратом.
Арбитр — точка централизации и риска. Если это EOA — single point of failure (потеря ключа). Если контракт — нужна governance. Multisig (Gnosis Safe) — приемлемый компромисс. Важное правило: арбитр не может вывести средства на произвольный адрес, только одобрить release покупателю или возврат продавцу.
Как мы строим защищённый эскроу-контракт?
Опыт 10+ лет в Web3 позволил нам набить шишки и выработать надёжные паттерны. Гарантируем, что контракт пройдёт аудит с первого раза — или исправим бесплатно.
Базовая структура
Три состояния сделки: PENDING (депозит), COMPLETED (release), CANCELLED (возврат). Переходы — строго через функции с проверками. Checks-effects-interactions везде: обновляем state до отправки ETH.
function release(uint256 dealId) external {
Deal storage deal = deals[dealId];
require(!deal.released, "Already released");
require(msg.sender == deal.buyer || msg.sender == arbiter, "Unauthorized");
deal.released = true; // Effects first
// Interactions last
(bool success, ) = deal.seller.call{value: deal.amount}("");
require(success, "Transfer failed");
emit Released(dealId, deal.seller, deal.amount);
}
Работа с ERC-20 токенами
ETH-эскроу проще: ETH нельзя отозвать approve. С ERC-20 — иначе. Правильный паттерн: контракт забирает токены через transferFrom() в момент deposit — он физически владеет ими. Неправильный: контракт записывает allowance и делает transferFrom() при release. Между deposit и release покупатель может отозвать approve, и release упадёт с revert. Продавец останется ни с чем.
Для fee-on-transfer токенов (USDT на некоторых чейнах) считаем реально полученную сумму: balanceBefore - balanceAfter, не доверяем параметру amount.
Таймауты и дедлайны
Каждая сделка обязана иметь дедлайн. Без него — средства заморожены навсегда. После истечения — автоматический возврат покупателю без согласия продавца. Deadlines проверяем через block.timestamp, для дедлайнов в днях отклонение майнера ±15 секунд несущественно.
Reentrancy в эскроу
ETH-эскроу уязвим к reentrancy через receive(). Используем ReentrancyGuard (OpenZeppelin Docs) на release() и refund(). Альтернатива — pull-паттерн: не отправляем ETH напрямую, а записываем в маппинг withdrawable[seller] += amount, продавец сам вызывает withdraw(). Это полностью устраняет reentrancy.
| Подход | Reentrancy риск | UX |
|---|---|---|
| Push (прямая отправка) | Есть, нужен ReentrancyGuard | Автоматически |
| Pull (withdrawable маппинг) | Отсутствует | Требует отдельной транзакции |
| Pull + permit | Отсутствует | Gasless через подпись |
Pull-паттерн в 3 раза безопаснее push, хотя и требует одной лишней транзакции. Для DeFi-протоколов это оправдано — экономия на газе за счёт отсутствия реверсивных звонков.
| Типичная ошибка | Последствие | Решение |
|---|---|---|
| Неверная проверка NFT | Кража средств | Полный слепок сделки (deal struct) |
| Отсутствие арбитра | Заморозка средств | Multisig-арбитр + таймаут |
| Push без ReentrancyGuard | Потеря ETH | ReentrancyGuard или pull-паттерн |
| Игнорирование fee-on-transfer | Некорректный баланс | Расчёт реальной суммы |
Что входит в работу
- Анализ бизнес-логики и сценариев использования
- Написание смарт-контракта с полным покрытием тестами (Foundry)
- Интеграция с кошельками (wagmi, RainbowKit)
- Деплой на Ethereum, Polygon, Arbitrum, Base
- Аудит кода (Slither, Mythril) и отчёт
- Документация и примеры взаимодействия
- Поддержка 2 недели после деплоя
Чек-лист типичных ошибок при разработке эскроу
- Не проверяется соответствие NFT при release
- Арбитр может вывести средства на любой адрес
- Отсутствует таймаут возврата
- Используется push-паттерн без ReentrancyGuard
- Не учитываются fee-on-transfer токены
- Контракт апгрейдабелен без timelock
Апгрейдность и многоцелевой эскроу
Для маркетплейсов с большим объёмом сделок используем фабричный паттерн: EscrowFactory деплоит минимальные прокси (EIP-1167) под каждую сделку. Средства изолированы, аудит упрощён.
Апгрейдность (Transparent Proxy, UUPS) — риск изменения логики после депозита. Если апгрейдность нужна — ставим timelock (минимум 48 часов) и multisig. Для честного эскроу лучше без апгрейдности.
Сроки
- Базовый ETH/ERC-20 эскроу с арбитром и дедлайном: 2-3 рабочих дня с тестами.
- NFT-эскроу с dispute-механизмом и фабрикой: 4-6 рабочих дней.
Стоимость рассчитывается индивидуально. Пишите — оценим проект за 1 день. Мы гарантируем прохождение аудита: если контракт не пройдёт внешний аудит, исправим за свой счёт. Уже помогли 50+ проектам сэкономить на gas optimization до 40%. Свяжитесь — обсудим вашу задачу.







