티스토리 뷰
calling another contract : interface vs call vs delegate call
_April 2022. 8. 29. 11:45솔리디티 컨트랙트에서 다른 컨트랙트를 부르는방식은 여러가지가 있다.
interface, call, delegate call 에 대해 살펴보자. (static call은 일단 제외)
1) 코드상(사용방법상) 차이
2) 소모되는 가스 unit 차이
3) Low / High level function
4) 최종 컨트랙트의 msg. sender, 데이터 저장위치
(packing, unpacking 때문에 한가지 contract 사용보단 무조건 비쌈)
이 세가지에 대해 정리, 테스트해본다.
1 코드상(사용방법상) 차이
(0) 불릴 코드 (Callee)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract Callee {
uint public value1;
uint public value2;
struct template {
uint256 tCount;
uint256 tValue;
}
mapping(uint256 => template) templateList;
function setV1(uint _value1) external returns (uint) {
value1 = _value1;
return value1;
}
function setV1andV2(uint _value1, uint _value2) external payable returns (uint, uint) {
value1 = _value1;
value2 = _value2;
return (value1, value2);
}
function setTemplate(uint256 idx , template memory _template) external returns (bool) {
templateList[idx] = _template;
return true;
}
function setTemplateForJS(uint256 idx , uint256 _val1, uint256 _val2) external returns (bool) {
template memory _template = template({
tCount: _val1,
tValue: _val2
});
templateList[idx] = _template;
return true;
}
function getTemplate(uint256 idx) external view returns (template memory) {
return templateList[idx];
}
}
다른 컨트랙트에서 불릴수도있고, js 에서도 바로 불릴수 있는 코드이다(setTemplateForJS)
(1) interface
// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "hardhat/console.sol";
interface ICallee {
struct template {
uint256 tCount;
uint256 tValue;
}
function setV1(uint256 _value1) external returns (uint256);
function setV1andV2(uint256 _value1, uint256 _value2)
external
payable
returns (uint256, uint256);
function setTemplate(uint256 idx, template memory _template)
external
returns (bool);
function getTemplate(uint256 idx) external view returns (template memory);
}
contract Caller_interface {
address internal _calleeAddr;
constructor(address _calleeAddr_) {
_calleeAddr = _calleeAddr_;
}
function setV1(uint256 _v1) public {
uint256 x = ICallee(_calleeAddr).setV1(_v1);
console.log("setV1: %s", x);
}
function setV1andV2(uint256 _v1, uint256 _v2) public {
(uint256 x, uint256 y) = ICallee(_calleeAddr).setV1andV2(_v1, _v2);
console.log("setV1andV2: %s, %s", x, y);
}
function setTemplate(
uint256 idx,
uint256 _val1,
uint256 _val2
) public {
ICallee.template memory temp = ICallee.template({
tCount: _val1,
tValue: _val2
});
bool x = ICallee(_calleeAddr).setTemplate(idx, temp);
}
function getTemplate(uint256 _idx) public {
ICallee.template memory temp = ICallee(_calleeAddr).getTemplate(_idx);
}
}
인터페이스로 부를수있는 코드이다
(2) call
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "hardhat/console.sol";
contract Caller_call {
address internal _calleeAddr;
constructor(address _calleeAddr_) {
_calleeAddr = _calleeAddr_;
}
function setV1(uint256 _v1) public {
(bool success, bytes memory result) = _calleeAddr.call(
abi.encodeWithSignature("setV1(uint256)", _v1)
);
if(success){
uint x = abi.decode(result, (uint));
console.log("setV1: %s", x);
}
}
function setV1andV2(uint256 _v1, uint256 _v2) public {
(bool success, bytes memory result) = _calleeAddr.call(
abi.encodeWithSignature("setV1andV2(uint256,uint256)", _v1, _v2)
);
(uint256 x, uint256 y) = abi.decode(result, (uint256, uint256));
console.log("setV1andV2: %s, %s", x, y);
}
function setTemplate(
uint256 idx,
uint256 _val1,
uint256 _val2
) public {
(bool success, bytes memory result) = _calleeAddr.call(
abi.encodeWithSignature("setTemplate(uint256,(uint256,uint256))",idx, _val1, _val2)
);
console.logBytes(result);
}
function getTemplate(uint256 _idx) public {
(bool success, bytes memory result) = _calleeAddr.call(
abi.encodeWithSignature("getTemplate(uint256)",_idx)
);
console.logBytes(result);
}
}
(3) delegate call
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "hardhat/console.sol";
contract Caller_delegateCall {
address internal _calleeAddr;
constructor(address _calleeAddr_) {
_calleeAddr = _calleeAddr_;
}
function setV1(uint256 _v1) public {
(bool success, bytes memory result) = _calleeAddr.delegatecall(
abi.encodeWithSignature("setV1(uint256)", _v1)
);
if(success){
uint x = abi.decode(result, (uint));
console.log("setV1: %s", x);
}
}
function setV1andV2(uint256 _v1, uint256 _v2) public {
(bool success, bytes memory result) = _calleeAddr.delegatecall(
abi.encodeWithSignature("setV1andV2(uint256,uint256)", _v1, _v2)
);
(uint256 x, uint256 y) = abi.decode(result, (uint256, uint256));
console.log("setV1andV2: %s, %s", x, y);
}
function setTemplate(
uint256 idx,
uint256 _val1,
uint256 _val2
) public {
(bool success, bytes memory result) = _calleeAddr.delegatecall(
abi.encodeWithSignature("setTemplate(uint256,(uint256,uint256))",idx, _val1, _val2)
);
console.logBytes(result);
}
function getTemplate(uint256 _idx) public {
(bool success, bytes memory result) = _calleeAddr.delegatecall(
abi.encodeWithSignature("getTemplate(uint256)",_idx)
);
console.logBytes(result);
}
}
2 가스비 차이
이걸알기 위해 이 모든 테스트를 시작했다
결과는 다음과 같다
Callee 에서 직접 js 로 부르기 <<<< Interface << delegate call < call
의외로 인터페이스로 부르는게 제일 효율적이었다
다음엔.. 꼭 이더리움 황서 읽는법을 익혀서 계산하고싶다.. 전수 테스트 말고
그러면 call, delegate call은 왜 존재하고 차이가 뭐길래 가스비가 차이가 나는걸까?
더 좋은점이 있어서 비싼것일까?
3 Low / High level function
call 과 delegate call 은 Low level function 이다.
(1)로우레벨 펑션이 뭐가 좋냐면 컨트랙트의 펑션을 dynamic하게 발동할수 있게한다.
이는 자바의 reflection 과 비슷한데,
자바의 reflection 이란 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 한다.
간단한 코딩에서는 필요없지만 나중에 패턴을 도입하면 필요할수 있다.
이게 와닿는지 않는다면
(2) Low 는 실패시 무조건 revert 시키지 않고
성공결과가 bool 로 리턴된다. false를 리턴받으면 실패인것.
그래서 false 를 받았을때 재시도하기 등의 여러 작업을 해볼수있다.
4 최종 컨트랙트의 msg. sender, 데이터 저장위치
call, interface를 통한 호출의 경우에는 callee 를 부른 주소가 (msg.sender)
반면 delegate call 의 경우에는 callee를 부른주소가 최초의 eoa 이다.
Callee.sol 에 로그 추가해주고 각 caller의 setV1 에도 로그를 찍는다.
function setV1(uint256 _value1) external returns (uint256) {
console.log('callee address: %s', msg.sender);
value1 = _value1;
return value1;
}
이제 로그를 찍어보면
0xf39fd... 가 EOA 다.
맨처음 delegatecall의 경우 EOA 가 최종 컬리와 딜리게이트콜 컨트랙트를 모두 부름을 알수있다.
call, interface 의 경우 배포된 주소(CA)로 callee address를 부르게된다.
저장위치
delegate call 이용시 데이터는 Caller_delegateCall.sol 에 저장되고 Callee.sol 은 함수만 제공한다.
-> 이걸 upgradeble smart contract 라고함
'dev > Solidity' 카테고리의 다른 글
Zero-Knowledge Rollups(1) 배경 (0) | 2022.12.01 |
---|---|
solidity 에러 - stack too deep (0) | 2022.08.24 |
[Solidity]String 과 용량, 가스비 (0) | 2022.07.29 |
opensea 및 NFT market 매매 흐름(setApprovalForAll, safeTransferFrom) (0) | 2022.07.22 |
Storing Structs is costing you gas (0) | 2022.06.20 |