상세 컨텐츠

본문 제목

솔리디티 강좌 27강 - 에러 핸들러 3 try/catch 1

솔리디티 깨부수기 - 기본

by D_One 2021. 9. 26. 13:21

본문


유튜브를 통해, 쉽고 간편하게 이해 해보아요!

https://youtu.be/8amwmk-Gjro

구독/좋아요 해주셔서 감사합니다 :) !!


 

 

안녕하세요, 

 

오늘은 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 에러 별로 나온답니다. 

  1. 0x00: Used for generic compiler inserted panics.
  2. 0x01: If you call assert with an argument that evaluates to false.
  3. 0x11: If an arithmetic operation results in underflow or overflow outside of an unchecked { ... } block.
  4. 0x12; If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0).
  5. 0x21: If you convert a value that is too big or negative into an enum type.
  6. 0x22: If you access a storage byte array that is incorrectly encoded.
  7. 0x31: If you call .pop() on an empty array.
  8. 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).
  9. 0x41: If you allocate too much memory or create an array that is too large.
  10. 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

에 대해 설명할게요 .

 

영상 꼭 참고 하셔야 이해 잘갑니다.

관련글 더보기