Разница между require и assert и разница между revert и throw

Я просматривал документы и ищу разъяснения о разнице между requireи assertи throwи revert.

assert(bool condition): прервать выполнение и вернуть изменения состояния, если условие ложно (используется для внутренней ошибки)

require(bool condition): прервать выполнение и вернуть изменения состояния, если условие ложно (используется для искаженного ввода)

В частности, в отношении assertи require, как вы проводите линию между искаженным вводом и внутренней ошибкой?

Вы также можете проверить эту прекрасную статью на тему: Revert(), Assert() и Require() в Solidity и новый код операции REVERT в EVM.

Ответы (5)

Есть два аспекта, которые следует учитывать при выборе между assert()иrequire()

  1. Эффективность газа
  2. Анализ байт-кода

1. Эффективность газа

assert(false)компилируется в 0xfe, что является недопустимым кодом операции, израсходовав весь оставшийся газ и отменив все изменения.

require(false)компилируется в 0xfdкоторый является REVERTкодом операции, что означает, что он возместит оставшийся газ. Код операции также может возвращать значение (полезно для отладки), но я не думаю, что на данный момент это поддерживается в Solidity. (2017-11-21)

2. Анализ байт-кода

Из документов (выделено мной)

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

Приведенный выше отрывок является ссылкой на все еще (по состоянию на 21 ноября 2017 г.) экспериментальный и недокументированный файл SMTChecker.

Я использую несколько эвристик, чтобы решить, что использовать.

Используйте require()для:

  • Проверка ввода пользователя
  • Проверить ответ от внешнего контракта,
    т.е. использоватьrequire(external.send(amount))
  • Проверка условий состояния перед выполнением операций по изменению состояния, например, в ownedситуации с контрактом .
  • Как правило, вы должны использовать requireчаще,
  • Как правило, он будет использоваться в начале функции.

Используйте assert()для:

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

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

Историческая справка:

Функции require()и assert()были добавлены в Solidity до форка Byzantium, в v0.4.10. До Византии они вели себя одинаково, но уже компилировались под разные опкоды. Это означало, что некоторые контракты, развернутые до Byzantium , после форка вели себя по-другому , главное отличие заключалось в том, что началось возмещение неиспользованного газа.

Но оба изменения assert()и require()откат записываются в блокчейн, чтобы balance[_to] =balance[_from] +_valueих можно было отменить, если сработает условие, не так ли assert()? require()(я говорю после прошлогоднего хардфорка). Или assert()продолжают вноситься изменения?
Оба отката меняются, что указано в разделе Gas Efficiency.
Я действительно не понимаю смысла использования assert больше, так как у меня точно такое же поведение с require, но у меня есть возможность возместить немного газа, в чем цель?
Разница также смысловая, т.е. «это условие даже не должно быть достижимо. Попробуйте скомпилировать этот код с последней версией solc: gist.github.com/maurelian/02904ae729fb11213cde20ba05a202e6 . Он предупредит вас, что второе утверждение утверждения может быть истинным для определенных значений.

Я использую requireдля проверки ввода, так как это немного эффективнее, чем if/throw.

function foo(uint amount) {
    require(amount < totalAmount);
    ...
}

Где as assertследует использовать больше для обнаружения ошибок во время выполнения:

function foo(uint amount) {
    ...
    __check = myAmount;
        myAmount -= amount;
    assert(myAmount < __check);
    ...
}

revertотменит изменения и возместит неиспользованный газ в более поздней версии Ethereum, но ATM действует так же, как и throw.

Могу ли я просто написать assert(myAmount-amount<myAmount) и пропустить переменную __check? какой из них лучше, почему/почему бы и нет?
Гораздо безопаснее не иметь ненужного дублирования логики в мутации состояния и assertаргументе. Это может привести к кошмару отладки, когда операции будут связаны для состояния, но забыты для утверждения.
Но оба изменения assert()и require()откат записываются в блокчейн, чтобы balance[_to] =balance[_from] +_valueих можно было отменить, если сработает условие, не так ли assert()? require()(я говорю после прошлогоднего хардфорка). Или assert()продолжают вноситься изменения? Но оба изменения assert()и require()откат записываются в блокчейн, чтобы balance[_to] =balance[_from] +_valueих можно было отменить, если сработает условие, не так ли assert()? require()(я говорю после прошлогоднего хардфорка). Или assert()продолжают вноситься изменения?

Я думаю, что ни один из ответов не является правильным.

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

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

throwустарел в пользу возврата.

revertзарезервирован для условий ошибок, влияющих на бизнес-логику. Например, кто-то отправляет голос, когда голосование уже близко.

requireи revertв основном схожи с внутренней реализацией EVM, но разработчики оценят различие.

Очень обновленный ответ. +1

Assertподходит для проверки условий, которые не должны произойти, но происходят.

Requireподходит для проверки нежелательных условий, которые могут возникнуть.

В Solidity есть SMTChecker , который делает использование assertочень крутым, потому что он может доказать , что ваши инварианты верны:

Solidity реализует формальный подход к проверке, основанный на SMT (теориях выполнимости по модулю) и решении Хорна . Модуль SMTChecker автоматически пытается доказать, что код удовлетворяет спецификации, заданной операторами requireи assert. То есть он рассматривает requireутверждения как предположения и пытается доказать, что условия внутри assertутверждений всегда верны. Если обнаружена ошибка утверждения, пользователю может быть предоставлен контрпример, показывающий, как утверждение может быть нарушено. Если SMTChecker не выдает предупреждения для свойства, это означает, что свойство безопасно.

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

Вы используете assert, чтобы помочь вам поймать, когда происходит невозможное.

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

Если SMTChecker вдохновит вас начать использовать assert, это очень хорошо. Но помните, что они предназначены для инвариантов , как упоминается в документах Solidity :

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