솔리디티에서는 에러 핸들링을 위해 require 라는 함수를 사용한다.
require 함수로 문제가 되거나 의도하지 않은 상황을 제어할 수 있다.
require(bool, string memory) 의 input 만 지원한다.
다음의 예시 코드를 보자.
uint a = 1;
function require1() public {
bool c;
a = 5;
require(c, "error");
}
예시 코드에서는, 정수형 상태변수 a 를 1로 설정했다.
그리고 함수 require1 을 실행시키면 어떻게 될까?
일단 bool 값은 0 이므로 false 이고, a 를 5로 바꿔주었다.
그리고 require 함수가 c 가 true 인지 판단하는데,
c 는 false 이므로 "error" .
"error" 라는 이유를 반환하며 revert 되었다고 알려준다.
그렇다면, 직접 상태변수를 수정하는 것 말고 다른 함수를 호출해서 사용하는 경우는 어떻게 될까?
function getA() public view returns(uint) {
return a;
}
function setAfive() public {
a = 5;
}
function require2() public {
bool c;
setAfive();
require(c, "error");
}
이렇게 require2 함수에서 setAfive 함수를 호출해서 사용하는데,
"error" 라는 함수를 반환하며 initial state 로 돌아가게 된다.
결론은, 상태변수를 직접 변경하거나 다른 함수를 호출하는 경우에도 되돌린다.
require 의 조건을 2개를 만든다거나,
if 문안에 require 를 사용하는 응용도 가능하다.
// require 조건 2개
function Require3(uint _n) public pure returns(bool) {
require(_n%3==0 && _n>12, "Nope");
return true;
}
// if문 안의 require
function Require4(uint _a) public pure returns(uint) {
if(_a%3==0) {
require(_a%3!=0, "nope");
} else if(_a%3==1) {
return _a%3;
} else {
return _a%3;
}
}
constructor 는 생성자로, 컨트랙트를 배포 하자마자 실행되는 특별한 함수이다.
쉽게 생각하면 컨트랙트의 배포와 동시에 초깃값을 설정하는 함수라고 볼 수 있겠다.
input 값은 없으며, constructor 에는 if, payable, require 등의 문법을 사용할 수 있다.
constructor() payable {
payable(this).transfer(msg.value); // 배포할 때 msg.value 만큼 contract 에게 입금
owner = payable(msg.sender); // 배포하는 지갑 주소가 바로 owner 로 설정
}
위와 같이 owner 라는 변수를 컨트랙의 배포와 동시에 선언하면서, owner 만이 이 컨트랙트를 관리하게 만들 수 있다.
아래 코드처럼 require 함수와 같이 사용해, 이 함수의 실행자(msg.sender)가 owner(컨트랙트의 배포자) 일때만 두번째 줄의 코드를 실행한다.
function withdraw() public {
require(msg.sender == owner, "owner only");
owner.transfer(address(this).balance);
}
여기서 헷갈리는 부분이 있었는데.
그래서 construct 함수를 이용해서 컨트랙트를 배포하자마자 컨트랙트에 돈을 보낼 수 있었다.
modifier 는 에러핸들러 require 와 같이 쓰이는 경우가 많다. 그리고 다른 함수에 들어가 이 modifier 를 실행이 가능하다.
modifier 는 매개변수가 있어도, 없어도 문제가 없다.
_; 는 다른 함수가 실행될 곳을 지정해주는데, 아래 코드에서 require 의 윗줄이나 아랫줄, 또는 두줄에 여러번 혹은 모두 쓰일 수도 있다.
다른 함수에서 modifier 를 사용할 땐, 함수의 속성을 설정하는 부분에 사용할 modifier 의 이름을 입력해주면 된다.
modifier 이름(_매개변수){
require();
_;
}
function a() modifier 이름 public {
b;
}
_; 여기에서 함수가 실행된다고 했는데, 어떻게 실행되는지 해보았다.
a 를 정수 5의 상태변수로 선언하고
require 함수의 위 아래 함수를 두 번씩 실행하는 modifier 를 선언했다.
그리고 plusA() 함수에서 checkA modifier 를 가져와 실행한다.
contract checkModifier{
uint a = 5;
modifier checkA {
a++;
a++;
require(a >= 5, "please check A value.");
a++;
a++;
}
function plusA() checkA public {
a++;
}
function getA() public view returns(uint){
return a;
}
}
plusA() 함수를 실행한 결과는 9이다.
함수가 실행되는 순서를 보면 다음과 같다.
- getA() 실행
- checkA 첫번째(plusA) 함수 실행, a=6
- checkA 두번째(plusA) 함수 실행, a=7
- require. a >= 5 확인
- checkA 세번째(plusA) 함수 실행, a=8
- checkA 네번째(plusA) 함수 실행, a=9
- a 값 9 반환, 종료.
조금 더, 직관적으로 코드를 해석해보면 이렇게도 생각할 수 있다.
uint a=5;
modifier checkA {
a++;
a++;
require(a >= 5, "please check A value.");
a++;
a++;
}
만약 함수를 실행하는 중, require 를 통과하지 못한다면 어떻게 될까?
원래 require 를 통과하지 못한다면, 모든 값은 함수를 실행하기 이전인 initial state 로 돌아가게 된다. 가스비도 소모되지 않는다.
modifier 를 이용한 다른 함수의 안에서도 마찬가지일까?
이렇게 a를 6으로 선언하고, minusA 를 실행해보았다.
contract checkModifier{
uint a = 6;
modifier checkA {
_;
require(a > 5, "please check A value.");
_;
}
function minusA() checkA public {
a--;
}
function getA() public view returns(uint){
return a;
}
}
아래 사진처럼 경고 문구가 뜬다. require 조건을 통과하지 못했고, 값은 initial state 상태로 돌아간다고 되어있다.
modifier checkA {
require(a > 5, "please check A value.");
_;
}
이렇게 require 윗줄을 지우고 실행시키면, 정상적으로 실행된다.
그리고 minusA 함수가 실행되면서 a 값이 5로 바뀌게 된다.
이렇게 require 를 약간 우회하는 느낌으로 사용할 수도 있을 것 같다.
이렇게 modifier 함수 사용 방법을 알아보았고, 응용에는 require 와 함께 에러핸들링 할때가 제일 적합할 것 같다.