Tratamento de erros de condições e exceções

porMatheusem17/05/2022

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ção

  • revert - é semelhante a require, porém é lançada dentro de uma condição

  • assert - é 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: MIT
pragma 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ções
require(_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 acima
if (_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 num
assert(num == 0);
}
// Erros personalizáveis
error 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: MIT
pragma 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 >= balance
require(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 balance
assert(balance >= oldBalance);
}
function withdraw(uint _amount) public {
uint oldBalance = balance;
// balance - _amount não executa a função se balance >= _amount
require(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 balance
assert(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: MIT
pragma solidity ^0.8.13;
// External contract used for try / catch examples
contract 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 externa
foo = 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á executado
try foo.myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
// Se for detectado uma exceção (exception) um erro será emitido
emit 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 aqui
emit 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);
}
}
}

Testar no Remix