UniswapV2部署与测试本文将详细说明如何使用Foundry在本地和Sepolia测试网上部署UniswapV2合约。✅已验证:本文档中的所有命令已在2025-12-01成功执行并通过测试。相关测试合约在本地及Sepolia测试网均已通过测试验证。
本文将详细说明如何使用 Foundry 在本地和 Sepolia 测试网上部署 Uniswap V2 合约。
✅ 已验证: 本文档中的所有命令已在 2025-12-01 成功执行并通过测试。

├── src/
│ ├── v2-core/ # Uniswap V2 核心合约 (Solidity 0.5.16)
│ ├── v2-periphery/ # Uniswap V2 外围合约 (Solidity 0.6.6)
│ └── WETH9.sol # WETH 合约 (本地测试用)
├── script/
│ ├── DeployUniswapV2.s.sol # 完整部署脚本
│ ├── 01_DeployFactory.s.sol # 分步部署 - Factory
│ ├── 02_DeployRouter.s.sol # 分步部署 - Router
│ └── 03_DeployWETH.s.sol # 分步部署 - WETH
├── test/
│ └── UniswapV2.t.sol # 集成测试
├── DEPLOYMENT.md # 详细部署文档
└── foundry.toml # Foundry 配置
| 合约 | 路径 | Solidity 版本 | 说明 |
|---|---|---|---|
UniswapV2Factory |
src/v2-core/contracts/ |
0.5.16 | 工厂合约,用于创建交易对 |
UniswapV2Pair |
src/v2-core/contracts/ |
0.5.16 | 交易对合约(由 Factory 自动部署) |
UniswapV2Router02 |
src/v2-periphery/contracts/ |
0.6.6 | 路由合约,提供用户交互接口 |
1. 部署 WETH(本地需要,Sepolia 使用已有地址)
2. 部署 UniswapV2Factory
3. 更新 init code hash(重要!)
4. 部署 UniswapV2Router02
⚠️ 重要: UniswapV2Library.sol 中硬编码了 Pair 合约的 init code hash,用于计算交易对地址。如果使用 Foundry 编译,hash 值会与原版不同,必须更新。
当前项目的 init code hash:
0x25aad938d8616b6e59148d3e701e4966de4418a752233589352d7c616a256568
原版 Uniswap V2 的 hash:
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f
# Windows (使用 foundryup)
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 验证安装
forge --version
cast --version
anvil --version
⚠️ 项目依赖foundry框架来部署和测试,若未安装成功foundry,请到官方仓库https://github.com/foundry-rs/foundry/releases 进行下载,windows系统下载foundry_nightly_win32_amd64包,解压后配置系统变量即可全局使用
复制 .env.example 为 .env 并填入你的值:
# 私钥(可以包含 0x 前缀)
PRIVATE_KEY=0xyour_private_key_here
# Sepolia RPC URL(可使用 Alchemy、Infura 等)
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your-api-key
# Etherscan API Key(用于合约验证)
ETHERSCAN_API_KEY=your_etherscan_api_key
⚠️ 安全提示:
.env添加到.gitignore- 永远不要在公开仓库中暴露私钥
- 测试网使用专门的测试钱包
forge init --no-git uniswap_v2 (如需git提交 后续再使用git init 命令)
Uniswap V2 核心 v2-core 和 v2-periphery 的仓库代码需要克隆到初始化后的src目录中 https://github.com/Uniswap/v2-core https://github.com/Uniswap/v2-periphery
forge install Uniswap/solidity-lib --no-commit
forge build
注意:
lib/solidity-lib目录已包含必要的依赖文件,无需额外安装。
# 在新终端窗口启动 Anvil
anvil
Anvil 默认配置:
http://127.0.0.1:8545313370xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff800xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266# Step 1: 部署 WETH
forge create src/WETH9.sol:WETH9 \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast
# Step 2: 部署 Factory(将 constructor-args 替换为你的地址)
forge create src/v2-core/contracts/UniswapV2Factory.sol:UniswapV2Factory \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast \
--constructor-args 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
# Step 3: 部署 Router(替换 FACTORY_ADDRESS 和 WETH_ADDRESS)
forge create src/v2-periphery/contracts/UniswapV2Router02.sol:UniswapV2Router02 \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast \
--constructor-args <FACTORY_ADDRESS> <WETH_ADDRESS>
# 查询 Router 的 factory 地址
cast call <ROUTER_ADDRESS> "factory()(address)" --rpc-url http://127.0.0.1:8545
# 查询 Router 的 WETH 地址
cast call <ROUTER_ADDRESS> "WETH()(address)" --rpc-url http://127.0.0.1:8545
| 合约 | 地址 |
|---|---|
| WETH (Sepolia) | 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9 |
以下是实际执行成功的部署命令:
# Step 1: 部署 Factory
forge create src/v2-core/contracts/UniswapV2Factory.sol:UniswapV2Factory \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY> \
--broadcast \
--constructor-args <YOUR_ADDRESS>
# Step 2: 部署 Router
forge create src/v2-periphery/contracts/UniswapV2Router02.sol:UniswapV2Router02 \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY> \
--broadcast \
--constructor-args <FACTORY_ADDRESS> 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9
# 验证 Router 配置
cast call <ROUTER_ADDRESS> "factory()(address)" \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY>
cast call <ROUTER_ADDRESS> "WETH()(address)" \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY>
以下是在 Sepolia 上实际执行成功的完整测试流程。
# 部署 Token A (1,000,000 个)
forge create src/TestToken.sol:TestToken \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY> \
--broadcast \
--constructor-args "Token A" "TKA" 1000000000000000000000000
# 部署 Token B (1,000,000 个)
forge create src/TestToken.sol:TestToken \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY> \
--broadcast \
--constructor-args "Token B" "TKB" 1000000000000000000000000
# 通过 Factory 创建交易对
cast send <FACTORY_ADDRESS> \
"createPair(address,address)" \
<TOKEN_A_ADDRESS> <TOKEN_B_ADDRESS> \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY>
# 查询交易对地址
cast call <FACTORY_ADDRESS> \
"getPair(address,address)(address)" \
<TOKEN_A_ADDRESS> <TOKEN_B_ADDRESS> \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY>
# 授权 Router 使用 Token A (授权大额度)
cast send <TOKEN_A_ADDRESS> \
"approve(address,uint256)" \
<ROUTER_ADDRESS> 1000000000000000000000000 \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY>
# 授权 Router 使用 Token B
cast send <TOKEN_B_ADDRESS> \
"approve(address,uint256)" \
<ROUTER_ADDRESS> 1000000000000000000000000 \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY>
# 添加 100 Token A + 100 Token B 的流动性
# deadline 使用一个足够大的时间戳 (9999999999)
cast send <ROUTER_ADDRESS> \
"addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256)" \
<TOKEN_A_ADDRESS> \
<TOKEN_B_ADDRESS> \
100000000000000000000 \
100000000000000000000 \
0 \
0 \
<YOUR_ADDRESS> \
9999999999 \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY>
# 用 1 个 Token A 换 Token B
cast send <ROUTER_ADDRESS> \
"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)" \
1000000000000000000 \
0 \
"[<TOKEN_A_ADDRESS>,<TOKEN_B_ADDRESS>]" \
<YOUR_ADDRESS> \
9999999999 \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY> \
--private-key <YOUR_PRIVATE_KEY>
# 查询 Token B 余额
cast call <TOKEN_B_ADDRESS> \
"balanceOf(address)(uint256)" \
<YOUR_ADDRESS> \
--rpc-url https://sepolia.infura.io/v3/<YOUR_API_KEY>
forge create ... \
--verify \
--etherscan-api-key <YOUR_ETHERSCAN_API_KEY>
# 验证 Factory
forge verify-contract <FACTORY_ADDRESS> \
src/v2-core/contracts/UniswapV2Factory.sol:UniswapV2Factory \
--chain sepolia \
--constructor-args $(cast abi-encode "constructor(address)" <FEE_TO_SETTER>) \
--etherscan-api-key <YOUR_ETHERSCAN_API_KEY>
# 验证 Router
forge verify-contract <ROUTER_ADDRESS> \
src/v2-periphery/contracts/UniswapV2Router02.sol:UniswapV2Router02 \
--chain sepolia \
--constructor-args $(cast abi-encode "constructor(address,address)" <FACTORY> <WETH>) \
--etherscan-api-key <YOUR_ETHERSCAN_API_KEY>
症状: addLiquidity 或 swap 交易失败,无明确错误信息。
原因: Router 中的 UniswapV2Library.sol 硬编码了 init code hash,与实际编译的 Pair 合约不匹配。
解决方案:
forge inspect src/v2-core/contracts/UniswapV2Pair.sol:UniswapV2Pair bytecode > pair_bytecode.txt
$bytecode = (Get-Content pair_bytecode.txt -Raw).Trim()
if (-not $bytecode.StartsWith("0x")) { $bytecode = "0x" + $bytecode }
cast keccak256 $bytecode
src/v2-periphery/contracts/libraries/UniswapV2Library.sol 第 24 行:hex'25aad938d8616b6e59148d3e701e4966de4418a752233589352d7c616a256568' // init code hash
症状: 错误信息 UniswapV2Router: EXPIRED
解决方案: 使用更大的 deadline 值,如 9999999999
# 方式一:直接设置
$env:PRIVATE_KEY = "0x..."
$env:SEPOLIA_RPC_URL = "https://..."
# 方式二:从 .env 文件加载
$envContent = Get-Content .env | Where-Object { $_ -match '=' -and $_ -notmatch '^#' }
foreach ($line in $envContent) {
$parts = $line -split '=', 2
if ($parts.Count -eq 2) {
[Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim(), 'Process')
}
}
forge create ... --gas-limit 500000
| 合约 | 地址 | 状态 |
|---|---|---|
| Factory | 0x9A267db279FE7d11138f9293784460Df577c3198 |
✅ 已部署 |
| Router | 0x3c66Fe68778281be4358F21BfB63aE9cD242aB58 |
✅ 已部署 |
| WETH | 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9 |
(已有) |
| Token A (测试) | 0x3aBe45b63e723eF24680afeA45B98D51bd398Bdd |
✅ 已部署 |
| Token B (测试) | 0x64e6775b20Ed22ec10bca887400E6F5790112e75 |
✅ 已部署 |
| Pair (TKA/TKB) | 0xF3efCad26af4AA64F2eE7a99f2051b45e9057040 |
✅ 已创建 |
Init Code Hash: 0x25aad938d8616b6e59148d3e701e4966de4418a752233589352d7c616a256568
测试结果:
Etherscan 链接:
本项目源码已同步github,供参考:https://github.com/lookteas/uniswap_v2
forge --version).env 配置正确UniswapV2Library.sol 的 init code hash 已更新Router.factory() 返回正确的 Factory 地址Router.WETH() 返回正确的 WETH 地址如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!