ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [solidity] NoDelegateCall 컨트랙트와 immutable, constant 키워드에 대하여
    solidity 2023. 2. 13. 22:29
    반응형
    abstract contract NoDelegateCall {
        address private immutable original;
    
        constructor() {
            original = address(this);
        }
    
        function checkNotDelegateCall() private view {
            require(address(this) == original);
        }
    
        modifier noDelegateCall() {
            checkNotDelegateCall();
            _;
        }
    }

    위 코드는 유니스왑v3의 NodelegateCall 컨트랙트이다.

    살펴보면 이름그대로 delegatecall 을 막는용도의 컨트랙트 이고 주석에도

    /// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract

    이렇게 적혀있다. 이컨트랙트를 상속 받아서 아래 modifier를 붙여서 사용하게 되면 delegatecall 을 막을 수 있다 라는 내용이다.

     

    delegatecall이 뭔지 간단하게만 적어보자면 A컨트랙트에서 B컨트랙트의 함수를 호출하여 사용할수 있는데 A에서 call 을 사용하해서 호출하면 B컨트랙트에서 해당함수가 작동하지만 delegatecall 은 B컨트랙트의 함수를 가져와 A컨트랙트에서 실행하는 호출법이다.

        function testCallFoo(address payable _addr) public payable {
            (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
                abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
            );
        }
        
        function setVars(address _contract, uint _num) public payable {
            (bool success, bytes memory data) = _contract.delegatecall(
                abi.encodeWithSignature("setVars(uint256)", _num)
            );
        }

    만약 B컨트랙트가 위의 NoDelegateCall 컨트랙트를 상속받아 특정함수(setVars)에 modifier(noDelegateCall) 를 같이 사용했다고 가정해보자 A컨트랙트에서 B의 특정함수를 가져와 사용하는 delegatecall 을 통해 호출한다면 address(this)는 A 컨트랙트의 주소가 되고 original 은 B컨트랙트의 주소 임으로 modifier의 require 문을 통과할수 없게된다. 그냥 call 로 호출을 했다면 함수의 동작이 B컨트랙에서 이루어짐으로 address(this) 역시 B 컨트랙트여서 require 문을 통과할수 있다.

     

    다시 컨트랙트를 보자

    abstract contract NoDelegateCall {
        address private immutable original;
    
        constructor() {
            original = address(this);
        }
    
        function checkNotDelegateCall() private view {
            require(address(this) == original);
        }
    
        modifier noDelegateCall() {
            checkNotDelegateCall();
            _;
        }
    }

    address에 immutable 키워드가 붙어있다.

    immutable 변수는 할당이 선언과 동시 혹은 생성자에서만 할당할수 있으며 한번만 할당이 가능하다라고 나와있고 사용목적은 상속이나 변수초기화 생성자실행순서에의해 의도와 다르게 변경될 여지가 있는 부분에 보호하기 위해서 사용한다 라고 나와있는데 읽어보고 아~ 어디에 써야겠다. 이런 느낌은 안드는데 그래도 nodelegateCall 컨트랙트에서 original 예시를 생각하면 조금 이해하는데 도움이 된다.

     

    그리고 그와 비슷하게 constant 변수도 존재하는데 이건 좀더 조건들이 많다. 할당가능한 값들이 정해져 있는데 immutable 변수는 임의의 값을 할당할수 있지만 constant 변수는 제약이 있다.

    1. 할당은 선언된 위치에서만 가능하다.

    2. storage 나 블록체인 데이터에 접근해서 가져오는 데이터는 할당할수없다.(block.number, block.timestamp, address(this))

    3. execution data( msg.value, gasleft()) 사용불가

    4. built-in function 은 사용가능 (keccak256, sha256 ...)

     

    둘은 할당이후 변수를 수정할수 없다라는 공통점이 있지만 constant 변수는 컴파일 타임에 변수값이 있어야 하지만 immutable 변수는 construction time 까지 존재하면 되는 차이가 있다 즉 immutable은 constructor 에서 값을 할당해줘도 되고 선언즉시 할당해줘도 되지만 constant는 선언즉시 할당만 가능하다는 이야기이다.

    둘다장점으로는 일반 변수에 비해 가스비가 저렴하다 이고 constant 가 immutable 보다 때때로 저렴할수 있다고 한다.

     

    아래는 공식문서의 예시이다.

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity >=0.7.4;
    
    uint constant X = 32**22 + 8;
    
    contract C {
        string constant TEXT = "abc";
        bytes32 constant MY_HASH = keccak256("abc");
        uint immutable decimals;
        uint immutable maxBalance;
        address immutable owner = msg.sender;
    
        constructor(uint decimals_, address ref) {
            decimals = decimals_;
            // Assignments to immutables can even access the environment.
            maxBalance = ref.balance;
        }
    
        function isBalanceTooHigh(address other) public view returns (bool) {
            return other.balance > maxBalance;
        }
    }

     

     

Designed by Tistory.