欢迎光临
我们一直在努力

WTF-Solidity 第24讲:在 Solidity 合约中创建新合约(CREATE 操作码与极简 Uniswap 工厂模式)

WTF-Solidity 第24讲:在 Solidity 合约中创建新合约(CREATE 操作码与极简 Uniswap 工厂模式)

【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy 【免费下载链接】WTF-Solidity 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

在以太坊上,不仅用户(外部账户 EOA)可以部署智能合约,合约自身同样可以创建新的合约——这正是去中心化交易所 Uniswap 借助工厂合约(PairFactory)批量创建币对合约(Pair)的底层能力。本讲基于 WTF-Solidity 教程第 24 讲,先讲透最基础的 create 方法语法与底层地址生成原理,再用一个可编译、可部署的极简 Uniswap 实例(完整源码见 24_Create/Create.sol)演示工厂模式的落地,最后给出在 Remix 上的完整验证步骤。学完本讲,你将掌握"合约创建合约"的核心写法,并能自行实现类似 Uniswap V2 的工厂-子合约架构。

为什么合约能创建合约

以太坊上的账户分为两类:

  • 外部账户(EOA):由私钥控制,可以发起交易,也可以创建智能合约;
  • 合约账户:由代码控制,同样拥有调用 create / create2 操作码的能力。
  • Uniswap V2 的核心合约正是利用了后者的能力:UniswapV2Factory(工厂合约)负责创建并管理所有 UniswapV2Pair(币对合约),后者则负责管理币对地址、流动性以及买卖逻辑。每上线一个代币交易对,工厂合约就在链上"生"出一个新的币对合约,这就是"合约创建合约"最典型的工业级应用。

    Solidity 中创建新合约有两种方法:create 和 create2。本讲聚焦 create,create2 会在仓库 25_Create2/readme.md 中展开。

    create 语法详解

    create 的用法非常直接:用 new 关键字实例化一个合约,并传入新合约构造函数所需的参数:

    Contract x = new Contract{value: _value}(params)

    其中各部分的含义如下:

    语法元素说明
    Contract 要创建的合约名(需在编译单元内可见)
    x 新创建合约的对象引用,其地址可通过 address(x) 获取
    {value: _value} 可选。当新合约构造函数声明为 payable 时,可在创建的同时转入 _value 数量的 ETH
    params 新合约构造函数的入参

    这条语句会在当前交易的上下文中触发一次新的合约部署,消耗一笔额外的 gas(由合约创建、存储读写和初始化代码执行产生)。

    极简 Uniswap:用 create 实现工厂模式

    参照 Uniswap V2 的核心架构,仓库 24_Create/Create.sol 用两个合约演示了完整的"工厂创建子合约"流程:

    • Pair:币对合约,负责管理币对地址;
    • PairFactory:工厂合约,负责创建新的币对并登记管理。

    Pair 币对合约

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.34;

    contract Pair{
    address public factory; // 工厂合约地址
    address public token0; // 代币1
    address public token1; // 代币2

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

    // called once by the factory at time of deployment
    function initialize(address _token0, address _token1) external {
    require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
    token0 = _token0;
    token1 = _token1;
    }
    }

    对应源码见 24_Create/Create.sol#L4-L19。Pair 合约本身很精简,只包含三个状态变量:

    • factory:部署它的工厂合约地址。构造函数中通过 msg.sender 直接捕获创建者——也就是 PairFactory 合约的地址;
    • token0 / token1:币对中的两种代币地址;
    • initialize:由工厂在部署完成后手动调用一次,用于设置代币地址。函数内用 require(msg.sender == factory, 'UniswapV2: FORBIDDEN') 做权限检查,确保只有当初部署它的工厂合约才能初始化,防止外部任意调用篡改币对。

    提问:为什么 Uniswap 不在 constructor 中直接把 token0 和 token1 地址设置好,而要额外提供一个 initialize 函数?

    答:因为 Uniswap 实际使用的是 create2 创建合约,生成的合约地址可以事先预测。而 create2 的地址计算依赖的是创建者地址、salt 和 initcode,与构造函数参数无关,因此需要把"设置币种参数"这一步骤拆到部署完成之后、由工厂显式调用的 initialize 中完成。详细原理参见仓库 25_Create2/readme.md 及其配套源码 25_Create2/Create2.sol。

    PairFactory 工厂合约

    contract PairFactory{
    mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址
    address[] public allPairs; // 保存所有Pair地址

    function createPair(address tokenA, address tokenB) external returns (address pairAddr) {
    // 创建新合约
    Pair pair = new Pair();
    // 调用新合约的initialize方法
    pair.initialize(tokenA, tokenB);
    // 更新地址map
    pairAddr = address(pair);
    allPairs.push(pairAddr);
    getPair[tokenA][tokenB] = pairAddr;
    getPair[tokenB][tokenA] = pairAddr;
    }
    }

    对应源码见 24_Create/Create.sol#L21-L36。工厂合约维护两个关键状态变量:

    • getPair:tokenA -> tokenB -> pairAddr 的双层 mapping,支持双向查询(创建时同时写入 getPair[tokenA][tokenB] 与 getPair[tokenB][tokenA]),方便根据任意一对代币快速定位币对地址;
    • allPairs:存储所有币对地址的数组,用于遍历全部币对。

    核心逻辑集中在唯一的 createPair 函数中,它完成四件事:

  • 创建新合约:Pair pair = new Pair();——这就是"合约创建合约"的核心一行;
  • 初始化币对:调用新 Pair 的 initialize(tokenA, tokenB),写入两种代币的地址;
  • 登记地址:将 pairAddr 追加进 allPairs 数组;
  • 建立索引:在 getPair 映射中双向登记,供后续按代币查币对。
  • create 地址生成的底层原理

    create 创建的合约地址并不是随机的,而是由创建者信息决定的。依据仓库 25_Create2/readme.md 中的说明,通过 create 创建的新合约地址计算方式为:

    新地址 = hash(创建者地址, nonce)

    其中 nonce 对于外部账户是该地址发送交易的总数,对于合约账户则是其创建合约的总数(每创建一个合约 nonce 加一)。由于创建者地址固定不变、但 nonce 会随时间改变,因此用 create 创建的合约地址无法在部署前稳定预测——这正是 create 与 create2 的核心差异,也是 Uniswap 选择 create2 的原因:它能把币对地址变成 (tokenA, tokenB) 的确定性函数,使 Router 无需跨合约调用 getPair 即可算出地址。关于 create2 的 0xFF + 创建者地址 + salt + initcode 四要素地址公式,可继续阅读 25_Create2/readme.md。

    在 Remix 上验证极简 Uniswap

    动手验证前,先用下面两个地址作为 createPair 的入参(教程实测使用的示例地址):

    WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78
    BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c

    第 1 步:调用 createPair 获取新币对地址

    部署 PairFactory 合约后,在 Remix 的 DEPLOY & RUN TRANSACTIONS 面板中调用 createPair,分别传入 WBNB 和 PEOPLE 的地址,返回的 decoded output 即为新创建的 Pair 合约地址(教程实测得到 0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48):

    Remix 调用 createPair 创建币对并返回 Pair 地址

    第 2 步:用 At Address 加载 Pair 合约

    将 Contract 下拉框切换为 Pair,在 At Address 输入框中粘贴上一步得到的 Pair 合约地址,点击按钮即可为已部署合约创建前端调用接口:

    Remix 中通过 At Address 加载新创建的 Pair 合约

    第 3 步:查看 Pair 合约的变量

    在加载出的 Pair 合约实例上,可以依次查看 factory(应为 PairFactory 地址)、token0 和 token1(应分别为传入的两种代币地址),从而确认工厂合约已正确创建并初始化了币对:

    查看 Pair 合约的 factory、token0 与 token1 状态变量

    第 4 步:Debug 查看 create 操作码

    对刚才的 createPair 交易打开 DEBUGGER,在 EVM trace 中可以看到 0x02 CREATE 操作码——这正是新 Pair 合约通过 create 指令完成部署的直接证据:

    通过 Debugger 在 EVM trace 中查看 CREATE 操作码

    总结

    本讲以极简 Uniswap 为例,完整演示了如何在合约中通过 create 创建新合约:Pair 币对合约负责记录工厂地址与两种代币地址,PairFactory 工厂合约则通过 new Pair() 批量部署币对,并用 mapping 与数组对币对地址做双向索引与管理。核心语法 Contract x = new Contract{value: _value}(params) 简单直接,但底层地址由 hash(创建者地址, nonce) 决定、无法事先预测,这也为下一讲 create2(可在部署前确定合约地址)埋下了伏笔——Uniswap 真实使用的正是 create2,读者可继续阅读 25_Create2/readme.md 深入了解。

    【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy 【免费下载链接】WTF-Solidity 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:171主机测评 » WTF-Solidity 第24讲:在 Solidity 合约中创建新合约(CREATE 操作码与极简 Uniswap 工厂模式)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址