無知

갈 길이 먼 공부 일기

기술 공부/블록체인

스마트 컨트랙트 (5-6) | Solidity(솔리디티) Error Handling

moozii 2022. 4. 18. 19:14

 

@Errors and the Revert Statement https://docs.soliditylang.org/en/v0.8.13/contracts.html#errors-and-the-revert-statement

 

Error handling require revert assert
Use when control flow is simple control flow is complicated control flow is simple
Check Pre-condition Pre-condition Post-condition
Returns Gas Unused Return Return Does not Return

 

- 솔리디티에서는 exception 발생 시 현재까지의 모든 transaction이 중단되고 transaction 이전 상태로 돌아간다.

 

 

 

 


exception을 발생시키기 위한 함수

 

1. require

주어진 조건을 검사해서 이를 만족시키지 못하면 exception을 발생시킨다. 남은 gas는 반환된다.

// require({check_condition}, "error message");

require(msg.sender == owner);
require(balances[msg.sender] >= amount, "Balance is not enough");

 

2. revert

require와 유사하지만 control flow가 복잡할 때, 중첩된 if 문과 같이 사용하면 편리하다. 남은 gas는 반환된다.

if (msg.sender != owner || balances[msg.sender] >= amount)
	revert();

 

3. assert

조건을 검사하는데 일반적으로 어떤 값을 변경하고 나서 그 값이 조건을 만족하는지 혹은 절대로 발생하면 안 되는 상황인지 확인할 때 사용한다. 따라서, 앞의 require나 revert가 함수의 앞쪽에서 pre-condition을 확인하는 데 많이 사용되는 데에 반해 assert는 뒷부분에서 post-condition을 확인하는 데 많이 사용된다. 남은 gas를 반환하지 않는다.

assert(address(this).balance >= amount);

 

 

Errors and the Revert Statement

Errors in Solidity provide a convenient and gas-efficient way to explain to the user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries). They have to be used together with the revert statement which causes all changes in the current call to be reverted and passes the error data back to the caller.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

/// Insufficient balance for transfer. Needed `required` but only
/// `available` available.
/// @param available balance available.
/// @param required requested amount to transfer.
error InsufficientBalance(uint256 available, uint256 required);

contract TestToken {
    mapping(address => uint) balance;
    function transfer(address to, uint256 amount) public {
        if (amount > balance[msg.sender])
            revert InsufficientBalance({
                available: balance[msg.sender],
                required: amount
            });
        balance[msg.sender] -= amount;
        balance[to] += amount;
    }
    // ...
}​


Errors cannot be overloaded or overridden but are inherited. The same error can be defined in multiple places as long as the scopes are distinct.

Instances of errors can only be created using revert statements. The error creates data that is then passed to the caller with the revert operation to either return to the off-chain component or catch it in a try/catch statement. Note that an error can only be caught when coming from an external call, reverts happening in internal calls or inside the same function cannot be caught. If you do not provide any parameters, the error only needs four bytes of data and you can use NatSpec as above to further explain the reasons behind the error, which is not stored on chain. This makes this a very cheap and convenient error-reporting feature at the same time. More specifically, an error instance is ABI-encoded in the same way as a function call to a function of the same name and types would be and then used as the return data in the revert opcode. This means that the data consists of a 4-byte selector followed by ABI-encoded data. The selector consists of the first four bytes of the keccak256-hash of the signature of the error type.

The statement <require(condition, "description");> would be equivalent to <if (!condition) revert Error("description")> if you could define <error Error(string)>. Note, however, that Error is a built-in type and cannot be defined in user-supplied code. Similarly, a failing <assert> or similar conditions will revert with an error of the built-in type Panic(uint256).

https://docs.soliditylang.org/en/v0.8.13/contracts.html#errors-and-the-revert-statement