# Solidity CREATE2 创建合约（Uniswap createPair）

Original: <https://lifelonglearn.ing/solidity-create-contract>

Author: [奶爸](https://lifelonglearn.ing/users/3b04650c-8231-499b-8ef0-381fc4d7efb0)

Published: 2022-02-07T08:57:58Z

Updated: 2026-09-27T06:57:43Z

Version: 1

这个技术是 Uniswap 创建交易对 和 [GasToken（ChiToken）](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly9saWZlbG9uZ2xlYXJuaS5uZy9nYXN0b2tlbg==)的技术原理。利用 Solidity 的 `CREATE2` 创建可计算地址的合约，然后利用这个合约的 `selfdestruct` 来返还一些 做一些需要 **固定地址（计算出地址）** 的业务逻辑。

之前的版本还需要使用 `create2` 操作符来创建合约，现在的版本 **0.8.12** 都不需要了，发出来记录一下，省的后面用的时候又忘记。

做了一个 [示例](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly9rb3Zhbi5ldGhlcnNjYW4uaW8vYWRkcmVzcy8weGMxMzllNWNiMDY4YmU5MjkyNGQzMzA1MDg5Mjc0NGJhYWI1MDk1NDI=)：可以带 ETH 调用 `createChild`，来将附带 ETH 存入创建出来的子合约，然后可以调用 `claim` 方法将存入的 ETH 提取出来。并没有记录所有子合约的合约地址，而是根据计数推算出子合约合约地址进行调用。看下操作记录：

1. 存入 [100 ETH](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly9rb3Zhbi5ldGhlcnNjYW4uaW8vdHgvMHhiMmI2ZWY4Y2JjNzM4MDBlNGZlOGQ4MTZiNjhmNjgxYmE3YzQ1YjIwMDI1NWEyMTg2ODkwMTVjZjRhNjU4MGZk)。
2. 提取前面存入的 [100 ETH](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly9rb3Zhbi5ldGhlcnNjYW4uaW8vdHgvMHgzMGMyMzNkMzIyYjZkNjE4MWIxMDM5Mzg3MzdlN2MwODg3MDdkNjYzZGMwMDU5ZWM4ODJlY2RiNDE3YzVmOTY4)。
3. 后面进行了 [多次存取](https://lifelonglearn.ing/r/go?url=aHR0cHM6Ly9rb3Zhbi5ldGhlcnNjYW4uaW8vYWRkcmVzcy8weGMxMzllNWNiMDY4YmU5MjkyNGQzMzA1MDg5Mjc0NGJhYWI1MDk1NDI=) 来验证技术实现的对不对。

**后记：** 开始没有将 ​**Child**​、**Parent** 合约拆分到两个文件里面，在 Remix 里面测试时发现计算出的子合约的合约地址是错误的，结果是因为 Remix 里面复制的 bytecode 是​**整个文件的 bytecode**​，而不是单个选中的合约的 bytecode，导致 hash 计算错误出现问题。

## Child 子合约

用来储存 ETH 资产

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

contract Child {
    address public owner;

    constructor() payable {
        owner = msg.sender;
    }

    function destruct(address payable to) public {
        assert(owner == msg.sender);
        selfdestruct(to);
    }
}
```

## Parent 控制器

用来部署子合约，调用销毁子合约、计算子合约合约地址

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

import "./Child.sol";

contract Parent {
    bytes32 public constant CHILD_BYTECODE_KECCAK32 =
        0x3777cc71780224f9113b98623e11e85919892f48d89f0264112bb5be178f2d2e;

    uint256 public nonce;
    uint256 public claimNonce;

    event ChildCreated(address addr);

    function balance() public view returns (uint256) {
        return nonce - claimNonce;
    }

    function claim() public {
        require(claimNonce < nonce, "no remaining balance");
        Child child = Child(computeChild(++claimNonce));
        child.destruct(payable(msg.sender));
    }

    function createChild() public payable {
        assert(msg.value > 0);
        emit ChildCreated(
            address(
                new Child{
                    salt: keccak256(abi.encode(++nonce)),
                    value: msg.value
                }()
            )
        );
    }

    function computeChild(uint256 _nonce) public view returns (address) {
        return
            address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                hex"ff",
                                address(this),
                                keccak256(abi.encode(_nonce)),
                                CHILD_BYTECODE_KECCAK32
                            )
                        )
                    )
                )
            );
    }

    function k3ccak256(bytes calldata data) public pure returns (bytes32) {
        return keccak256(data);
    }
}
```
