WTF-Solidity 第24讲:在 Solidity 合约中创建新合约(CREATE 操作码与极简 Uniswap 工厂模式)
【免费下载链接】WTF-Solidity WTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
在以太坊上,不仅用户(外部账户 EOA)可以部署智能合约,合约自身同样可以创建新的合约——这正是去中心化交易所 Uniswap 借助工厂合约(PairFactory)批量创建币对合约(Pair)的底层能力。本讲基于 WTF-Solidity 教程第 24 讲,先讲透最基础的 create 方法语法与底层地址生成原理,再用一个可编译、可部署的极简 Uniswap 实例(完整源码见 24_Create/Create.sol)演示工厂模式的落地,最后给出在 Remix 上的完整验证步骤。学完本讲,你将掌握"合约创建合约"的核心写法,并能自行实现类似 Uniswap V2 的工厂-子合约架构。
为什么合约能创建合约
以太坊上的账户分为两类:
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 函数中,它完成四件事:
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):

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

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

第 4 步:Debug 查看 create 操作码
对刚才的 createPair 交易打开 DEBUGGER,在 EVM trace 中可以看到 0x02 CREATE 操作码——这正是新 Pair 合约通过 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 项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考


