Nesse artigo iremos abordar sobre como tratar de forma correta os erros de transações e condições no Solidity.
Falaremos sobre require, revert e assert para tratar condições e try / catch para tratar exceções.
Tratando erros de condições
Um erro desfará todas as alterações feitas no estado durante uma transação.
Você pode lançar um erro chamando require, revert ou assert
require- é usado para validar entradas e condições antes da execuçãorevert- é semelhante arequire, porém é lançada dentro de uma condiçãoassert- é usado para verificar um código que nunca deve ser falso. Falha na asserção provavelmente significa que há um bug
Usar o erro personalizado pode gerar economias no custo de gas pois evita que um código seja executado se não estiver de acordo com as condições.
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract Error {function testRequire(uint _i) public pure {// Require deve ser usado para validar condições como:// - entradas// - condições para execução de uma determinada operação// - retornar valores de chamadas para outras funçõesrequire(_i > 10, "Input must be greater than 10");// Irá retornar uma mensagem de erro se _i for menor ou igual a 10// Todo o código abaixo do require não será executado.}function testRevert(uint _i) public pure {// Reverter é útil quando a condição a ser verificada é complexa.// Este código faz exatamente a mesma coisa que o exemplo acimaif (_i <= 10) {revert("Input must be greater than 10");}}uint public num;function testAssert() public view {// Assert só deve ser usado para testar erros internos e verificar invariantes.// Aqui afirmamos que num é sempre igual a 0,// pois é impossível atualizar o valor de numassert(num == 0);}// Erros personalizáveiserror InsufficientBalance(uint balance, uint withdrawAmount);function testCustomError(uint _withdrawAmount) public view {uint bal = address(this).balance;if (bal < _withdrawAmount) {revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount});}}}
Aqui está outro exemplo simulando uma aplicação de carteira
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract Account {uint public balance;uint public constant MAX_UINT = 2**256 - 1;function deposit(uint _amount) public {uint oldBalance = balance;uint newBalance = balance + _amount;// balance + _amount não executa a função se balance + _amount >= balancerequire(newBalance >= oldBalance, "Invalid new balance");balance = newBalance;// Aqui afirmamos que o balance deve sempre ser maior ou igual ao oldBalance// após a atualização da variável balanceassert(balance >= oldBalance);}function withdraw(uint _amount) public {uint oldBalance = balance;// balance - _amount não executa a função se balance >= _amountrequire(balance >= _amount, "Balance insufficient");if (balance < _amount) {revert("Balance insufficient");}balance -= _amount;// Aqui afirmamos que o balance deve sempre ser menor ou igual ao oldBalance// após a atualização da variável balanceassert(balance <= oldBalance);}}
Tratamento de exceções (Try/catch Exceptions)
try / catch serve para detectar erros de chamadas de funções externas e na criação de contratos.
Abaixo podemos analisar um exemplo onde o try / catch se aplica
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;// External contract used for try / catch examplescontract Foo {address public owner;constructor(address _owner) {require(_owner != address(0), "endereço inválido 0");assert(_owner != 0x0000000000000000000000000000000000000001);owner = _owner;}function myFunc(uint x) public pure returns (string memory) {require(x != 0, "require retornou false para validação");return "myFunc chamada";}}contract Bar {event Log(string message);event LogBytes(bytes data);Foo public foo;constructor() {// O contrato Foo é usado por exemplo de try catch com chamada externafoo = new Foo(msg.sender);}// Exemplo de try / catch com chamada externa// tryCatchExternalCall(0) => Log("external call failed")// tryCatchExternalCall(1) => Log("my func was called")function tryCatchExternalCall(uint _i) public {// Se nenhuma exceção for detectada, o código emit Log será executadotry foo.myFunc(_i) returns (string memory result) {emit Log(result);} catch {// Se for detectado uma exceção (exception) um erro será emitidoemit Log("falha na chamada de função externa");}}// Exemplo de try / catch com a criação do contrato// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")function tryCatchNewContract(address _owner) public {try new Foo(_owner) returns (Foo foo) {// Você pode usar a variável foo aquiemit Log("Foo criado");} catch Error(string memory reason) {// pegar uma falha do revert() e require()emit Log(reason);} catch (bytes memory reason) {// pegar uma falha do assert()emit LogBytes(reason);}}}