Сегодняшняя статья открывает новую подсерию в серии статей «Все о Solidity», посвященную ошибкам в Solidity и средах выполнения на основе EVM.

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

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

«Если отладка — это процесс устранения программных ошибок, то программирование должно быть процессом их добавления».
Эдсгер В. Дейкстра

Оглавление

- The Program Counter
- EVM Errors & Halting Exceptions
- Types of Errors (Compile vs Runtime Errors)
- Compile Time Errors
- Contract Runtime Errors
- EVM Halting Exceptions
    - Out of Gas
    - Stack Under/OverFlow
    - Invalid JUMP Destination
    - Bad Instruction

Счетчик программ

Когда EVM запускает байт-код смарт-контракта, он запускает каждый код операции один за другим, используя счетчик программ (ПК).

Счетчик программ (ПК) кодирует, какая инструкция (хранящаяся в коде контракта) должна быть прочитана EVM следующей (и, следовательно, выполнена). он увеличивается на один байт после каждой следующей инструкции (за исключением инструкций PUSHN и JUMP/JUMPI, где ПК модифицируется до пункта назначения JUMPDEST в байт-коде)

Думайте о EVM как о водителе на дороге. Когда водитель сталкивается с некоторыми дорожными знаками с направлениями, сигналами и ограничениями, он знает, что ему следует делать дальше или нет.

EVM будет продолжать запускать коды операций в текущей среде выполнения контракта до тех пор, пока ПК не обнаружит допустимые коды операций.

Но некоторые коды операций могут указывать на остановку выполнения. Когда выполнение останавливается, мы говорим, что EVM останавливается (см. Yellow Paper).

Но выполнение может быть остановлено успешно или с ошибкой. Если выполнение успешно останавливается, состояние блокчейна обновляется. В противном случае любое изменение состояния отменяется, и транзакция не регистрируется в блокчейне. Это связано с атомарной природой Ethereum. Транзакция может либо завершиться полностью, либо не завершиться вообще. Не существует понятия «частичное завершение».

EVM прекратит выполнение и успешно завершит работу, когда счетчик программ столкнется с одним из следующих двух кодов операций:

  • STOP (код операции 0x00): успешно завершает выполнение.
  • RETURN (опкод 0xf3): успешно покидает текущий контекст + возвращает некоторые данные из памяти (= определенное количество байтов, начиная с указанного смещения в памяти).

Напротив, EVM остановит выполнение и завершит работу с ошибкой, когда счетчик программ столкнется с одним из следующих двух кодов операций:

  • REVERT: все изменения состояния отменяются. Некоторые данные (указанные в памяти) и оставшийся газ возвращаются вызывающему абоненту.
  • INVALID: это назначенная недействительная инструкция в EVM. Все изменения состояния отменяются, и весь оставшийся газ расходуется. Звонящий не получает компенсацию за газ.

Ошибки EVM и исключения остановки

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

Для EVM по умолчанию нет безопасного способа продолжить выполнение, если ожидаемый эффект не произошел (или произошел непредвиденный эффект). Возврат сохраняет атомарность транзакций. Как описано в документации Solidity:

Самое безопасное действие — отменить все изменения и сделать всю транзакцию (или хотя бы вызов) безрезультатной.

Как показано в предыдущем разделе, ошибка возникает, когда программный счетчик встречает коды операций REVERT или INVALID. Но на самом деле это не единственные сценарии! Другие факторы могут вызвать ошибку в средах выполнения EVM.

Как мы увидим в следующих разделах, согласно «Желтой книге» на самом деле существует два типа операций по остановке:

  • исключительные ошибки остановки.
  • обычные ошибки остановки.

Упоминается как «исключительные/нормальные состояния остановки» в «Желтой книге».

Типы ошибок (ошибки компиляции и выполнения)

При написании смарт-контрактов в Solidity вы столкнетесь с тремя широкими категориями ошибок.

  • Ошибки времени компиляции (генерируются компилятором Solidity)
  • Ошибки выполнения (вызванные EVM при взаимодействии с байт-кодом смарт-контракта)
  • Исключительные ошибки остановки (вызванные процессором стека EVM)

Ошибки времени выполнения определяются как «Ошибка VM» в Remix при взаимодействии с развернутым контрактом (VM означает «Виртуальная машина» и в данном случае относится к EVM).

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

