low-level call은 보통 다음과 같이 작성된다.
(bool success, bytes memory data) = addr.call("");
require(success);
data
에는 bytes
의 형태로 return된 값이 저장되게 된다.
근데 만약 revert가 나면 어떻게 될까?
contract Caller {
function caller(address _target) external {
(bool success, bytes memory data) = _target.call(abi.encodeWithSignature("reverter()"));
console.log(success);
console.log(string(data));
}
}
contract Callee {
function reverter() external returns(string memory) {
return "Return Value";
}
}
true
Return Value
contract Caller {
function caller(address _target) external {
(bool success, bytes memory data) = _target.call(abi.encodeWithSignature("reverter()"));
console.log(success);
console.logBytes(data);
}
}
contract Callee {
function reverter() external returns(string memory) {
return "Return Value";
}
}
false
0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e526576657274206d657373616765000000000000000000000000000000000000
뭔가 return이 되기는 한다. 한번 분석해보자.
08c379a0 // bytes4(keccak256("Error(string)"))
0000000000000000000000000000000000000000000000000000000000000020 // location
000000000000000000000000000000000000000000000000000000000000000e // length
526576657274206d657373616765000000000000000000000000000000000000 // "Revert message"
bytes 혹은 string 타입의 경우 calldata가 저렇게 3등분으로 나누어지게 된다.
(bool success, bytes memory data) = address(target).delegatecall(callee);
if (success == false) {
assembly {
let ptr := mload(0x40) // memory pointer
let size := returndatasize() // data 길이
returndatacopy(ptr, 0, size) // ptr에 data 길이 0~size를 store
revert(ptr, size) // ptr부터 size만큼 길이를 revert
}
}
return result;
이렇게 low-level call에서 발생한 revert sring을 받아와서,
high-level call에서의 revert처럼 만들 수 있다.
(하지만 보통은 그냥 require(success, "Call failed")
와 같이 처리한다)
assembly에 대해서는 https://www.evm.codes/ 를 참고하면 자세히 알 수 있다.