반응형

web3 생태계에서 다른 컨트랙트의 함수를 호출하는 과정에서 우리는 어떨 때는 직접 호출을 하는 경우도 있고, 저수준 호출을 하는 경우도 있다. 

A라는 컨트랙트에 있는 "func1(uint _number)" 함수를 호출한다고 가정해보자.

그러면 우리는, 두 가지 호출 방식이 있을 것이다. 

 

1. A.func1(123) - 직접호출 방식

2. A.call{}(abi.encodeWithSignature("func1(uint)")) - 저수준 호출 방식

 

이 두 경우의 차이점, 그리고 호출 실패 시 어떻게 핸들링하는지를 알아보고자 한다.


1. 직접호출 방식

직접 호출 방식은 가장 기본적인 방법으로, 호출하려는 함수의 이름과 인수를 사용하여 함수를 호출한다. 이 방식은 Solidity 컴파일러에 의해 함수의 존재와 시그니처가 검증되므로 타입 안정성을 제공한다. 

호출 방법은 위에서도 썼듯,

A.func1(123);

이다. 

이 방식의 장점이라고 한다면 가독성 및 안정성이라고 할 수 있을 것이다. 컴파일타임에 함수의 존재를 확인하고, 인자타입이 맞지 않을 경우 컴파일 에러를 발생시킨다. 

또한, 리턴 값을 받는 것도 굉장히 직관적이라고 할 수 있다. 

만약 "func1"함수가 uint형을 return 한다면

uint res = A.func1(123);

으로 리턴을 받을 수 있다.

함수 호출 실패 시

함수가 존재하지 않거나, 인자가 잘못되거나 했을 경우, 컴파일 시점에 에러를 발생시키지만, 컴파일 시점에 문제가 없는 modifier, revert, require 등으로 인해 함수 호출이 실패했을 경우, 전체 트랜잭션이 실패하고 롤백되게 된다.

2. 저수준 호출

저수준의 'call'함수를 사용하게 되면 더욱 유연하지만 복잡하고 에러가 발생하기 쉬운 방식의 호출이 된다.

'call'은 ABI 인코딩을 통해 어떠한 함수든 호출할 수 있고, 이 때 함수의 존재여부나 시그니처 등은 컴파일이 아닌, 런타임에 검증된다.

(bool success, bytes memory data) = A.call{value: msg.value, gas: 5000}(abi.encodeWithSignature("func1(uint)", 123));

이런식으로 사용을 하게 된다.

success 의 경우, 함수 호출이 성공적이었는지 여부를 나타내며,

data 의 경우, 호출된 함수로부터 반환된 return 값이다.

이렇게 반환된 return값은, abi.decode() 메서드를 통해 적절하게 처리할 수 있다.

(bool success, bytes memory data) = A.call(abi.encodeWithSignature("func1(uint)", 123));
require(success, "Call failed");

uint result;
if (data.length > 0) {  // 함수에서 데이터가 실제로 반환되었는지 확인
    result = abi.decode(data, (uint));
}

이런식으로 사용할 수 있다.

함수 호출 실패 시

저수준 함수 호출에는, 에러가 발생한다고 해도 롤백이 되지 않는다. 대신, success플래그를 통해 성공 여부를 반환하게 된다. 

(bool success, bytes memory data) = A.call{gas: 5000}(abi.encodeWithSignature("func1(uint)", 123));
if (!success) {
    // 에러 핸들링 로직
    revert("Function call failed.");
}

그래서, 함수 호출에 실패했을 경우, 위와 같이 직접 핸들링을 해주어야 한다. 다소 불편할 수 있지만, 유연성을 제공한다는 점에서 장점이 있다고 할 수 있다.

반응형

+ Recent posts