Ошибки времени компиляции

Ошибки времени компиляции возникают, когда некоторый код, написанный на языке программирования смарт-контрактов, ориентированный на виртуальную машину Ethereum (EVM), компилируется в исполняемый код EVM (= байт-код EVM).

К ним относятся ошибки, генерируемые компиляторами, например:

Ошибки выполнения контракта

Ошибки времени выполнения — это тип ошибок, возникающих в производственном коде (смарт-контракты, развернутые в тестовой сети или основной сети).

Название «ошибка выполнения» происходит от этих ошибок, возникающих при взаимодействии со смарт-контрактом.

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

Ошибки выполнения — это то, что мы называем исключениями, изменяющими состояние. Это означает, что они отменяют все изменения состояния, внесенные в контракт во время текущего вызова контракта и транзакции.

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

Ниже приведен пример, когда EOA tippy.eth (= адрес, стоящий за этим именем ENS) пытался выполнить обмен токенов, взаимодействуя с контрактом маршрутизатора агрегации 1inch v3, но что-то пошло не так. , и транзакция откатывается при вызове функции swap(address,...,bytes).

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

  • под адресом To (контракта 1inch) мы видим красное сообщение: Warning! Error encountered during contract execution [Reverted]. Это дает нам информацию о том, что на уровне смарт-контракта произошла ошибка.
  • Рядом с полем Status вверху мы видим сообщение об ошибке, возвращаемое смарт-контрактом: Fail with error ‘callBytes failed: Error(External call failed: Error(ORDER_UNFILLABLE))

Исключения остановки EVM

Примечание. Точной терминологии для описания ошибок такого типа не существует. В Желтой книге Ethereum они упоминаются как «исключительное состояние остановки». См. конец раздела выше «EVM и исключения остановки» для справки.

Существует подкатегория ошибок времени выполнения, которую я называю ошибками «исключения остановки EVM». Эти ошибки возникают, когда что-то идет не так со стековой машиной EVM.

В то время как ошибки времени выполнения контракта, описанные выше, возникают из-за того, как запрограммирован код смарт-контракта Solidity, ошибки стековой машины связаны непосредственно с EVM и тем, как виртуальная машина Ethereum работает и обрабатывает инструкции.

EVM — это стековая машина, которая использует процессор на основе стека для выполнения инструкций кодов операций.

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

  • из газа
  • опустошение стека
  • переполнение стека
  • неверный пункт назначения перехода
  • плохая инструкция

Из газа

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

Ниже приведен пример ошибки времени выполнения от etherscan.

Адрес EOA superphiz.eth пытался взаимодействовать с контрактом Stoner Cats Token, но не предоставил достаточно газа для завершения всей транзакции.

Транзакция показывает статус “Fail” с сообщением ниже с указанием “Warning! Error encountered during contract execution [out of gas]”.

Недостаточное/переполнение стека

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

Однако их можно воссоздать вручную для демонстрационных целей и понять, как EVM может останавливаться в исключительных случаях.

Давайте воспользуемся некоторыми упражнениями из Мастерской EVM Nethermind + игровой площадки EVM.codes, чтобы поэкспериментировать и воспроизвести эти ошибки.

Опустошение стека

Переполнение стека

Недопустимый пункт назначения JUMP

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

Плохая инструкция

Примечание: его не следует путать с кодом операции INVALID 0xfe.

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

Если мы посмотрим на полную таблицу наборов инструкций EVM из ethvm.io, то увидим, что некоторые из шестнадцатеричных значений каждого 16-базового диапазона не имеют связанного с ними кода операции.

EVM, например, выдаст ошибку «плохая инструкция», если счетчик команд наткнется на шестнадцатеричный код операции 0x21. Поскольку это шестнадцатеричное значение не связано ни с одной действительной инструкцией в наборе (см. красный кружок ниже), у EVM нет другого выбора, кроме как остановить выполнение и выдать "плохую инструкцию"ошибка.

Примечание.эта ошибка не воспроизводится в evm.codes, так как игровая площадка преобразует неправильные шестнадцатеричные инструкции байт-кода в код операции INVALID. Смотрите скриншот ниже.

Рекомендации