체인의정석

Solidity Create와 Create2 본문

블록체인/Solidity

Solidity Create와 Create2

체인의정석 2024. 1. 8. 14:53
728x90
반응형

여러개의 컨트렉트를 만드는 contractFactory 컨트렉트를 만들어야 하는 상황이 생겼다.

이런경우 사용가능한 함수는 create1,2,3가 있다. 이번엔 create1과 2에 대해 정리하려고한다.

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

 

Deploying Smart Contracts Using <code>CREATE2</code> - OpenZeppelin Docs

Under normal circumstances, sending funds to a random Ethereum address is a bad idea. Here however, we know we’ll be able to deploy Vault at the computed address and retrieve our funds. So let’s do it! The easiest way to send Ether is by using oz trans

docs.openzeppelin.com

1. Create

create의 경우 solidity에서 new Contract를 쓰면 자연스럽게 생성되는 것으로 sender주소와 nonce값을 가지고 컨트렉트 주소를 뽑아낸다.

new_address = hash(sender, nonce)

https://docs.soliditylang.org/en/latest/control-structures.html#creating-contracts-via-new

 

Expressions and Control Structures — Solidity 0.8.24 documentation

Expressions and Control Structures Edit on GitHub Expressions and Control Structures Control Structures Most of the control structures known from curly-braces languages are available in Solidity: There is: if, else, while, do, for, break, continue, return,

docs.soliditylang.org

create1을 써서 컨트렉트를 배포하는 코드는 다음과 같다.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) payable {
        x = a;
    }
}

contract C {
    D d = new D(4); // will be executed as part of C's constructor

    function createD(uint arg) public {
        D newD = new D(arg);
        newD.x();
    }

    function createAndEndowD(uint arg, uint amount) public payable {
        // Send ether along with the creation
        D newD = new D{value: amount}(arg);
        newD.x();
    }
}

 

2. Create2

create2의 경우

  • 0xFF, a constant that prevents collisions with CREATE
  • The sender’s own address
  • A salt (an arbitrary value provided by the sender)
  • The to-be-deployed contract’s bytecode
new_address = hash(0xFF, sender, salt, bytecode)

다음과 같이 사용자가 직접 salt값을 지정해준다. 예를들어 유니스왑v2의 contractFactory의 경우 동일 풀에 대한 컨트레트 주소가 중복 생성되는것을 막아야하기 때문에 각 토큰의 주소를 salt값으로 넣어주어서 동일 토큰에대한 풀 주소 배포를 방지해준다. 따라서 만약 고유 설정값이 있는 컨트렉트를 배포하는 경우에는 해당 값을 salt로 넣어주면되고 고유하지 않다면 그냥 create를 써주면된다.

Create2를 배포하는 solidity코드는 다음과 같다. 만약 입력값을 빼고 싶다면 getCreationBytecode에서 bytecode만 사용하면 된다.

입력값을 뺀 후 나중에 initialize를 하는 방법또한 가능하다.

    function getCreationBytecode(uint256 a, uint256 b, uint256 c, uint256 d) public pure returns (bytes memory) {
        bytes memory bytecode = type(ContractName).creationCode;

        return abi.encodePacked(bytecode, abi.encode(a, b, c, d));
    }

    function _createVote(uint256 a, uint256 b, uint256 topN, c d) internal returns (address voteAddress) {
        bytes memory bytecode = getCreationBytecode(a, b, c, d);
        bytes32 salt = 
        keccak256(
            abi.encodePacked(
                a,
                b,
                c,
                d
            )
        );
        assembly {
            dappAddress := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        return dappAddress;
    }

하지만 여기에서는 입력값이 필요했고 여기에 대한 2가지 해결방법을 생각해냈다.

1. initialize함수에 따로 빼기

생성자 형태로 초기화가 필요한 함수를 생성자에 넣는 대신 따로 initialize함수로 빼서 실행을 한다.

해당 방법의 경우 나중에 만약 생성자를 다시 실행시켜야 할 경우에 유용할 수도 있으며 배포 과정이 2단계가 되지만 생성자에 대한 관리가 조금 덜 햇갈릴것 같아서 해당 방식을 개인적으로는 좋게 생각한다.

2. constructor 의 input값을 같이 바이트코드로 인코딩하여 실행하기

https://it-timehacker.tistory.com/340

 

UniswapV2 정리 2 - Uniswap V2 Factory

https://it-timehacker.tistory.com/339 UniswapV2 정리 - UniswapV2Pair & UniswapV2ERC20 컨트렉트 https://github.com/Uniswap/v2-core/tree/master/contracts GitHub - Uniswap/v2-core: 🎛 Core smart contracts of Uniswap V2 🎛 Core smart contracts of Uni

it-timehacker.tistory.com

먼저 참고한 부분은 uniswapV2의 Factory였다.

create2에 대한 설명은 위의 포스팅에 있기 때문에 생략하도록 하겠다.

https://ethereum.stackexchange.com/questions/78738/passing-constructor-arguments-to-the-create-assembly-instruction-in-solidity

 

Passing constructor arguments to the CREATE assembly instruction in solidity

I have a contract which deploys other contracts by bytecode: contract DeployContract { function deploy(bytes calldata _bytecode) external returns(address addr) { bytes memory bytecode...

ethereum.stackexchange.com

해당 글을 보니

abi.encodePacked(bytecode, abi.encode(arg1, arg2))

이런식으로 input값을 create2의 바이트코드에 넣을값과 함께 인코딩하면 된다고 한다.

그 이유는

bytes memory bytecode = type(컨트렉트이름).creationCode;

해당 바이트코드를 도출하는 코드에서 쓰는 creationCode가 생성자 함수를 포함해서 바이트코드를 도출하는 것이기 때문에 input값을 같이 인코딩해서 넣으면 생성자에 input값을 넣은것처럼 되기 때문이다.

 

728x90
반응형
Comments