Понимание того, почему и как создать «смертный» смарт-контракт с учетом относительных последствий для безопасности.

Что такое смарт-контракт

Учетная запись «смарт-контракт» — это просто программа, которая работает в блокчейне Ethereum и определяется своим кодом (функциями) и данными (своим состоянием).

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

После развертывания в сети смарт-контракты не могут быть изменены или удалены по умолчанию. Однако можно включить возможность selfdestruct, которая позволит «удалить» смарт-контракт, удалив код и его внутреннее состояние (хранилище) и оставив пустую учетную запись.

Зачем разрушать контракт

Таким образом, использование функциональности selfdestruct позволяет разработчикам удалять смарт-контракты из блокчейна. Но каковы причины этой радикальной контрмеры?

Чтобы ответить на этот вопрос, полезно прочитать Почему смарт-контракты самоуничтожаются?

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

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

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

Как работает самоуничтожение?

Жизнь смарт-контракта начинается с транзакции создания из EOA или контрактной учетной записи. Полезная нагрузка транзакции, создающей смарт-контракт, сама по себе является байт-кодом, который запускает конструктор контракта (если он есть) для инициализации состояния контракта.

С другой стороны, последним вздохом контракта является его уничтожение. selfdestruct в Ethereum — это OPCODE на уровне EVM, независимо от используемого языка или клиента.

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

selfdestruct(address recipient);

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

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

Удаление контракта не удаляет историю транзакций (прошлое) контракта, поскольку сама цепочка блоков неизменна.

«Поэтому использование самоуничтожения — это не то же самое, что удаление данных с жесткого диска».

Создание смарт-контракта «Смертный»

Теперь мы создадим простой смарт-контракт с конструктором и командой selfdestruct:

Обычно желательно, чтобы команду selfdestruct могла вызывать только учетная запись, изначально создавшая контракт. По этой причине используется переменная типа адреса с именем owner. Фактически при создании контракта конструктор присваивает эту переменную msg.sender.

Контракт имеет минимальную destroy() функцию. Оператор require гарантирует, что только владелец может запустить эту функцию. Он проверяет, что владелец является вызывающим абонентом, в противном случае происходит сбой. Если чек в порядке, контракт самоуничтожится, а оставшаяся сумма будет отправлена ​​на адрес владельца.

Следующие тесты показывают предполагаемое поведение разрушаемого контракта.

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

Влияние на безопасность

Существует распространенное заблуждение о смарт-контрактах. На самом деле, многие неопытные разработчики Solidity ошибочно полагают, что контракт может получить эфиры только через платные функции.

Это неправда!

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

Итак, функция selfdestruct — это способ принудительно отправить эфиры на любой контракт вне зависимости от того, какой код реализован внутри него (даже если в контракте отсутствуют оплачиваемые функции).

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

  1. можно создать контракт с функцией selfdestruct
  2. отправить на него эфир
  3. позвони selfdestruct(targetContract)

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

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

Этот контракт представляет собой простую игру и не предназначен для использования на практике. Пользователь, который хочет играть, отправляет 0,5 эфира на контракт (вызов функции play()). Они надеются стать теми, кто достигнет одной из трех вех. Вехи — это определенное количество эфиров, принадлежащих контракту с течением времени. Первый пользователь, достигший определенного рубежа, может получить награду в эфире по окончании игры. Игра заканчивается, когда достигнута последняя веха.

Злоумышленник может отправить небольшое количество эфира (также достаточно 0,000001) через функцию selfdestruct контракта, развернутого им самим. Эта простая операция не позволяет любому будущему игроку достичь вехи. Это связано с тем, что currentBalance, рассчитанное в строке 17, никогда не будет кратно 0,5 эфира благодаря этому небольшому вкладу эфира в баланс контракта (обратите внимание, что игроки вынуждены отправлять только 0,5 эфира).

Проблема здесь в неправильном использовании this.balance в строке 17. Логика контракта не должна зависеть от баланса контракта, так как им можно злонамеренно манипулировать.

Возможным решением может быть использование самоопределяемой переменной (например, depositedEth) и ее увеличение для безопасного отслеживания депонированного эфира. На эту переменную не повлияет вызов selfdestruct, поскольку она больше не ссылается на this.balance.

uint currentBalance = depositedEth + msg.value;

Спасибо за чтение. Надеюсь, вам понравилась статья.

Дай мне знать, что ты думаешь об этом.