【Solidity】CREATE & CREATE2

创建、部署合约

我们可以通过合约的创建字节码 (creation bytecode) 创建、部署合约,并获取合约地址。creation bytecode 包含了部署合约所需的所有信息,包含构造函数和初始化代码。

实现合约的创建、部署功能需要在 Solidity 的内联汇编 assembly { ... } 里面调用 create 方法。

create 方法:create(v, p, n)v 表示要发送的以太数量、p 是 creation bytecode 的起始指针,用于告诉 Solidity 编译器 creation bytecode 的起始位置、n 表示 creation bytecode 的长度;create 方法会创建、部署合约并返回合约地址。

  1. 在 Solidity 中 发送的以太数量用 msg.value 表示,但在汇编中 用 callvalue() 表示

  2. 在 Solidity 中,动态数组和字符串的第一个 32 字节存储的是其长度。因此,实际的 creation bytecode 数据从第 32 字节开始。所以:

    1. 传入 p 时要跳过 32 byte,在汇编中可以通过 add(_creationCode, 0x20) 实现
    2. creation bytecode 的长度 n 在汇编中可以用 mload(_creationCode) 获取
contract Demo {
    address public owner = msg.sender;
}

contract Helper {
    function getByteCode() public pure returns (bytes memory) {
    	// 获取 Demo 合约的 creation bytecode 并返回
        return type(Demo).creationCode;
    }
}

contract Proxy {
    function deployDemo(
        bytes memory _creationCode // 合约的 creation bytecode
    ) public payable returns (address addr) {
        assembly {
            // 创建、部署合约, 获取合约地址
            addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
        }
        // 检查合约地址的有效性
        require(addr != address(0), "Failed to deploy contract");
    }
}
  1. 部署 Helper 合约、Proxy 合约

  2. 执行 Helper 合约的 getByteCode 方法,得到 Demo 合约的 creation bytecode

  3. 将 Demo 合约的 creation bytecode 作为参数传入 Proxy 合约的 deployDemo 方法并执行,创建、部署 Demo 合约并得到其地址

  4. 通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix

  5. 检查 Demo 合约的 owner 是否为 Proxy 合约的地址



部署合约并传入参数

contract Demo {
    address public owner = msg.sender;
    uint public count;

    constructor(uint _count) {
        count = _count;
    }
}

contract Helper {
    function getByteCode(uint _count) public pure returns (bytes memory) {
        return abi.encodePacked(type(Demo).creationCode, abi.encode(_count));
        // 用 encode 编码传入的参数; 用 encodePacked 包装 Demo 合约的 creation bytecode 和编码后的参数
    }
}

contract Proxy {
    function deployDemo(
        bytes memory _creationCode
    ) public returns (address addr) {
        assembly {
            addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
        }
        require(addr != address(0), "Failed to deploy contract");
    }
}
  1. 部署 Helper 合约、Proxy 合约

  2. 传入参数 _count 到 Helper 合约的 getByteCode 方法并执行,得到包装好的 Demo 合约的 creation bytecode 和编码后的参数

  3. 将步骤 2 得到的数据作为参数传入 Proxy 合约的 deployDemo 方法并执行,创建、部署 Demo 合约并得到其地址

  4. 通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix

  5. 检查 Demo 合约的 owner 是否为 Proxy 合约的地址、count 是否为步骤 2 中传入的参数



部署合约并传输以太

contract Demo {
    address public owner = msg.sender;
    uint public value = msg.value;

    constructor() payable {} // 声明 payable 构造方法, 以支持在部署合约时传输以太
}

contract Helper {
    function getByteCode() public pure returns (bytes memory) {
        return type(Demo).creationCode;
    }
}

contract Proxy {
    function deployDemo(
        bytes memory _creationCode
    ) public payable returns (address addr) {
        // 使用 payable 修饰方法, 以支持在 public / external 方法中使用 callvalue()
        assembly {
            addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
        }
        require(addr != address(0), "Failed to deploy contract");
    }
}
  1. 部署 Helper 合约、Proxy 合约

  2. 执行 Helper 合约的 getByteCode 方法,得到 Demo 合约的 creation bytecode

  3. 设置以太数量、将 Demo 合约的 creation bytecode 作为参数传入 Proxy 合约的 deployDemo 方法并执行,部署 Demo 合约并得到其地址

  4. 通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix

  5. 检查 Demo 合约的 owner 是否为 Proxy 合约的地址、value 是否为步骤 3 中传输的以太数量



CREATE2

CREATE2 是以太坊虚拟机(EVM)中的一个操作码,用于创建智能合约。与传统的 CREATE 操作码不同,CREATE2 允许开发者在部署合约之前预测其地址。

CREATE2 通过以下公式计算合约地址:address = keccak256(0xff + sender + salt + keccak256(bytecode)),其中:0xff 是一个常量,用于区分 CREATE2 和 CREATE;sender 是创建合约的合约地址;salt 是一个 32 字节的随机值,由开发者提供;bytecode 是要部署的合约的创建字节码。

contract DeployWithCreate2 {
    address public owner;

    constructor(address _owner) {
        owner = _owner;
    }
}

contract Create2Factory {
    event Deploy(address addr);

    // 使用 CREATE2 操作码部署 DeployWithCreate2 合约
    function deploy(uint _salt) external {
        DeployWithCreate2 _contract = new DeployWithCreate2{
            salt: bytes32(_salt)
        }(msg.sender);
        emit Deploy(address(_contract));
    }

    function getBytecode(address _owner) public pure returns (bytes memory) {
        // 获取 DeployWithCreate2 合约的创建字节码
        bytes memory bytecode = type(DeployWithCreate2).creationCode;
        // 用 encode 编码传入的参数; 用 encodePacked 包装 DeployWithCreate2 合约的 creation bytecode 和编码后的参数
        return abi.encodePacked(bytecode, abi.encode(_owner));
    }

    // 计算合约地址
    function getAddress(
        bytes memory bytecode,
        uint _salt
    ) public view returns (address) {
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                _salt,
                keccak256(bytecode)
            )
        );
        // 将 hash 转为 uint 再转为 uint160, 表示取最后 20 个字节, 因为以太坊地址的长度为 20 字节
        return address(uint160(uint(hash)));
    }
}
  1. 部署 Create2Factory 合约

  2. 将编辑器的地址作为参数传入 getBytecode 方法并调用,获取包装好的 DeployWithCreate2 合约的 creation bytecode 和编码后的参数

  3. 将步骤 2 获取到的数据和随机值作为参数传入 getAddress 方法并调用,计算合约地址;随机值这里以 123 为例

  4. 调用 deploy 方法,传入随机值 123,部署 DeployWithCreate2 合约

  5. 查看 DeployWithCreate2 合约地址,与计算的合约地址比对,两者应该相等


  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值