Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【开发小记】create2的使用方式 #6

Open
SpaceStation09 opened this issue Jan 13, 2025 · 3 comments
Open

【开发小记】create2的使用方式 #6

SpaceStation09 opened this issue Jan 13, 2025 · 3 comments

Comments

@SpaceStation09
Copy link
Owner

本issue用于记录在合约开发过程中,合约端,以及链下部分如何计算出匹配的create2地址,以及这两个部分需要注意的点。

@SpaceStation09
Copy link
Owner Author

Overview

CREATE2相比原来的操作码CREATE不再依赖于账户的nonce,而是对以下三个参数进行hash,计算出新地址:

  • 合约部署者地址
  • 传入的salt
  • 合约的部署代码initCode),特别需要注意这里的部署代码的构成。

具体的计算公式是这样的:

address = keccak256(0xff ++ deployerAddress ++ salt ++ keccak256(initCode))[12:]

@SpaceStation09
Copy link
Owner Author

如何计算

如上所说,此处需要注意的是所谓合约的部署代码的构成,部署代码指的是,合约在被部署时传入账户的数据,而非他的runtime code,也非其代码直接编译出的结果。直接的来说,这个initCode = bytecode ++ constructorArgs (如果有)

在合约端:

//calculate initCode
bytes memory initCode = abi.encodePacked(
  type(DeployContract).creationCode,
  abi.encode(constructorArgs1, constructorArgs2)
);

//compute address
bytes32 hash = keccak256(
  abi.encodePacked(
    bytes1(0xff),
    deployer,
    salt,
    keccak256(initCode)
  )
);

// OR use Openzeppelin Create2
Create2.computeAddress(salt, keccak256(initCode));

// Deploy
DeployContract contract = new DeployContract{salt: _salt}(constructorArgs1, constructorArgs2);

在ts测试端:

//get creationCode
const creationCode = (await ethers.getContractFactory("DeployedContract")).bytecode;

//encode constructorArgs
const args = AbiCoder.defaultAbiCoder().encode(
  ["type", "type"],
  [constructorArgs1, constructorArgs2]
);
// Combine initCode
const initCode = solidityPacked(["bytes", "bytes"], [creationCode, args]);

@SpaceStation09
Copy link
Owner Author

SpaceStation09 commented Jan 13, 2025

One Little Trick

在前面部分,重点强调了在计算地址时,我们用到的参数是部署代码。这个代码是用来创建合约的,合约创建完成后将返回runtime bytecode。要讲清楚接下来tricky的地方,我们首先需要搞清楚creation coderuntime code,以及他们之间的因果关系。

AHA Moment

constructor logiccreation code的一部份,而creation code实际上是只会被EVM执行一次的代码,所以constructor logic也只会被执行一次。在creation code被执行时,会根据constructor logic 以及合约本身代码,生成并返回runtime code,最终这个runtime code才是被保存在链上的,用于执行后续交互的代码,而creation code并没有被存储在链上的合约地址里。

关键:如果我们在constructor logic中return 一个bytecode,那么这意味着,这个最终被存储在链上用作后续交互的就是这个bytecode。 详情可查看The Journey of Smart Contracts from Solidity code to Bytecode,其中Deployment and Initialization of Smart Contract 的章节,对于部署合约时的底层opcode有详细的讲解,你可以更深刻的理解我描述的这个过程。

Example

上面的文字表述可能不够直接,那么我们使用一组代码,来直截了当的展示,这是一个怎样的trick。代码来源是:2019 Balsn CTF 的 Creativity 的 WP 提供的 PoC

pragma solidity ^0.5.10;

contract Deployer {
    bytes public deployBytecode;
    address public deployedAddr;

    function deploy(bytes memory code) public {
        deployBytecode = code;
        address a;
        // Compile Dumper to get this bytecode
        bytes memory dumperBytecode = hex'6080604052348015600f57600080fd5b50600033905060608173ffffffffffffffffffffffffffffffffffffffff166331d191666040518163ffffffff1660e01b815260040160006040518083038186803b158015605c57600080fd5b505afa158015606f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015609857600080fd5b81019080805164010000000081111560af57600080fd5b8281019050602081018481111560c457600080fd5b815185600182028301116401000000008211171560e057600080fd5b50509291905050509050805160208201f3fe';
        assembly {
            a := create2(callvalue, add(0x20, dumperBytecode), mload(dumperBytecode), 0x9453)
        }
        deployedAddr = a;
    }
}

contract Dumper {
    constructor() public {
        Deployer dp = Deployer(msg.sender);
        bytes memory bytecode = dp.deployBytecode();
        assembly {
            return (add(bytecode, 0x20), mload(bytecode))
        }
    }
}

每次我们使用deploy(code)来部署合约时,他的creation code实际上是Dumper的bytecode (每次都是固定的),再加上确定的deployer以及salt,我们可以确保最终合约会被部署到同一个地址上,而Dumper在它的constructor logic中return了实际的bytecode,使得这个地址的合约最终的runtime code 其实是deploy(code)中的code这样我们就可以通过这种方式,不管我们要部署什么合约,最终,它们都会部署到同一个地址上。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant