За контекст вы платите аудитору. Вот что можно отдать заранее
Аудитор берёт деньги за время, и первые дни уходят на восстановление контекста. Четыре вещи, которые мы сделали для своего сканера чужих контрактов.
Обновлено:
Оговорка вперёд: своих контрактов, прошедших аудит, у нас нет. У нас есть инструмент, который читает 166 чужих контрактов — реестр остановившейся сети DFK Chain и сканер, который по любому адресу показывает, что на нём осталось. Это другая сторона того же стола: мы месяц занимались ровно тем, за что аудитор берёт деньги в первые дни, — восстанавливали контекст по чужому коду, у которого его не было.
Отсюда список того, что стоит отдать аудитору сразу. Каждый пункт мы делали себе, и у каждого есть цена, которую мы заплатили, когда его не было.
1. Происхождение, а не список адресов
Реестр у нас разложен на две части, и это разделение оказалось главным.
Первая — происхождение: файл на 196 килобайт, где у каждого адреса стоит либо ссылка на документацию и отметка о подтверждении чтением из сети, либо явное «выяснить не удалось». Вторая — слим-проекция на 166 контрактов, которую читает код; она генерируется из первой и руками не правится.
Правило, которое это держит, сформулировано ещё в первой фазе разбора: каждое утверждение заканчивается либо ссылкой на источник, либо записанным «выяснить не удалось — исключено». Второй вариант — полноправный, и в реестре он встречается: у резервного RPC в графе стоит «unresolved», потому что документации на него нет.
Аудитору нужна именно первая часть. Список адресов он вытащит из деплоя за десять минут; чего он не вытащит — это чем каждый из них обоснован и какие вы сами обосновать не смогли.
2. Аудит собственной арифметики — до чужого аудита
За две недели до отключения сети мы сели проверять формулу, считающую долю пользователя в пуле ликвидности. Было три подозрения:
- доля считается с жёстко зашитыми восемнадцатью знаками;
- если цена известна для одной стороны пула, код удваивает её;
- цена токена берётся из последнего попавшегося пула, без учёта глубины.
Два подозрения из трёх данные сняли. Восемнадцать знаков стоят и в числителе, и в знаменателе — множитель сокращается, доля верна при любых decimals. Удвоение цены за месяц не сработало ни разу: у всех четырнадцати документированных пар цена известна для обеих сторон, а перекос между сторонами не превышал 1 %. Оговорка, которую надо произносить вместе с выводом: проверка покрыла только документированные пары — на недокументированных эта ветка не измерялась, и именно там нашёлся настоящий дефект.
Настоящий дефект оказался не в формуле, а в том, откуда берутся её части. Для недокументированных пар резервы читались с цепочки живьём, а totalSupply — из кэша реестра, собранного неделями раньше. Числитель свежий, знаменатель устаревший. Замер против цепочки показал средний дрейф supply 13,2 %, максимальный — 61 %. Считается это так: живой supply просел до 39 % от закэшированного, значит деление на устаревший знаменатель занижает долю в 2,6 раза. И занижает в опасную сторону — когда за деньгами не приходят, решив, что их там мало.
Полезное здесь не в самой ошибке, а в порядке действий: три гипотезы были записаны до проверки, и две из них честно умерли о данные. Это второй документ, который стоит отдать аудитору, — список того, что вы уже проверили и чем именно опровергли.
3. Отчёт, который объявляет свои пробелы
В нашем отчёте есть обязательное поле notCovered — перечень того, что проверка не покрывает: предметы в контрактах аукционов, классы активов, по которым нет официальных указаний, контракты, не ответившие на запрос.
Формулировка из README: сканер, который молча пропускает класс контрактов и печатает чистый отчёт, хуже, чем отсутствие сканера. Ровно то же верно про подготовку к аудиту. «Вот покрытие тестами, вот чего в нём нет и почему» экономит аудитору первые два дня; «вот покрытие тестами» без второй половины эти два дня ему создаёт — он всё равно будет искать границу, только уже за ваш счёт.
Мы этот принцип нарушили сами и знаем цену: собственный дефект превратил неудачное чтение в «здесь пусто», и отчёт получился чистым там, где проверка не проходила. Разбор — в отдельной статье.
4. Изоляция по модулям и фиксированный блок
Две вещи технические, но обе экономят чужое время.
Изоляция. Проверка разложена на восемь модулей, и упавший модуль даёт строку ошибки в отчёте, а не отменяет отчёт. Аудитор, у которого падает один шаг из восьми, теряет прогон целиком; у которого падает один модуль — теряет модуль.
Фиксированный блок. Любой наш замер снимается на конкретном номере блока и потому воспроизводится кем угодно. Это же свойство ловит ошибки: расхождение на исторических данных не спишешь на движение рынка, потому что данные там не меняются.
Что собрать и в каком порядке
По убыванию отдачи; первый пункт — работа на вечер, остальные три укладываются в пару недель:
- Список утверждений с источниками и честной строкой «не выяснено» там, где не выяснено. Это документ, а не код, и делается быстрее всего.
- Разбор собственных допущений: что вы подозревали, что проверили, чем опровергли. Даже если все подозрения не подтвердились — особенно если не подтвердились.
- Явные границы покрытия рядом с тестами.
- Воспроизводимый прогон: фиксированный блок, изоляция шагов, ошибки в выходе, а не в логе.
Ни один из четырёх пунктов не требует трогать сам контракт. Всё это — про то, чтобы человек, которому вы платите за поиск дефектов, занимался поиском дефектов.
Читать дальше
В резолюции ломается момент, а не факт
Два разбора 2026 года — снимок цены, который покупали на секунды, и спор о продаже биткоина Strategy. Факт был известен, а рынок закрыли против него.
Дешевле всего обходится то, что до модели не доехало
Два наших сборщика перемалывают 62 миллиона знаков чужого текста. Разбор счёта показал, что расходы держит не цена за токен, а фильтр перед моделью.
Аудит был. В границы он не входил
Заголовок обещает 885 млн потерь вне границ аудита. В самом исследовании этого числа нет, а на двух инцидентах держится весь вывод. Разбираем, что осталось.