За контекст вы платите аудитору. Вот что можно отдать заранее

Аудитор берёт деньги за время, и первые дни уходят на восстановление контекста. Четыре вещи, которые мы сделали для своего сканера чужих контрактов.

Обновлено:

Оговорка вперёд: своих контрактов, прошедших аудит, у нас нет. У нас есть инструмент, который читает 166 чужих контрактов — реестр остановившейся сети DFK Chain и сканер, который по любому адресу показывает, что на нём осталось. Это другая сторона того же стола: мы месяц занимались ровно тем, за что аудитор берёт деньги в первые дни, — восстанавливали контекст по чужому коду, у которого его не было.

Отсюда список того, что стоит отдать аудитору сразу. Каждый пункт мы делали себе, и у каждого есть цена, которую мы заплатили, когда его не было.

1. Происхождение, а не список адресов

Реестр у нас разложен на две части, и это разделение оказалось главным.

Первая — происхождение: файл на 196 килобайт, где у каждого адреса стоит либо ссылка на документацию и отметка о подтверждении чтением из сети, либо явное «выяснить не удалось». Вторая — слим-проекция на 166 контрактов, которую читает код; она генерируется из первой и руками не правится.

Правило, которое это держит, сформулировано ещё в первой фазе разбора: каждое утверждение заканчивается либо ссылкой на источник, либо записанным «выяснить не удалось — исключено». Второй вариант — полноправный, и в реестре он встречается: у резервного RPC в графе стоит «unresolved», потому что документации на него нет.

Аудитору нужна именно первая часть. Список адресов он вытащит из деплоя за десять минут; чего он не вытащит — это чем каждый из них обоснован и какие вы сами обосновать не смогли.

2. Аудит собственной арифметики — до чужого аудита

За две недели до отключения сети мы сели проверять формулу, считающую долю пользователя в пуле ликвидности. Было три подозрения:

  1. доля считается с жёстко зашитыми восемнадцатью знаками;
  2. если цена известна для одной стороны пула, код удваивает её;
  3. цена токена берётся из последнего попавшегося пула, без учёта глубины.

Два подозрения из трёх данные сняли. Восемнадцать знаков стоят и в числителе, и в знаменателе — множитель сокращается, доля верна при любых decimals. Удвоение цены за месяц не сработало ни разу: у всех четырнадцати документированных пар цена известна для обеих сторон, а перекос между сторонами не превышал 1 %. Оговорка, которую надо произносить вместе с выводом: проверка покрыла только документированные пары — на недокументированных эта ветка не измерялась, и именно там нашёлся настоящий дефект.

Настоящий дефект оказался не в формуле, а в том, откуда берутся её части. Для недокументированных пар резервы читались с цепочки живьём, а totalSupply — из кэша реестра, собранного неделями раньше. Числитель свежий, знаменатель устаревший. Замер против цепочки показал средний дрейф supply 13,2 %, максимальный — 61 %. Считается это так: живой supply просел до 39 % от закэшированного, значит деление на устаревший знаменатель занижает долю в 2,6 раза. И занижает в опасную сторону — когда за деньгами не приходят, решив, что их там мало.

Полезное здесь не в самой ошибке, а в порядке действий: три гипотезы были записаны до проверки, и две из них честно умерли о данные. Это второй документ, который стоит отдать аудитору, — список того, что вы уже проверили и чем именно опровергли.

3. Отчёт, который объявляет свои пробелы

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

Формулировка из README: сканер, который молча пропускает класс контрактов и печатает чистый отчёт, хуже, чем отсутствие сканера. Ровно то же верно про подготовку к аудиту. «Вот покрытие тестами, вот чего в нём нет и почему» экономит аудитору первые два дня; «вот покрытие тестами» без второй половины эти два дня ему создаёт — он всё равно будет искать границу, только уже за ваш счёт.

Мы этот принцип нарушили сами и знаем цену: собственный дефект превратил неудачное чтение в «здесь пусто», и отчёт получился чистым там, где проверка не проходила. Разбор — в отдельной статье.

4. Изоляция по модулям и фиксированный блок

Две вещи технические, но обе экономят чужое время.

Изоляция. Проверка разложена на восемь модулей, и упавший модуль даёт строку ошибки в отчёте, а не отменяет отчёт. Аудитор, у которого падает один шаг из восьми, теряет прогон целиком; у которого падает один модуль — теряет модуль.

Фиксированный блок. Любой наш замер снимается на конкретном номере блока и потому воспроизводится кем угодно. Это же свойство ловит ошибки: расхождение на исторических данных не спишешь на движение рынка, потому что данные там не меняются.

Что собрать и в каком порядке

По убыванию отдачи; первый пункт — работа на вечер, остальные три укладываются в пару недель:

  1. Список утверждений с источниками и честной строкой «не выяснено» там, где не выяснено. Это документ, а не код, и делается быстрее всего.
  2. Разбор собственных допущений: что вы подозревали, что проверили, чем опровергли. Даже если все подозрения не подтвердились — особенно если не подтвердились.
  3. Явные границы покрытия рядом с тестами.
  4. Воспроизводимый прогон: фиксированный блок, изоляция шагов, ошибки в выходе, а не в логе.

Ни один из четырёх пунктов не требует трогать сам контракт. Всё это — про то, чтобы человек, которому вы платите за поиск дефектов, занимался поиском дефектов.

Сергей Палий

Основатель, Sepia Software

Обо мне

Читать дальше

Блокчейн5 мин чтения

Аудит был. В границы он не входил

Заголовок обещает 885 млн потерь вне границ аудита. В самом исследовании этого числа нет, а на двух инцидентах держится весь вывод. Разбираем, что осталось.

Все статьи