이전글
에러 처리
이번 글에서는 솔리디티의 에러 처리에 대해 알아보겠습니다.
1. assert, revert, require
먼저 솔리디티에서 에러가 발생하는 상황을 만들어보겠습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
uint8 n;
function error(uint8 _n) public {
n = _n - 1;
}
}
위 함수에 1 이상의 값을 입력값으로 주면 문제가 발생하지 않지만, 0을 입력값으로 줄 경우 uint8에 음수를 할당하는 상황이 발생하여 에러가 발생하게 됩니다. 이렇게 에러가 발생할 경우 아래와 같은 에러 메시지가 전송됩니다.
솔리디티에서는 assert, revert, require 에러가 존재하며, 각 에러 함수를 이용하여 위와 같은 에러를 인위적으로 발생시킬 수 있습니다.
assert 에러는 솔리디티 내부 문제를 처리하는 데 사용됩니다. 에러 발생에는 assert 함수가 사용되며, 이 경우 Panic 예외가 발생합니다. 위의 예시처럼 언더플로우가 발생하는 경우나 올바르지 않은 인덱스로 배열에 접근하는 경우 등이 여기에 해당합니다.
여기까지의 내용을 알고 있는 상태에서 위 에러 메시지를 보면 이상한 점을 찾을 수 있습니다. 내부 문제의 경우 assert 에러가 사용되어야 하지만 에러 메시지에는 revert라고 적혀있습니다. 이러한 일이 발생하는 이유는 솔리디티 버전 0.8 이후부터는 assert가 revert를 사용하기 때문입니다.
다음과 같이 assert(false)를 이용해 인위적으로 assert 에러를 발생시킬 수 있습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
uint8 public n;
function error(uint8 _n) public {
n = _n - 1;
assert(false);
}
}
위 코드를 실행해 보면 assert에러가 발생하는 것을 확인할 수 있습니다. 또한 n에 아무 값도 할당되어 있지 않는 것도 확인할 수 있습니다. 그 이유는 에러가 발생할 경우 지금까지의 변경 사항을 모두 취소하고 이전 상태로 되돌리기 때문입니다.
revert 에러는 내부적인 오류는 아니지만, 컨트랙트 배포자가 원하지 않았던 동작을 수행하려고 할 경우 발생시키는 에러입니다. revert의 경우 Error 예외가 발생합니다. Panic은 정수로된 에러 코드가 반환되지만, Error의 경우 문자열로 된 에러 메시지가 반환됩니다.
revert 에러는 다음과 같이 revert("msg") 형태로 발생시킬 수 있습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
uint8 public n;
function error(uint8 _n) public {
if (_n < 1 || 5 < _n ) revert("error message");
n = _n - 1;
}
}
위 코드를 실행했을 때 n에 1보다 작거나 5보다 큰 값을 입력한 경우 "error message"가 revert에러와 함께 반환되는 것을 확인할 수 있습니다.
마지막으로 require은 if문과 revert가 하나로 합쳐진 형태의 에러 함수입니다. require의 첫 번째 인자로 오류가 발생하지 않는 조건을 입력하고, 두 번째 인자로 에러 메시지를 넣습니다. 주의할 점은 조건식이 false일 경우에 오류가 발생한다는 점입니다. revert와 동일한 오류를 발생시키려면 require에서는 다음과 같이 입력해야 합니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
uint8 public n;
function error(uint8 _n) public {
require(1 <= _n && _n <= 5,"error message");
n = _n - 1;
}
}
만약 n이 1 이상 5 이하의 값이라면 위 조건식을 만족하므로 true를 반환합니다. 이 경우 오류가 발생하지 않습니다. 하지만 n이 1보다 작거나 5보다 큰 경우 위 조건식은 false를 반환하여 require에러가 발생하게 됩니다. 에러가 발생할 경우 반환되는 내용은 revert와 동일합니다.
2. try-catch
에러가 발생하여 코드 실행이 중단되고 실행된 내용이 취소되는 것을 막기 위해 try-catch를 사용할 수 있습니다. 다른 프로그래밍 언어에서 사용하는 try-catch와 유사하지만, try에 외부 함수를 사용하는 과정이 필요하다는 차이점이 있습니다. 다음과 같이 try 뒤에 있는 func함수를 실행한 뒤 함수 내부에서 에러가 발생할 경우 catch로 넘어가게 됩니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
event Log(string s);
uint8 public n;
function func(uint8 _n) public pure returns(uint8) {
return _n - 1;
}
function error(uint8 _n) public {
try this.func(_n) returns(uint8 r) {
n = r;
} catch {
emit Log("error");
n = 0;
}
}
}
오류를 발생시킬 경우 트랜잭션은 정상 처리되며, "error"가 로그에 저장되는 것을 확인할 수 있습니다.
catch Error와 catch Panic을 이용하면 에러를 보다 세부적으로 처리할 수 있습니다. 이전에 알아봤던 것처럼 catch Error는 require과 revert 에러를 처리하며, catch Panic은 assert 에러를 처리합니다. 가장 마지막 catch는 Error나 Panic이 아닌 에러를 처리하게 됩니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Err {
event Log(string s);
event Log2(uint i);
event Log3(bytes);
uint8 public n;
function func(uint8 _n) public pure returns(uint8) {
return _n - 1;
}
function error(uint8 _n) public {
try this.func(_n) returns(uint8 r) {
n = r;
} catch Error(string memory e) {
emit Log(e);
n = 0;
} catch Panic(uint e) {
emit Log2(e);
n = 0;
} catch (bytes memory e) {
emit Log3(e);
n = 0;
}
}
}
위 코드를 실행하여 error 함수에 0을 매개변수로 입력할 경우 assert 에러가 발생하여, catch Panic에 있는 Log2 이벤트가 실행됩니다. 실제로 결과를 확인해 보면 다음과 같습니다.
Panic 예외는 이전에 알아봤던 것처럼 에러 코드를 반환합니다. 위의 경우 반환된 에러 코드는 17이며, 16진수로 0x11입니다. 솔리디티 공식문서를 확인해 보면 해당 오류 코드는 언더플로우나 오버플로우 오류 코드인 것을 확인할 수 있습니다. 아래 공식문서에서 다른 종류의 오류 코드도 확인할 수 있습니다.
지금까지 솔리디티의 에러 처리에 대해 알아봤습니다. 감사합니다.
다음글
'이더리움 > 솔리디티' 카테고리의 다른 글
[솔리디티] 13. receive, fallback, delegatecall (0) | 2023.08.04 |
---|---|
[솔리디티] 12. Payable, Transfer, Send, Call (0) | 2023.08.02 |
[솔리디티] 10. 이벤트 (0) | 2023.07.27 |
[솔리디티] 9. 조건문, 반복문 (0) | 2023.07.26 |
[솔리디티] 8. 매핑, 구조체, 열거형 (0) | 2023.07.24 |