티스토리 뷰

솔리디티 컨트랙트에서 다른 컨트랙트를 부르는방식은 여러가지가 있다.

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 라고함

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함