ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [solidity] UniswapV3PoolDeployer 컨트랙트와 return variable(반환변수), new 컨트랙트 생성에 대해 알아보기
    solidity 2023. 2. 21. 00:56
    반응형

    이전글까지 uniswap v3-core의 factory 컨트랙트와 solidity 문법을 같이 봤었다 오늘은 pool 컨트랙트를 보려고 했는데 그전에 factory 컨트랙트의 createpool 함수와 pooldeployer 컨트랙트를 보고 넘어가면 좋을거 같기도 하고 문법적으로 처음 보는 부분도 있어서 한번더 보고 넘어가려고 한다. 참고로 v2 까지는 pool 컨트랙트의 역할은 pair 컨트랙트에서 사용됬는데 이름이 바뀐부분인거같다.

    contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer {
        struct Parameters {
            address factory;
            address token0;
            address token1;
            uint24 fee;
            int24 tickSpacing;
        }
    
        Parameters public override parameters;
    
        function deploy(
            address factory,
            address token0,
            address token1,
            uint24 fee,
            int24 tickSpacing
        ) internal returns (address pool) {
            parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing});
            pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());
            delete parameters;
        }
    }

    pooldeployer 컨트랙트가 하는 역할은 오직 deploy 역할을 하는 것이다. 이함수는 factory컨트랙트에서 createPool 함수에서 사용이 되고 있다. 아래는 해당 부분코드이다.

    function createPool(address tokenA, address tokenB, uint24 fee) external override noDelegateCall returns (address pool) {
        ...
        pool = deploy(address(this), token0, token1, fee, tickSpacing);
        ...
        emit PoolCreated(token0, token1, fee, tickSpacing, pool);
    }

    createPool 함수내의 코드는 익숙한 코드들 인데 deploy함수는 좀 의아하게 느껴지는 부분들이 좀 있다.

     

    1. parameters 구조체에 deploy에 사용한 항목을 저장했다가 pool 을 만든이후에 delete 한다. 사용하는 부분도 없는데 왜 만들었다가 지우는지 궁금했다.

    2. returns (address pool) 이라고 명시되어 있는데 return 키워드가 함수내에 없다.

    3. new UniswapV3Pool{salt: keccak256(...)}()) 이부분의 코드 문법적 의미에 대한 학습이 안되어있다.

     

    일단 1번 당연한거긴 한데 사용하는 부분이 있다ㅎㅎ 일단 구조체 변수가 삭제되기 이전에 일어난 일은 pool을 생성하는 부분밖에 없다.

    그러니까 pool 컨트랙트의 생성자 부분 코드를 보자

     

    constructor() {
            int24 _tickSpacing;
            (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
            tickSpacing = _tickSpacing;
    
            maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
        }

    생성자에서 변수를 parameters()를 통해서 받아와 설정해 주는 부분을 볼수 있다. 그니까 사용부분이 없는게 아니라 중간에 컨트랙트 생성하는 코드의 생성자에서 생성하고 있었다.

     

    그럼 2번 .. 잘모르겠다. 연관이 있을지도 모르니 3번 부터 알아보자 문법적인 내용이니 solidity doc을 찾아보자 https://docs.soliditylang.org/en/v0.8.18/control-structures.html#creating-contracts-via-new

     

    일단 기본적으로 다른 컨트랙트에서 new 키워드를 사용해서 컨트랙트를 생성할수 있다. 그리고 생성하면서 컨트래트로 이더도 전송해줄수 는데 자세한 설명은 doc을 참고하는게 좋고 간단한 구문만 적어두면 아래와 같다.

    D newD = new D(arg);
    D newD = new D{value: amount}(arg); // Send ether along with the creation

    그리고 내가 궁금했던 부분은 create2 라고 부르는데 이는 일반적인 create 즉 일반적인 컨트랙트 생성에는 nonce 값이 사용되며 이값으로 인해 같은컨트랙트와 같은생성자를 이용해도 nonce 값이 변하기 때문에 생성된 컨트랙트의 주소는 매번 바뀌게 되며 이를 비결정론 적으로 컨트랙트주소가 생성된다고 한다. 그러나 create2 는 이런 일반적인 방법과 다르기 때문에 create2 라고 부르는거 같고 그말은 결정론 적으로 컨트랙트 주소가 정해져 생성 된다는 말이다.

    이때 nonce 값 대신에 salt 값을 지정해 줌으로써 생성이전에 어떤 주소로 생성이 될지 알수 있다. 즉 결정론적으로 컨트랙트를 생성해내는 방법이라고 생각하면 된다. 구문은 아래와 같고 주소가 결정되는 방식역시 닥스에 잘 나와 있고 아래는 해당코드이다.

    contract D {
        uint public x;
        constructor(uint a) {
            x = a;
        }
    }
    
    contract C {
        function createDSalted(bytes32 salt, uint arg) public {
            // This complicated expression just tells you how the address
            // can be pre-computed. It is just there for illustration.
            // You actually only need ``new D{salt: salt}(arg)``.
            address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
                bytes1(0xff),
                address(this),
                salt,
                keccak256(abi.encodePacked(
                    type(D).creationCode,
                    abi.encode(arg)
                ))
            )))));
    
            D d = new D{salt: salt}(arg);
            require(address(d) == predictedAddress);
        }
    }
     
    /// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash
    /// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain

    해당 주석은 poolDeployer 의 인터페이스에 적혀있는 주석인데 1,3번과 관련된 내용이라 적어뒀다.

    parameters 는 pool contract 의 생성자의 인자로 사용되는 부분을 피할수 있게 해주고 그로인해서 init code hash 를 생성한다고 나왔고 create2는 더 값싸게 체인에서 계산이 가능하다 이런내용이다. 

    init code hash 라는 키워드가 검색해보니까 따로 의미하는 바가 있는것 같고 create2도 opcode 상에서 create2를 나타내기위한 표현인거 같다. 일단 대략 어떤 맥락인지는 알았는데 추후에 한번더 알아보면 좋은 내용인거 같다.

    오픈제플린 docs 에 자세하게 나와있는데 나중에 봐야지 ㅎㅎ

     https://docs.openzeppelin.com/cli/2.8/deploying-with-create2

     

    다시 2번을 보자 아니이게 docs 찾아보니까 있네 https://docs.soliditylang.org/en/v0.8.18/contracts.html#return-variables

    반환변수라는 개념을 내가 몰랐네 어려운 개념은 아니고 그냥 문법적인 내용이라 숙지만 하면될듯 반환변수는 할당이후에 생략할수 있다.

    contract Simple {
        function arithmetic(uint a, uint b)
            public
            pure
            returns (uint sum, uint product)
        {
            sum = a + b;
            product = a * b;
        }
    }

    그니까 pool = address(new~~) 하면서 pool 이 반환변수여서 리턴이 된거였구나

     

    오늘 느낀거는 내가 위의 예시코드에는 지저분해서 주석을 다 빼놓긴 했는데 주석이 정말 친절하고 @title @notice @dev 이런식으로 어떠한 주석인지 분류까지 되어있으니까 분석하는데 많은 도움도 됬고 커밋메세지도 깃렌즈로 같이보면서 분석에 많이 도움이 됬다.

    몰랐던 내용을 알게된것도 의미가 있었지만 더 와닿았던 부분은 그동안 나는 주석 왠만하면 안달고 그냥 함수이름이랑 변수이름만 잘 지어줘도 된다고 생각하면서 코딩해왔었는데 조금 생각이 바뀌게 되는 계기가 되는 코드들 이였다. 커밋메시지나 커밋주기 같은 부분도 많이 배울수 있는 부분이여서 앞으로 고민하는데 도움이 될거같다.

Designed by Tistory.