Обработка ошибок (assert, require, revert)
Вызов контракта может завершиться и вернуть ошибку. Обработка ошибок в Solidity осуществляется с помощью четырех функций: assert, require, revert и throw (теперь устаревшая).
Когда контракт завершается с ошибкой, все изменения состояния (изменения переменных, баланса и т.д.) отменяются, вплоть до цепочки вызовов контракта, если было вызвано более одного контракта. Это гарантирует, что транзакции являются атомарными, то есть они либо завершаются успешно, либо не влияют на состояние и полностью отменяются.
Функции assert и require работают одинаково, оценивая условие и прекращая выполнение с ошибкой, если условие ложно. По традиции assert используется, когда ожидается, что результат будет истинным, то есть мы используем assert для проверки внутренних условий. Для сравнения, require используется при тестировании входных данных (таких как аргументы функции или поля транзакции), устанавливая наши ожидания для этих условий.
Мы использовали require в модификаторе функции onlyOwner, чтобы проверить, что отправитель сообщения является владельцем контракта:
require(msg.sender == owner);
Функция require действует как условие шлюза, предотвращая выполнение остальной части функции и выдавая ошибку, если она не выполнена.
Начиная с Solidity v0.6.0, require может также включать полезное текстовое сообщение, которое может быть использовано для отображения причины ошибки. Сообщение об ошибке записывается в журнал транзакций. Таким образом, мы можем улучшить наш код, добавив сообщение об ошибке в нашу функцию require:
require(msg.sender == owner, "Только владелец контракта может вызвать эту функцию");
Функции revert и throw останавливают выполнение контракта и возвращают все изменения состояния. Функция throw устарела и будет удалена в будущих версиях Solidity; вместо нее следует использовать revert. Функция revert также может принимать в качестве единственного аргумента сообщение об ошибке, которое записывается в журнал транзакций.
Некоторые условия в контракте будут вызывать ошибки независимо от того, проверяем ли мы их явным образом. Например, в нашем контракте Faucet мы не проверяем, достаточно ли эфира для удовлетворения запроса на вывод средств. Это связано с тем, что функция перевода средств выдаст ошибку и отменит транзакцию, если баланс недостаточен для осуществления перевода:
msg.sender.transfer(withdraw_amount);
Тем не менее, возможно, будет лучше выполнить явную проверку и предоставить четкое сообщение об ошибке в случае неудачи. Мы можем сделать это, добавив оператор require перед передачей:
require(this.balance >= withdraw_amount,
"Недостаточный баланс в кране для запроса на снятие средств");
msg.sender.transfer(withdraw_amount);
Дополнительный код проверки ошибок, подобный этому, немного увеличит потребление газа, но он обеспечивает лучшее информирование об ошибках, чем если бы он был опущен. Вам нужно найти правильный баланс между потреблением газа и многословной проверкой ошибок, основываясь на предполагаемом использовании вашего контракта. В случае контракта Faucet, предназначенного для тестовой сети, мы, вероятно, сделаем выбор в пользу дополнительных отчетов, даже если это будет стоить больше газа. Возможно, в случае контракта для основной сети мы предпочтем быть экономными в использовании газа.