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);}}}