이번시간에
Modifier & Error Handling을 살펴보자
이 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()에 대해 알아보자
조건문을 간단하게 사용할 수 있는 방법이다.
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를 사용한다.
이도 require()와 거의 유사하다.
// 위 코드의 require()의 부분만 바꾸면
assert(msg.value != 0);
이렇게 쓸 수 있다. assert()는 뒤에 Error문을 따로 출력하지 않는다.
조건 체크할 때 에러가 발견되면 이 스마트 컨트랙트에서
지금까지 실행된 Tx들을 롤백시키고, 사용된 가스 수수료를 돌려준다.
이는 require()와 다르게, 조건문을 사용하지 않는다.
그래서 이 함수를 쓰는 경우는, 이미 에러가 있는 상태를 알고 그 곳에서 사용한다.
예를들어,
if (msg.value < minPrice) {
revert("msg.value must not be zero");
}
이런식이다.
보낸 이더양이 최소값 보다 작으면 전송이 안되기 때문에
이럴 때는 바로 revert()를 통해 오류를 생성한다.
개발자는 try~catch처럼 중간중간 오류를 찾아야하기 때문에
require(), revert()를 사용한다. 에러문구를 포함하고 있기 때문이다.
assert()는 개발 테스트를 진행할 때 에러문구를 보지 않아도
개발자라면 이 부분에서 오류가 났다 등을 알 수 있어 이 함수 또한 사용한다.
하지만 이 모두를 통틀어 require()를 가장 많이 사용한다.
흔히 알고있는 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 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;
}
}
에러문구 선언은 컨트랙트 안팎 상관없다.
이런 식으로 에러문구를 따로 뺀 후 사용할 수도 있다.
함수를 사용하다가 특이한 경우가 분명히 발생한다.
한 예로, ETH를 전송해야 하는데 실수로 사용자가 ERC-20토큰을 전송했다.
근데 그 컨트랙트 구조가 한 번 잘못 들어오면 나오지 못하게 형성돼있으면
그 사용자는 그 돈을 잃는다.
그래서 이런 점들을 방지하고자 미리 코드 앞부분에서 체크를 하고
함수를 진행한다.
fallback() external payable {
revert();
}
recieve() external payable {
revert();
}
이런 식으로 먼저 발견되면 revert()를 통해 유저들의 실수를 방지할 수 있다.
두 함수의 사용 경우는
fallback() -> 사용자가 존재하지 않는 함수를 호출할 때, msg.data가 비어있지 않은 경우
receive() -> 사용자가 ETH를 전송할 때, msg.data가 빈 경우
두 케이스를 잘 파악하고 방어적인 코드를 작성할 수 있다.
이번 포스트는 여기까지다.
끝