함수 변경자

(Function Modifier)

 

 

Solidity 언어에는 다른 언어에서는 볼 수 없는 Modifier라는 것이 있다. Modifier는 함수의 동작을 변경시키기 위해 사용된다. 이 말의 의미는 Modifier를 사용하면 함수를 실행시키기 전과 실행시킨 후에 특정한 기능을 할 수 있도록 만들 수가 있다는 것이다. 따라서, Modifier를 사용하면 사전에 어떤 조건에 부합되는지 확인이 가능하다. Modifier는 스마트 컨트랙트(Smart Contract)의 상속이 가능한 property이기 때문에, overriding해서 사용이 가능하다. 예를 들면, 함수를 실행시키기 전에, 스마트 컨트랙트를 배포(deploy)한 사람의 계정과 동일한 계정인지를 확인할 수도 있다.

Modifiers can be used to easily change the behaviour of functions. For example, they can automatically check a condition prior to executing the function. Modifiers are inheritable properties of contracts and may be overridden by derived contracts.



Modifier 사용방법

Modifier는 두가지 단계로 사용된다.

  1. Modifier 정의

  2. Modifier 를 함수에 적용

pragma solidity >=0.5.0 <0.7.0;
contract joker {
   uint public data = 0;   

   modifier check { // Modifier 정의
       data++;
       _;
   }
   
   function getWithCheck() check public { // Modifier를 함수에 적용
   
   }
}



Modifier _; (underscore)

Modifier를 작성할 때, “_”를 사용하게 된다. “_” 는 함수 실행하는 시점을 나타내는 것이다. 따라서, 함수를 실행시키기 위해 행동을 제한하려는 어떤 코드를 추가하기를 원한다면 modifier 안에 “_” 를 기준으로 작성하면 된다. “_” 이해를 위해 아래 작성된 예제 코드를 살펴봅시다.

pragma solidity >=0.5.0 <0.7.0;
contract joker {
   uint public mutex = 0;
   uint public exeCnt = 0;   

   modifier check {
       mutex++;
       _;
       mutex--;
   }
   function getWithCheck() check public {
       if(mutex==1)
           exeCnt=exeCnt+1;
   }
}


contract caller {
   event log(uint data1, uint data2);   

   function func() public {
       joker k = new joker();   

       emit log(k.mutex(), k.exeCnt()); // (event log) data1: 0, data2: 0
       k.getWithCheck();
       emit log(k.mutex(), k.exeCnt()); // (event log) data1: 0, data2: 1
       k.getWithCheck();
       emit log(k.mutex(), k.exeCnt()); // (event log) data1: 0, data2: 2
   }
}

작성된 코드를 살펴보면, getWithCheck함수가 check라는 modifier를 사용하고 있다. 이 함수가 호출될 때, 함수와 modifier의 처리는 “_”를 기준으로 아래 그림처럼 동작하게 된다.

[ modifier, "_" 사용방법 ]

위 코드는 getWithCheck 함수에 check라는 modifier를 추가시켰다. check는 함수를 호출시키기 전에 mutex값을 1증가시키고, 호출완료된 후에는 mutex값을 1감소시키는 modifier이다. 위 코드는 check modifier를 getWithCheck함수에 추가했기 때문에, getWithCheck 함수를 실행시키기 전에 mutex라는 값을 증가시키고, getWithCheck 함수에 있는 내용을 모두 실행하고, getWithCheck 함수가 종료된 후에는 mutex라는 값을 감소시키는 것이다.  참고로, modifier를 사용하지 않고, 함수하나를 추가해서 위와 동일한 처리를 할 수 있도록 작성할 수도 있지만, 여기서는 modifier의 처리과정을 보기 위해 위처럼 작성한 것이다.

 

※ event log와 emit log는 solidity의 log함수이다. 예제 코드에서는 mutex와 count의 상태변화를 확인하기 위해 사용했다.

 

 

Modifier 이해

modifier의 이해하기 위해 작성한 예제 코드이다. 참고로, 아래 코드에는 아직 설명하지 않은 address, payable, require, msg.sender, selfdestruct, mapping 등의 키워드들이 사용되고 있지만, 실제의 코드에서는 modifier가 어떤식으로 사용되는지 살펴보면서 modifier를 이해하기 위해 작성했으므로, modifier를 중점으로 살펴본다. 예제 코드에는 각각 아래처럼 역할을 수행하는 5개의 계약서가 있다.

 

  • owned : 계약 배포자 관리 계약서

  • mortal : 계약을 소멸시키는 계약서

  • priced : 계약에 사용되는 비용을 제한하는 계약서

  • Register : 등록고객 비용 관리 계약서

  • Mutex : 계약은 한번에 한명만 할 수 있도록 제한하는 계약서

 

pragma solidity >=0.5.0 <0.7.0;
contract owned {
   address payable owner;  

   constructor() public {
       owner = msg.sender;
   }

   modifier onlyOwner {  // 계약서 배포자 계정만 실행할 수 있도록 제한하는 Modifier1 정의
       require(
           msg.sender == owner, "Only owner can call this function."
       );
       _;
   }
}

contract mortal is owned {
   function close() public onlyOwner { // 계약서 배포자 실행 제한 Modifier1 적용
       selfdestruct(owner);
   }
}

contract priced {    
   modifier costs(uint price) { // 계약하려는 비용을 제한하는 Modifier2 정의
       if (msg.value >= price) {
           _;
       }
   }
}

contract Register is priced, owned {
   mapping (address => bool) registeredAddresses;
   uint price;

   constructor(uint initialPrice) public {
       price = initialPrice;
   }

   function register() public payable costs(price) { // 계약하려는 비용 제한 Modifier2 적용
       registeredAddresses[msg.sender] = true;
   }

   function changePrice(uint _price) public onlyOwner { // 계약서 배포자 실행 제한 Modifier1 적용
       price = _price;
   }
}

contract Mutex {
   bool locked;   

   modifier noReentrancy() { // 함수 중복진입을 제한하는 Modifier3 정의
       require(!locked, "Reentrant call.");
       locked = true;
       _;
       locked = false;
   }

   function f() public noReentrancy returns (uint) { // 함수 중복진입을 제한하는 Modifier3 적용
       (bool success,) = msg.sender.call("");
       require(success);
       return 7;
   }
}



Modifier 버전별 특징

Solidity 언어가 계속해서 출시(Release)되고 있다. 각 버전마다 사용법이 조금씩 다른 부분이 있는데, 함수변경자(Modifier) 또한 차이가 있으므로 최신버전 뿐만 아니라 이전 버전도 알아두는 것이 좋다.

 

solidity version 0.4.0 이상

_; (underscore + semicolon)

pragma solidity ^0.4.0;
contract joker {
   uint public data = 0;  
   modifier check {
       data++;
       _;
   }
}

 

solidity version 0.4.0 이전

_(underscore)

// 0.3.6-3fc68da5 버전으로 컴파일
contract joker {
   uint public data = 0;  

   modifier check {
       data++;
       _
   }
}