유튜브를 통해, 쉽고 간편하게 이해 해보아요!
구독/좋아요 해주셔서 감사합니다 :) !!
안녕하세요,
오늘은 try/catch 문을 알아보도록 하겠습니다.
try/catch는 0.6 버전이후로 소개되었답니다.
try/catch 문은 기존에 소개되었던 assert/revert/require와 다르게, 프로그램을 죽는걸 방지 해주어요.
예를들어 기존의 에러 핸들러는, 에러를 발생시켜주고 에러메세지 띄어주고 프로그램을 끝냈지만,
try/catch의 경우는 try 문에서 에러를 잡아 catch 문으로 넘긴후 그 안에서 저희가 에러를 핸들링해주는 코드를 짤 수가 있습니다.
솔리디티의 try/catch 문에는 다른언어가 없는 몇가지 특징이 있습니다.
1. try/catch문 안에서, assert/revert/require을 통해 에러가 난다면 catch는 에러를 잡지를 못하고, 개발자가 의도한지 알고 정상적으로 try/catch만 끝낸다
try/catch문 밖에서 assert/revert/require을 통해 에러가 난다면 catch는 에러를 잡고, 에러를 핸들할수 있다
2. catch 에는 3 가지 종류가 있습니다.
catch Error(string memory reason) { ... } : revert 나 require을 통해 생성된 에러는 이 catch 에 잡힌답니다.
catch Panic(uint errorCode) { ... } : 26강에서 봤던 Paninc이네여, assert 를 통해 생선된 에러가 날 때 이 catch에 잡혀요. 에러들은, division zero(나누기 0 ), 오버플로우, 배열에 없는 인덱스 접근시 등등이 있답니다.
errorCode 는 솔리디티에서 정의 Panic 에러 별로 나온답니다.
- 0x00: Used for generic compiler inserted panics.
- 0x01: If you call assert with an argument that evaluates to false.
- 0x11: If an arithmetic operation results in underflow or overflow outside of an unchecked { ... } block.
- 0x12; If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0).
- 0x21: If you convert a value that is too big or negative into an enum type.
- 0x22: If you access a storage byte array that is incorrectly encoded.
- 0x31: If you call .pop() on an empty array.
- 0x32: If you access an array, bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
- 0x41: If you allocate too much memory or create an array that is too large.
- 0x51: If you call a zero-initialized variable of internal function type.
예를들어, 나누기가 0 이 된다면 0x12(=18) errorCode 를 리턴해요.
한가지더, Panic은 0.8.0 버전에는 없고 0.8.1 버전 부터 있습니다.
catch(bytesmemorylowLevelData){...} : 이 catch는 로우 레벨에러를 잡습니다.
이렇게 3가지 catch 가 있는데, 쓰기 귀찮을수도 있잖아요.
그럴때는 그냥 catch { ... } 쓰셔서 그안에 에러를 핸들링하는 코드를 넣을 수 있답니다.
자 인제 try/catch 문을 배워 보도록 해볼게요.
사실 try/catch 문은
외부 스마트 컨트랙을 함수를 부를때 : 다른 스마트 컨트랙을 인스턴스화 하여서, try/catch문이 있는 스마트 컨트랙의 함수를 불러와서 사용.
외부 스마트 컨트랙을 생성 할 때 : 다른 스마트컨트랙을 인스턴스화 생성 할 때 씀
이 경우들로 도입되었다고 합니다.
그리고 추가적으로 , try/catch문이 있는 스마트컨트랙 내에서 함수를 부를때도 try/catch를 쓸 수 있습니다.
스마트컨트랙 내의 함수 부를를때 this 라는 글로벌 번수를 사용하여 try/catch를 이용할 수 있습니다.
1. 외부 스마트 컨트랙을 함수를 부를때 try/catch
2. 외부 스마트 컨트랙을 생성 할 때 try/catch
3. 내부 스마트 컨트랙에서 함수를 부를때 try/catch
그래서, 저희는 총 3가지;예제를 통해서, 어떻게 try/catch를 써야하는지 볼게요
1. 외부 스마트 컨트랙을 함수를 부를때 try/catch
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract math{
function division(uint256 _num1,uint256 _num2) public pure returns (uint256){
require(_num1<10,"num1 shoud not be more than 10");
return _num1/_num2;
}
}
contract runner{
event catchErr(string _name,string _err);
event catchPanic(string _name,uint256 _err);
event catchLowLevelErr(string _name,bytes _err);
math public mathInstance = new math() ;
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
try mathInstance.division(_num1, _num2) 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);
}
}
}
저희는 두가지의 스마트 컨트랙이 있습니다.
math 라는 스마트 컨트랙은, division 이라는 함수가 있습니다.
division은 두개의 uint256 함수를 받고 나눈답니다.
runner 라는 스마트컨트랙이 있습니다.
이 runner는 Math 스마트컨트랙의 division 함수를 불러서 실행시킨답니다.
물론 divsion을 불러서 실행할때는 try/catch 문을 사용해야겠죠.
자 그러면, runner에서 math 스마트컨트랙의 division 함수 쓰려면,
인스턴스를 먼저해줘야겠죠.
math public mathInstance = new math() ;
이렇게 해주었습니다.
이로써 저희는 runner 스마트 컨트랙 내에서 math의 division함수를 사용 할 수 있겠네요.
본격적으로 try/catch 문을 만들기전에 저는 먼저 3가지의 catch용 이벤트 3개를 만들었어요.
에러가 나면, 이벤트가 출력이 되어 어떤 캐치에 에러가 걸렸는지 알 수가 있죠.
event catchErr(string _name,string _err);
event catchPanic(string _name,uint256 _err);
event catchLowLevelErr(string _name,bytes _err);
자 인제 본격적으로 try/catch를 사용하여 division을 불러 볼게요.
먼저 try/catch를 사용하기에 함수를 만들어야해요.
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
....
}
그래서 저는 playTryCatch(uint256 _num1, uint256 _num2) 만들었습니다.
일단 playTryCatch는 두개의 uint256 _num1 과 _num2를 받아야해요.
왜냐하면, math 컨트랙의 division 에 두개의 uint256 _num1 과 _num2를 받아서 넣어야 하기 때문이죠.
그리고 저희는 playTryCatch 함수에 두개의 returns(uint256,bool) 값이 있답니다.
uint256은 division 함수가 정상적으로 작동했을때 나온 결과의 값 (두개의 파라메터가 나눈 값) 그리고 bool은 성공 여부를 나타 냅니다.
자 인제 try의 형태를 볼가요
try 원하는 함수와 return 값 표시 {
}
이에 적응하면 밑에와 같은 코드가 생깁니다.
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
try mathInstance.division(_num1, _num2) returns(uint256 value){
return(value,true);
}
}
try 다음에 함수를 써져야하는데, division 함수를 쓰려면, math의 instance를 통해서 접근해야겠죠.
그러니, mathInstance.division(_num1, _num2) 가 나옵니다.
인제 저희는 division 의 리턴값을 returns(uint256 value) 이런식으로 value로 명시하고 들고 올 수 있습니다.
returns(uint256 value) 에 다소 생소 할 수 있는데, 이 부분에대해서 다다음강의에 다뤄볼게요.
그러면 try 문이 정상적으로 작동한다면,
return(value,true); 을 리턴합니다.
만약에 정상적으로 작동안할시 3가지 catch 문을 붙이면되겠죠.
function playTryCatch(uint256 _num1, uint256 _num2) public returns(uint256,bool){
try mathInstance.division(_num1, _num2) 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);
}
}
이렇게 된답니다.
다음 강의에서
2. 외부 스마트 컨트랙을 생성 할 때 try/catch
3. 내부 스마트 컨트랙에서 함수를 부를때 try/catch
에 대해 설명할게요 .
영상 꼭 참고 하셔야 이해 잘갑니다.