Solidity 문법 - (19) 에러 핸들링
목차 |
1. 에러 핸들링 ( assert, revert, require ) |
2. 에러 핸들링 (assert, revert, require) 예제코드 |
3. 에러 핸들링 (try/catch) |
4. 에러 핸들링 (try/catch) 예제코드 |
1. 에러 핸들링 ( assert, revert, require )
솔리디티에서는 자체적으로 에러를 출력하는 함수를 지원한다.
솔리디티는 버전에 따라서 문법이 조금씩 바뀌었으나 크게 다르지는 않다.
각 에러들에 대해서 알아보도록 하자.
assert
- ~ 0.8.0 : 가스환불 X
- 0.8.1 ~ : 가스환불 O, assert로 발생한 에러는 Panic 타입(uint256)의 에러에 속한다.(Error Exception)
- 가스를 모두 소비한 후, 특정한 조건에 부합하지 않으면 에러를 발생시킨다.
- 주로 스마트 컨트랙트의 테스트 용도로 사용한다.
revert
- 조건없이 에러를 발생시키고, gas를 환불 시켜준다.
- 인자값으로 에러 발생시 나올 메시지를 입력할 수 있다.
- 조건없이 에러를 발생시키므로, 조건문과 함께 사용하여 조건에 따라 에러를 발생시킨다.
require
- 특정한 조건에 부합하지 않으면 에러를 발생시키고, gas를 환불 시켜준다.
- 두 번째 인자값으로 에러 발생시 나올 메시지를 입력할 수 있다.
2. 에러 핸들링 ( assert, revert, require ) 예제 코드
우선 0.8.1 이전 컴파일 버전으로 실행 해보도록하자.
// SPDX-License-Identifier: MIT
// 0.8.1 이전 버전으로 컴파일
pragma solidity >= 0.7.0 < 0.9.0;
contract ErrorHandle {
function assertNow() public pure {
// assert(조건문);
// 보통 테스트를 위한 용도로 사용한다.
// 가스 환불 X
assert(false);
}
function revertNow() public pure {
// 조건없이 에러를 발생
// 인자로 에러 메시지를 입력
// 가스 환불 O
// 조건없이 에러를 발생 시키기 때문에 조건문과 함께 사용한다.
revert("error");
}
function requireNow(uint256 _num) public pure {
// 조건이 false일 경우에 에러를 발생
// 두 번째 인자로 메시지를 넣을 수 있다.
// 가스 환불 O
require(_num <= 10, "error message");
}
}
assertNow의 경우 가스를 2978938 소비하고 에러를 발생시킨것을 볼 수 있다.
revertNow의 경우에는 가스 소비가 255로 굉장히 낮은 것을 볼 수 있다.
또한, "error"가 출력된 것을 볼 수 있다.
마지막으로 require도 마찬가지로 가스 소비가 387로 굉장히 낮고, "error message"가 출력되었다.
3. 에러 핸들링 ( try/catch )
위의 세 가지 에러핸들러는 에러가 발생하면 프로그램이 종료되지만,
try catch는 에러가 나도 프로그램을 종료시키지 않고 어떻게 코드를 실행할지 동작을 정할 수 있다.
try catch문 안에서 assert/revert/require를 통해 에러가 발생하면 catch로 에러를 잡지않고, 프로그램을 종료시킨다.
이는개발자가 의도한 에러로 인식한다.
try catch문 밖에서 assert/revert/require를 통해 에러가 발생하면 catch는 에러를 잡고 Error를 핸들링한다.
try catch는 주로 다음의 세 가지 경우에 사용이 된다
(1) 주로 외부 스마트 컨트랙트 함수를 부를경우
(2) 외부 스마트 컨트랙트를 생성할 경우
(3) 스마트 컨트랙트 내에서 함수를 부를 경우
catch의 종류
try catch의 catch에서 잡는 에러의 종류가 있으며, 아무것도 적지 않으면 모든 종류의 에러를 전부 잡는다.
catch Error(string memory reason) { ... }
revert나 require를 통해 생성된 에러를 잡는다
catch Panic(uint errorCode) { ... }
assert를 통해 생성된 에러가 날 때 이 catch에 잡힌다
catch(bytes memory lowLevelData) { ... }
로우 레벨의 에러를 잡는다
catch { ... }
모든 종류의 에러를 모두 잡는다
4. 에러 핸들링 ( try/catch ) 예제코드
Case 1 외부 스마트 컨트랙트 함수를 부를경우
아래 코드의 주석을 풀고 각각 실행해보면 require 조건을 통과할 때에는 try문의 코드가 실행되고,
require 조건을 만족시키지 못하면 catch Error에 잡힌다.
주석을 바꿔서 assert로 코드를 실행하면, catch Panic에 에러가 잡힌다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Wallet {
uint256 public current_balance = 100;
function purchase(uint256 _value) public returns(uint256) {
// require(_value < current_balance, "not enough balance");
// assert(_value < current_balance);
current_balance -= _value;
return current_balance;
}
}
contract Case1 {
event catchErr(string _name, string _err);
event catchPanic(string _name, uint256 _err);
event catchLowLevelErr(string _name, bytes _err);
Wallet public instance = new Wallet() ;
function ExamTryCatch(uint256 _value) public returns(uint256, bool) {
try instance.purchase(_value) returns(uint256 value){
return(value, true);
} catch Error(string memory _err) {
emit catchErr("revert/require", _err);
return(0, false);
} catch Panic(uint256 _errorCode) {
emit catchPanic("assertError/Panic", _errorCode);
return(0, false);
} catch (bytes memory _errorCode) {
emit catchLowLevelErr("LowLevelError", _errorCode);
return(0, false);
}
}
}
Case 2 외부 스마트 컨트랙트(의 인스턴스)를 생성할 경우
위의 세 가지 종류의 에러를 적어주지 않아도 아래처럼 간결하게 코드작성이 가능하다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract character {
string private name;
uint256 private power;
constructor(string memory _name, uint256 _power){
name = _name;
power = _power;
}
}
// 외부 컨트랙트의 인스턴스를 생성할 때
contract Case2 {
event catchOnly(string _name, string _err);
function playTryCatch(string memory _name, uint256 _power) public returns(bool succeessOrfail) {
try new character(_name, _power) {
return(true);
} catch {
// catch를 하나만 써도 무방하다
emit catchOnly("catch", "ErrorS");
return(false);
}
}
}
Case 3 스마트 컨트랙트 내에서 함수를 부를 경우
this를 이용해서 같은 컨트랙트 내부에 있는 함수를 실행할 수 있다.
// 스마트 컨트랙트 내에서 함수를 부를 때
contract Case3 {
event catchOnly(string _name, string _err);
function simple () public pure returns(uint256) {
return 4;
}
function playTryCatch() public returns(uint256, bool) {
try this.simple() returns(uint256 _value) {
return(_value, true);
} catch {
emit catchOnly("catch", "ErrosS");
return(0, false);
}
}
}