Аудиты смарт-контрактов

Тема сегодняшней статьи довольно трудна, но ничем не теряет в актуальности. Не так давно мы уже публиковали статью, где рассказывали о самом явлении смарт-контрактов.

Нередки случаи, когда молодой проект запускал ограниченные минты платных NFT коллекций. При наличии плохо написанного смарт-контракта, знающие что к чему минтеры просто злоупотребляли возможностью обойти ограничения, что приводило к незаконному обогащению минтера и потере репутации и доверия к проекту.

Примеров больших потерь из-за плохо написанного смарт-контракта довольно много.

Как предотвратить потери?

Для достижения этой цели в наше время инструментов более чем достаточно, например:

  • Найм квалифицированного специалиста.
  • Заказ аудита у компании-аудитора.
  • Провести всю работу самостоятельно.

Сегодня мы разберем то, на что лучше обратить внимание, если возможности обратиться к специалистам у вас нет.

Как провести аудит смарт-контракта?

Важно - в этом гайде много непонятных слов. Если с технологиями и кодом вы "на ты" - вы можете не понять написанного, и это нормально. Этот материал может представлять для вас интерес, но лучше будет обратиться к профессионалу или хотя бы другу-кодеру.

Для начала необходимо удостовериться в безопасности окружения смарт-контракта. Для этого необходимо рассмотреть следующие аспекты:

  • Взаимодействие с внешними контрактами
    Проверьте, как контракт взаимодействует с другими контрактами и сервисами, и есть ли риски в этих взаимодействиях.
  • Фронтенд и Интеграция
    Если у контракта есть пользовательский интерфейс, убедитесь, что он не представляет явных угроз.

Далее, когда окружение рассмотрено, необходимо углубиться в проверку кода, для этого обратим внимание на:

  • Код и Синтаксис
    Проверьте, соответствует ли код стандартам Solidity (или другого используемого языка) и нет ли ошибок в синтаксисе.
  • Безопасность
    Проверка на наличие известных уязвимостей, таких как reentrancy, integer overflow/underflow и другие.
    Также немаловажно обратить внимание на оценку прав доступа, что связано с авторизацией адресов.
  • Логика
    Удостоверьтесь, что логика контракта соответствует его предназначению.
  • Архитектура
    Проверьте, хорошо ли контракт масштабируется и насколько он эффективен в использовании ресурсов (газа).
  • Тестирование
    Финальным этапом служит тестировка смарт-контракта и вынесение вердиктов на ее основе.

В зависимости от сложности и критичности контракта, вы можете варьировать глубину и ширину аудита. Чем выше ставки, тем больше внимания следует уделить каждому этапу.

Технически аудит кода смарт-контракта делится на два этапа: статичный и динамичный. Статический - этап, на котором проводится код-ревью, динамический - этап, на котором проводятся тесты. 

Инструменты, которые могут вам помочь с автоматической проверкой кода на наличие известных уязвимостей на этапе статического этапа:

На динамическом этапе можем порекомендовать попробовать сервис Truffle.

Пример

Вот примеры двух смарт-контрактов на Solidity: одного безопасного и другого потенциально небезопасного. Для наглядности сосредоточим внимание на одной конкретной уязвимости — реентерабельности.

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

Небезопасный смарт-контракт

Этот контракт уязвим для атаки реентерабельности, так как функция “withdraw” переводит средства, не обнуляя баланс пользователя:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UnsafeBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");
        
        balances[msg.sender] -= amount;
    }
}

Безопасный смарт-контракт

В этом контракте мы сначала обновляем баланс пользователя, а затем переводим средства, что предотвращает атаки реентерабельности.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SafeBank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");
    }
}

В безопасном варианте баланс пользователя уменьшается до того, как средства фактически переводятся. Это предотвращает возможность повторного входа (реентерабельности), при котором злоумышленник мог бы снова вызвать withdraw во время исполнения msg.sender.call.

Эти примеры упрощены для демонстрации конкретной уязвимости и не представляют из себя полноценных финансовых контрактов. Помимо реентерабельности существует множество других потенциальных уязвимостей, которые следует учитывать при разработке или аудите смарт-контрактов.

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

Для удобства их понимания и восприятия, команда AGAVA CRYPTO подготовила таблицу с самыми распространенными потенциально опасными конструкциями.

Это не исчерпывающий список, но основные проблемные моменты обычно связаны с безопасностью и логикой работы контракта. Именно поэтому важно всегда проводить тщательный аудит кода. 

Больше интересных статей и гайдов вы найдете в телеграм канале

AGAVA CRYPTO