[솔리디티] 기초 문법 - 4

근상·2025년 1월 7일

솔리디티 기초

목록 보기
5/7

이번시간에

Modifier & Error Handling을 살펴보자


Modifier

이 Modifier는 간단히 중복되는 코드를 통일시키기 위함이다.

예를들어

contarct ModifierExample {

	uint public minPrice = 10;
    
    // 최소 가격이 10인데
    mapping(address => uint) public exList;
    
    function fn1() public payable{
    	require(msg.value > minPrice);
        exList[msg.sender] = msg.value;
    }
    
    function fn2() public payable{
    	require(msg.value > minPrice);
        exList[msg.sender] = msg.value;
    }
}

이런식의 코드가 있다고 치자.

따로 선언한 두 함수의 내용이 겹친다. 이 때 Modifier를 사용할 수 있다.

그래서 Modifier를 사용한 예시를 보자.

contarct ModifierExample {

	uint public minPrice = 10; // 최소 가격이 10인데
    
    mapping(address => uint) public exList;
    
    modifier checkMinPrice() {
    	require(msg.value > minPrice);
    	_; // 위 require속 조건이 맞다면 이어서 실행해라
    }
    
    function fn1() public payable checkMinPrice{
        exList[msg.sender] = msg.value;
    }
    
    function fn2() public payable checkMinPrice{
        exList[msg.sender] = msg.value;
    }
}

이런 식으로 줄여나갈 수 있다.
그 다음 방금 사용한 require()에 대해 알아보자


Require

조건문을 간단하게 사용할 수 있는 방법이다.

contract RequireExample {
	mapping(address => uint) public exList;
    
    function order() public payable {
    	require(msg.value != 0, "Msg.value must not be zero");
        exList[msg.sender] = msg.value;
 	}
}

이런 식으로 require( A , B);
A에 조건문을 넣고 만약 조건에 맞지 않다면 B(에러문)를 출력한다.

사실 If문을 써도 상관은 없다. 하지만 위의 require부분을 if문으로 써보면

if (msg.value != 0) {
	exList[msg.sender] = msg.value;
} else {
	revert("msgvalue must not be zero");
}

코드가 길어진다
그래서 솔리디티에서 require를 사용한다.


Assert

이도 require()와 거의 유사하다.

// 위 코드의 require()의 부분만 바꾸면
assert(msg.value != 0);

이렇게 쓸 수 있다. assert()는 뒤에 Error문을 따로 출력하지 않는다.
조건 체크할 때 에러가 발견되면 이 스마트 컨트랙트에서
지금까지 실행된 Tx들을 롤백시키고, 사용된 가스 수수료를 돌려준다.


Revert

이는 require()와 다르게, 조건문을 사용하지 않는다.
그래서 이 함수를 쓰는 경우는, 이미 에러가 있는 상태를 알고 그 곳에서 사용한다.

예를들어,

if (msg.value < minPrice) {
	revert("msg.value must not be zero");
}

이런식이다.
보낸 이더양이 최소값 보다 작으면 전송이 안되기 때문에
이럴 때는 바로 revert()를 통해 오류를 생성한다.

개발자는 try~catch처럼 중간중간 오류를 찾아야하기 때문에
require(), revert()를 사용한다. 에러문구를 포함하고 있기 때문이다.

assert()는 개발 테스트를 진행할 때 에러문구를 보지 않아도
개발자라면 이 부분에서 오류가 났다 등을 알 수 있어 이 함수 또한 사용한다.

하지만 이 모두를 통틀어 require()를 가장 많이 사용한다.


Try ~ Catch

흔히 알고있는 try~catch지만 기능은 같게 되지만,
사용되는 경우가 일반적이지 않다.

내부는 안되고 외부에 있는 다른 컨트랙트를 호출할 때 사용할 수 있다.

예를들어

contract Math{
    function plusData(uint a, uint b) external pure returns(uint) {
        return a + b;
    }
}

contract TryCatchExample {
    Math math = new Math(); // 외부 컨트랙트 불러오기
    
     function callTheContract(address to) external payable{
       (math).plusData(6, 4);
       
        try math.plusData(6, 4) returns(uint result){
            require(msg.value >= result,"You must send more Ether");
            
           // msg.sender.call{value: result}(tx.origin); 
          } catch {
              revert();
         }
     } 

}

이런 식으로 컨트랙트 내부에서는 사용할 수 없다.


Error코드 다른 방법

여러 상황들이 있을텐데
에러 코드를 간단하게 짜는 것을 한 번 보자

error ZeroCheck();

contract Require {

	function fn1() public payable {
    
    	if (msg.value < minPrice) {
    		revert ZeroChceck);
		}
    	exList[msg.sender] = msg.value;
    }
	
    function fn2() public payable {
    
    	if (msg.value < minPrice) {
    		revert ZeroChceck);
		}
    	exList[msg.sender] = msg.value;
    }
}

에러문구 선언은 컨트랙트 안팎 상관없다.
이런 식으로 에러문구를 따로 뺀 후 사용할 수도 있다.


fallback() & receive()

함수를 사용하다가 특이한 경우가 분명히 발생한다.

한 예로, ETH를 전송해야 하는데 실수로 사용자가 ERC-20토큰을 전송했다.
근데 그 컨트랙트 구조가 한 번 잘못 들어오면 나오지 못하게 형성돼있으면
그 사용자는 그 돈을 잃는다.

그래서 이런 점들을 방지하고자 미리 코드 앞부분에서 체크를 하고
함수를 진행한다.

fallback() external payable {
	revert();
}

recieve() external payable {
	revert();
}

이런 식으로 먼저 발견되면 revert()를 통해 유저들의 실수를 방지할 수 있다.

두 함수의 사용 경우는

fallback() -> 사용자가 존재하지 않는 함수를 호출할 때, msg.data가 비어있지 않은 경우
receive() -> 사용자가 ETH를 전송할 때, msg.data가 빈 경우

두 케이스를 잘 파악하고 방어적인 코드를 작성할 수 있다.

이번 포스트는 여기까지다.


시리즈

  1. 값 자료형
  2. 참조 자료형
  3. 블록 & 트랜잭션
  4. 현재
  5. 컨트랙트 상속
  6. 컨트랙트 호출

0개의 댓글