• Duel открыт в Robinhood Chain
  • Запустите дуэль два токена, три пула, одна транзакция
  • Раунд закроется через --:--:--
  • Раунды по четыре часа выкуп победителя ограничен по цене
  • Ликвидность заблокирована навсегда, токенов команды нет

Безопасность

Что имеет ценность

  • ETH и токены пулов — в позициях, которые никто не может вывести. ETH стороны покидает её пул, только когда эту сторону в него продают: трейдер или выравнивание hook после сделки, разведшей три цены.
  • Банки, комиссии создателей и комиссии протокола — их хранит hook в виде прав требования на ETH в PoolManager Uniswap V4, с отдельным учётом для каждой дуэли.

Что это защищает

  • Заблокированная ликвидность. Только фабрика может открывать зарегистрированные пулы или добавлять в них ликвидность при запуске; вывод и пожертвование откатываются.
  • Ограниченные получатели. Банк покупает только победителя своей дуэли; комиссии создателя идут только создателю дуэли: кошельку, который её запустил, или адресу, которому он передал роль и который её принял. Комиссии протокола идут в текущее казначейство, которое владелец фабрики может сменить через 48 часов, в том числе для уже накопленных комиссий протокола.
  • Ограниченные полномочия фабрики. У токенов нет владельца и функции выпуска, а hook нельзя ни обновить, ни приостановить. Владелец фабрики может приостановить новые запуски, предложить ограниченные условия для будущих дуэлей или сменить казначейство после задержки. Он также может сразу снизить долю протокола в комиссии за своп всех дуэлей, вплоть до нуля, или вернуть её обратно, но никогда не выше условий дуэли: то, что протокол не берёт, идёт в банки. Владелец может навсегда отказаться от роли (renounceOwnership): тогда условия, казначейство, ставка протокола и приостановка остаются как есть, кроме уже предложенного изменения, которое любой может применить после задержки.
  • Расчёт перед свопом. Первый своп после закрытия рассчитывает завершённое среднее до начала торговли. Потолок выкупа находится примерно на 10 % выше ориентира, хранимого с точностью до долей тика: это 30-минутное экспоненциальное среднее тиков пула ETH, ограниченное снизу текущим тиком, чтобы оно сразу следовало за ценой вниз.
  • Раунды только в ETH. Соотношение и среднее раунда используют два пула ETH. Переход A/B, который не сдвигает ни одной цены в ETH, не меняет ни сигнал, ни активность раунда, который иначе остался бы тихим.
  • Комиссии только при полном исполнении. Свопы с предварительным списанием комиссии откатываются, если не могут полностью исполнить запрошенную сумму. Сетевой газ — отдельно.
  • Изолированное выравнивание. Сохраняются только прибыльные циклы, ограниченные ETH продающего пула. Неудачная попытка откатывается и пропускается; при нехватке газа откатывается своп (ArbitrageStarved), чтобы вызывающий не мог пропустить выравнивание, придержав газ. Это не гарантирует совпадения цен и не защищает от всех торговых потерь.
  • Одинаковые правила на любом маршруте. Hook взимает комиссии, налоги и сжигания, будь то роутер Duel или любой другой роутер V4.

Фабрика начинает работу с приостановленными запусками. Пока paused() равно true, запустить дуэль может только owner(); вызовы остальных откатываются с CreationPaused. Владелец открывает запуски через setPaused(false). Существующие дуэли продолжают торговаться.

Что показывает сайт

  • Изображения только с сайта. Страницы показывают изображения дуэли только с собственного адреса Duel и только обработанные сайтом: JPEG, PNG или WebP, сначала прочитанный по заголовку (не более 1536 × 1536 пикселей и 5 МБ), декодированный в WebAssembly, вписанный в 1024 пикселя и заново закодированный в WebP без метаданных. Политика безопасности страниц не позволяет им загружать изображения откуда-либо ещё.
  • Изолированные кодеки. Изображения декодируются в отдельном Worker, к которому обращается только сайт и который ничего не хранит: ни ключей, ни хранилища. Уязвимость в кодеке затронула бы только изображение, которое он читает. Сайт проверяет ответ этого Worker (статичный WebP ожидаемого размера) перед сохранением, а декодеры на C запускаются заново для каждого изображения.
  • Дуэли, запущенные напрямую через контракт. В их данных может быть указан любой адрес. Сайт не показывает изображения по https; изображение из IPFS он загружает один раз через публичный шлюз (не более 5 МБ и 10 секунд), обрабатывает его как загрузку и показывает свою копию или инициалы стороны.
  • Ограниченные загрузки. Сайт принимает изображения только со своих страниц: десять в минуту с одного адреса и ограниченное число в сутки. Изображение, которое не указано ни в одной дуэли, удаляется через сутки.

Как это проверялось

  • Автоматические тесты покрывают поведение контрактов, веб-приложение и бота оповещений. Покрытие — это метрика тестов, а не доказательство безопасности любого возможного поведения.
  • Форк-тесты проверяют интеграцию с развёрнутыми в Robinhood Chain контрактами Uniswap V4, включая Universal Router, Permit2 и V4 Quoter.
  • Регрессионные тесты сохраняют случаи, найденные при внутренней проверке, включая учёт комиссий, граничные случаи запуска, расчёт и опустошённые пулы.
  • Инвариантные тесты проверяют учёт на случайных последовательностях сделок, расчётов и выплат: права требования на ETH должны совпадать с причитающимися суммами, а промежуточные токены не должны оставаться в hook, фабрике или роутере. Прохождение заданного числа прогонов не доказывает безопасность для любой возможной последовательности.

Внутренняя проверка нашла и исправила маршрут с точным выходом в обход антиснайп-налога, соотношение, которое не следовало за стороной, купленной в одиночку, опустошённые пулы, которые неверно читала ориентирная цена выкупа, и небезопасные ссылки в метаданных дуэли.

Как сообщить об уязвимости

Если вы считаете, что нашли уязвимость, спасибо, что нашли время сообщить о ней. Напишите нам в личные сообщения в Telegram, @chatwithdivine, что вы нашли и, если можете, как это воспроизвести, и дайте нам время разобраться, прежде чем что-либо публиковать. Мы быстро ответим и будем держать вас в курсе.

Мы никогда не пишем первыми: доверяйте только @chatwithdivine.