Статьи

Smart Contract Safety: лучшие практики и шаблоны проектирования

Технология Blockchain — все еще очень экспериментальная технология. Все движется быстро, и вы должны ожидать постоянных изменений в ландшафте безопасности, так как обнаруживаются новые ошибки и угрозы безопасности, а также разрабатываются новые лучшие практики. Следуя правилам безопасности, описанным в этой статье, вы сможете лучше понять, как разрабатывать безупречные умные контракты Ethereum.

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

Подготовиться к неудаче

Любой нетривиальный контракт будет содержать ошибки. Следовательно, ваш код должен уметь корректно реагировать на ошибки и уязвимости. При написании кода имейте в виду следующие шаблоны проектирования безопасности.

Шаблон Проверки-Эффекты-Взаимодействия

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

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

function auctionEnd() public {
// 1. Checks
require(now >= auctionEnd);
require(!ended);
// 2. Effects
ended = true;
// 3. Interaction
beneficiary.transfer(highestBid);
}

Выключатель

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

bool private stopped = false;
address private owner;
modifier isAdmin() {
if(msg.sender != owner) {
throw;
}
_
}
function toggleContractActive() isAdmin public
{
// You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users
stopped = !stopped;
}
modifier stopInEmergency { if (!stopped) _ }
modifier onlyInEmergency { if (stopped) _ }
function deposit() stopInEmergency public
{
// some code
}
function withdraw() onlyInEmergency public
{
// some code
}

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

Ограничение скорости

Проблема, которую мы пытаемся решить, — это посылка запроса на определенную функцию, которая может помешать правильной работе умного контракта. Решение довольно простое: мы регулируем, как часто задача может быть выполнена в течение определенного периода времени. Мы также можем ограничить количество эфира, которое владелец может отозвать из контракта, чтобы предотвратить быстрый отток средств. Или другой пример — ограничение количества выданных токенов с течением времени на уровне контракта — каждый месяц контракт будет распространять 10 000 токенов.

contract RateLimit {
uint enabledAt = now;
modifier enabledEvery(uint t) {
require(now >= enabledAt, «Access is denied. Rate limit exceeded.»);
enabledAt = now + t;
_;
}
function f() public enabledEvery(1 minutes) {
// some code
}
}

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

Speed ​​Bumps — Задержка действий по контракту

Удары скорости чрезвычайно полезны, когда происходят злонамеренные события, поскольку это дает умному владельцу контракта время действовать соответственно. Например, в DAO была установлена ​​задержка в 27 дней, при которой не могли быть выполнены никакие функции, в результате чего средства в контракте были увеличены, что повысило вероятность восстановления. Тем не менее, увеличение скорости было довольно бесполезным для дела DAO, так как никаких действий по восстановлению не было. Таким образом, схема скачков скорости должна использоваться в сочетании с автоматическим выключателем, который блокирует контракт, кроме функции снятия. Это гарантирует, что пользователи смогут выводить средства до того, как будет выполнено вредоносное действие.

struct RequestedWithdrawal {
uint amount;
uint time;
}
mapping (address => uint) private balances;
mapping (address => RequestedWithdrawal) private requestedWithdrawals;
uint constant withdrawalWaitPeriod = 28 days; // 4 weeks
function requestWithdrawal() public {
if (balances[msg.sender] > 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0; // for simplicity, we withdraw everything;
// presumably, the deposit function prevents new deposits when withdrawals are in progress
requestedWithdrawals[msg.sender] = RequestedWithdrawal({
amount: amountToWithdraw,
time: now
});
}
}
function withdraw() public {
if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {
uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;
requestedWithdrawals[msg.sender].amount = 0;
if(!msg.sender.send(amountToWithdraw)) {
throw;
}
}
}

Это ускорение контракта Solidity задерживает запрос на снятие средств на 4 недели.

Предел баланса

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

contract LimitBalance {
uint256 public limit;
function LimitBalance(uint256 value) public {
limit = value;
}
modifier limitedPayable() {
require(this.balance <= limit);
_;
}
function deposit() public payable limitedPayable {
// some code
}
}

Этот фрагмент кода использует модификатор для ограничения баланса внутри контракта. Модификатор применяется к функции «депозит», поскольку именно там принимаются деньги.

Плагин iOlite для разработки безопасных контрактов

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

Кроме того, команда iOlite разработала плагин для разработчиков, который превращает любой язык программирования в код Solidity (сначала среди различных языков блокчейна), используя структуры, прикрепленные к фрагментам кода, определенным экспертами Solidity.

Плагин для Visual Studio Code можно найти на торговой площадке . Полную документацию можно посмотреть на Github .

Суть

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

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