当前位置: 首页 > news >正文

Foundry与Uniswap V2实战开发指南

Foundry + Uniswap V2 实战完整技术指南

目录

  1. 环境准备与搭建
    • Windows WSL Ubuntu 安装
    • 运行环境配置
    • 编译器版本管理
    • 测试工具配置
    • 部署工具配置
  2. 项目初始化与配置
    • 创建项目结构
    • 配置文件设置
    • 依赖管理
  3. Uniswap V2 交互合约开发
    • 代币查询合约
    • 代币兑换合约
    • 流动性管理合约
  4. 测试策略与实施
    • 单元测试编写
    • 集成测试编写
    • 分叉测试编写
  5. 测试网部署与测试
    • Goerli 测试网部署
    • 测试网功能验证
    • 升级测试
  6. 安全审计
    • 静态分析工具
    • 手动代码审查
    • 常见漏洞检查
  7. 正式网部署
    • 主网部署准备
    • 主网合约部署
    • 验证与监控
  8. 维护与升级
    • 监控与警报
    • 紧急响应计划
    • 合约升级策略

环境准备与搭建

Windows WSL Ubuntu 安装

  1. 启用WSL功能

    # 以管理员身份打开PowerShell
    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    
  2. 安装Ubuntu

    # 下载并安装Ubuntu 20.04 LTS
    wsl --install -d Ubuntu-20.04
    
  3. 初始设置

    # 启动Ubuntu并设置用户名和密码
    # 更新系统包
    sudo apt update && sudo apt upgrade -y
    sudo apt install build-essential git curl -y
    

运行环境配置

  1. 安装Foundry

    # 安装Foundry
    curl -L https://foundry.paradigm.xyz | bash
    source ~/.bashrc
    foundryup
    
  2. 安装Node.js和npm

    # 使用Node Version Manager (nvm) 安装Node.js
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
    source ~/.bashrc
    nvm install 18
    nvm use 18
    
  3. 安装Python和必要工具

    sudo apt install python3 python3-pip python3-venv -y
    

编译器版本管理

  1. 安装Solidity版本管理器

    # 安装svm (Solidity Version Manager)
    curl -L https://foundry.paradigm.xyz | bash
    source ~/.bashrc
    foundryup
    
  2. 配置多版本编译器

    # 安装多个Solidity版本
    svm install 0.8.19
    svm install 0.8.20
    svm install 0.8.21# 设置默认版本
    svm use 0.8.21
    
  3. 在Foundry中配置编译器

    # foundry.toml
    [profile.default]
    solc = "0.8.21"[fmt]
    solc = "0.8.21"
    

测试工具配置

  1. 安装测试工具

    # 安装Foundry标准测试库
    forge install foundry-rs/forge-std# 安装其他测试工具
    npm install -g mocha chai
    pip3 install slither-analyzer
    
  2. 配置测试环境

    # 创建测试环境配置文件
    mkdir -p config/test
    echo "TEST_NETWORK=goerli" > config/test/.env.test
    

部署工具配置

  1. 安装部署工具

    # 安装Hardhat(可选,作为Foundry的补充)
    npm install -g hardhat# 安装Truffle(可选)
    npm install -g truffle# 安装部署脚本依赖
    npm install dotenv @nomiclabs/hardhat-ethers ethers
    
  2. 配置多网络部署

    // hardhat.config.js (可选配置)
    require('@nomiclabs/hardhat-ethers');
    require('dotenv').config();module.exports = {networks: {goerli: {url: process.env.GOERLI_RPC_URL,accounts: [process.env.PRIVATE_KEY]},mainnet: {url: process.env.MAINNET_RPC_URL,accounts: [process.env.PRIVATE_KEY]}}
    };
    

项目初始化与配置

创建项目结构

# 创建项目目录结构
mkdir -p uniswapv2-integration
cd uniswapv2-integration
forge init --force
mkdir -p scripts test config deployments audit

配置文件设置

  1. 设置环境变量
    # 创建环境模板文件
    cat > .env.example << EOF
    # 网络配置
    MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-api-key
    GOERLI_RPC_URL=https://eth-goerli.g.alchemy.com/v2/your-api-key
    POLYGON_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/your-api-key# 钱包配置
    PRIVATE_KEY=your-private-key-without-0x
    DEPLOYER_ADDRESS=your-deployer-address# 验证配置
    ETHERSCAN_API_KEY=your-etherscan-api-key
    POLYGONSCAN_API_KEY=your-polygonscan-api-key# 其他配置
    GAS_LIMIT=3000000
    GAS_PRICE=50
    EOF# 复制为实际环境文件
    cp .env.example .env
    

注:1.获取网络配置

  1. Infura Infura 是一个提供 Ethereum RPC
    端点的云服务,非常适合开发者和项目。以下是获取 Infura 端点的步骤:

    1)访问 Infura (https://www.infura.io/zh)官网 并注册一个账号。
    2)登录后,你会看到一个“API Keys”的选项。点击“Create New Key”。
    3)为你的新密钥命名,选择一个计划(通常是免费的“Mainnet”计划)。
    4) 创建密钥后,你将看到一个类似于https://mainnet.infura.io/v3/YOUR_PROJECT_ID 的 URL,其中 YOUR_PROJECT_ID 是你的项目标识符。
    在这里插入图片描述
    2.获取钱包配置
    3.获取验证配置

  2. 配置Foundry

    # foundry.toml
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc = "0.8.21"
    optimizer = true
    optimizer_runs = 200
    gas_reports = ["*"][rpc_endpoints]
    mainnet = "${MAINNET_RPC_URL}"
    goerli = "${GOERLI_RPC_URL}"
    polygon = "${POLYGON_RPC_URL}"[etherscan]
    mainnet = { key = "${ETHERSCAN_API_KEY}" }
    goerli = { key = "${ETHERSCAN_API_KEY}" }
    polygon = { key = "${POLYGONSCAN_API_KEY}" }[fmt]
    line_length = 120
    tab_width = 2
    bracket_spacing = true[fuzz]
    runs = 256
    

依赖管理

  1. 安装项目依赖

    # 安装Uniswap V2接口
    forge install Uniswap/uniswap-v2-core
    forge install Uniswap/uniswap-v2-periphery# 安装OpenZeppelin合约
    forge install OpenZeppelin/openzeppelin-contracts# 安装其他有用库
    forge install foundry-rs/forge-std
    forge install PaulRBerg/prb-math
    
  2. 更新remappings.txt

    # remappings.txt
    @openzeppelin/=lib/openzeppelin-contracts/
    @uniswap/v2-core/=lib/uniswap-v2-core/
    @uniswap/v2-periphery/=lib/uniswap-v2-periphery/
    @prb/math/=lib/prb-math/src/
    forge-std/=lib/forge-std/src/
    

Uniswap V2 交互合约开发

代币查询合约

创建 src/UniswapV2Query.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";/*** @title UniswapV2Query* @dev 用于查询Uniswap V2相关信息的工具合约* @notice 这个合约提供了一系列查询功能,包括代币价格、储备量等*/
contract UniswapV2Query {using SafeMath for uint256;// Uniswap V2 Factory地址 (主网)address public constant UNISWAP_V2_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;// Uniswap V2 Router地址 (主网)address public constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;// WETH地址 (主网)address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;/*** @dev 根据两个代币地址计算Pair合约地址* @param tokenA 代币A地址* @param tokenB 代币B地址* @return pairAddress Pair合约地址*/function getPairAddress(address tokenA, address tokenB) public pure returns (address pairAddress) {// 对代币地址进行排序,确保一致性(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);// 计算salt (代币地址的keccak256哈希)bytes32 salt = keccak256(abi.encodePacked(token0, token1));// Uniswap V2的init code hashbytes32 initCodeHash = hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f";// 使用CREATE2方式计算地址pairAddress = address(uint160(uint256(keccak256(abi.encodePacked(hex"ff",UNISWAP_V2_FACTORY,salt,initCodeHash)))));}/*** @dev 查询指定交易对的储备量* @param tokenA 代币A地址* @param tokenB 代币B地址* @return reserveA 代币A的储备量* @return reserveB 代币B的储备量*/function getReserves(address tokenA, address tokenB) public view returns (uint256 reserveA, uint256 reserveB) {address pairAddress = getPairAddress(tokenA, tokenB);IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);(uint112 reserve0, uint112 reserve1, ) = pair.getReserves();(address token0, ) = sortTokens(tokenA, tokenB);(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);}/*** @dev 计算代币价格 (基于储备量)* @param tokenIn 输入代币地址* @param tokenOut 输出代币地址* @param amountIn 输入数量* @return amountOut 预计输出数量*/function getPrice(address tokenIn, address tokenOut, uint256 amountIn) public view returns (uint256 amountOut) {(uint256 reserveIn, uint256 reserveOut) = getReserves(tokenIn, tokenOut);amountOut = getAmountOut(amountIn, reserveIn, reserveOut);}/*** @dev 根据输入数量和储备量计算输出数量 (Uniswap V2公式)* @param amountIn 输入数量* @param reserveIn 输入代币储备量* @param reserveOut 输出代币储备量* @return amountOut 输出数量*/function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountOut) {require(amountIn > 0, "UniswapV2Query: INSUFFICIENT_INPUT_AMOUNT");require(reserveIn > 0 && reserveOut > 0, "UniswapV2Query: INSUFFICIENT_LIQUIDITY");uint256 amountInWithFee = amountIn.mul(997);uint256 numerator = amountInWithFee.mul(reserveOut);uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);amountOut = numerator / denominator;}/*** @dev 对两个代币地址进行排序* @param tokenA 代币A地址* @param tokenB 代币B地址* @return token0 排序后的第一个代币地址* @return token1 排序后的第二个代币地址*/function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) {require(tokenA != tokenB, "UniswapV2Query: IDENTICAL_ADDRESSES");(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);require(token0 != address(0), "UniswapV2Query: ZERO_ADDRESS");}/*** @dev 检查交易对是否存在* @param tokenA 代币A地址* @param tokenB 代币B地址* @return 是否存在*/function pairExists(address tokenA, address tokenB) public view returns (bool) {address pairAddress = getPairAddress(tokenA, tokenB);uint256 size;assembly {size := extcodesize(pairAddress)}return size > 0;}
}

代币兑换合约

创建 src/UniswapV2Swapper.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "./UniswapV2Query.sol";/*** @title UniswapV2Swapper* @dev 用于在Uniswap V2上进行代币兑换的合约* @notice 这个合约提供了安全的代币兑换功能,包括滑点保护和价格验证*/
contract UniswapV2Swapper is UniswapV2Query {using SafeMath for uint256;using SafeERC20 for IERC20;// 事件:代币兑换event Swap(address indexed sender,address tokenIn,address tokenOut,uint256 amountIn,uint256 amountOut,uint256 timestamp);// 事件:兑换失败event SwapFailed(address indexed sender,address tokenIn,address tokenOut,uint256 amountIn,string reason,uint256 timestamp);// 最大滑点容忍度 (基础点数,100 = 1%)uint256 public maxSlippageBps = 100;/*** @dev 设置最大滑点容忍度* @param slippageBps 滑点基础点数 (100 = 1%)*/function setMaxSlippage(uint256 slippageBps) external {require(slippageBps <= 500, "UniswapV2Swapper: SLIPPAGE_TOO_HIGH"); // 最大5%滑点maxSlippageBps = slippageBps;}/*** @dev 执行代币兑换* @param tokenIn 输入代币地址* @param tokenOut 输出代币地址* @param amountIn 输入数量* @param minAmountOut 最小输出数量* @param deadline 交易截止时间* @return amountOut 实际输出数量*/function swapExactTokensForTokens(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint256 deadline) external returns (uint256 amountOut) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2Swapper: EXPIRED");// 验证交易对是否存在require(pairExists(tokenIn, tokenOut), "UniswapV2Swapper: PAIR_NOT_EXISTS");// 计算预计输出数量uint256 expectedAmountOut = getPrice(tokenIn, tokenOut, amountIn);// 计算最小输出数量(考虑滑点)uint256 calculatedMinAmountOut = minAmountOut > 0 ? minAmountOut : expectedAmountOut.mul(10000 - maxSlippageBps).div(10000);// 验证最小输出数量require(expectedAmountOut >= calculatedMinAmountOut,"UniswapV2Swapper: INSUFFICIENT_OUTPUT_AMOUNT");// 转移输入代币到本合约IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);// 授权Router使用代币IERC20(tokenIn).safeApprove(UNISWAP_V2_ROUTER, amountIn);// 设置兑换路径address[] memory path = new address[](2);path[0] = tokenIn;path[1] = tokenOut;try IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokens(amountIn,calculatedMinAmountOut,path,msg.sender,deadline) returns (uint[] memory amounts) {amountOut = amounts[1];emit Swap(msg.sender, tokenIn, tokenOut, amountIn, amountOut, block.timestamp);} catch Error(string memory reason) {// 返还代币IERC20(tokenIn).safeTransfer(msg.sender, amountIn);emit SwapFailed(msg.sender, tokenIn, tokenOut, amountIn, reason, block.timestamp);revert(string(abi.encodePacked("UniswapV2Swapper: ", reason)));} catch {// 返还代币IERC20(tokenIn).safeTransfer(msg.sender, amountIn);emit SwapFailed(msg.sender, tokenIn, tokenOut, amountIn, "Unknown error", block.timestamp);revert("UniswapV2Swapper: UNKNOWN_ERROR");}}/*** @dev 执行ETH兑换代币* @param tokenOut 输出代币地址* @param minAmountOut 最小输出数量* @param deadline 交易截止时间* @return amountOut 实际输出数量*/function swapExactETHForTokens(address tokenOut,uint256 minAmountOut,uint256 deadline) external payable returns (uint256 amountOut) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2Swapper: EXPIRED");// 验证交易对是否存在require(pairExists(WETH, tokenOut), "UniswapV2Swapper: PAIR_NOT_EXISTS");// 计算预计输出数量uint256 expectedAmountOut = getPrice(WETH, tokenOut, msg.value);// 计算最小输出数量(考虑滑点)uint256 calculatedMinAmountOut = minAmountOut > 0 ? minAmountOut : expectedAmountOut.mul(10000 - maxSlippageBps).div(10000);// 验证最小输出数量require(expectedAmountOut >= calculatedMinAmountOut,"UniswapV2Swapper: INSUFFICIENT_OUTPUT_AMOUNT");// 设置兑换路径address[] memory path = new address[](2);path[0] = WETH;path[1] = tokenOut;try IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactETHForTokens{value: msg.value}(calculatedMinAmountOut,path,msg.sender,deadline) returns (uint[] memory amounts) {amountOut = amounts[1];emit Swap(msg.sender, WETH, tokenOut, msg.value, amountOut, block.timestamp);} catch Error(string memory reason) {// 返还ETHpayable(msg.sender).transfer(msg.value);emit SwapFailed(msg.sender, WETH, tokenOut, msg.value, reason, block.timestamp);revert(string(abi.encodePacked("UniswapV2Swapper: ", reason)));} catch {// 返还ETHpayable(msg.sender).transfer(msg.value);emit SwapFailed(msg.sender, WETH, tokenOut, msg.value, "Unknown error", block.timestamp);revert("UniswapV2Swapper: UNKNOWN_ERROR");}}/*** @dev 紧急提取代币(仅限合约所有者)* @param token 代币地址* @param amount 提取数量*/function emergencyWithdraw(address token, uint256 amount) external {// 注意:在实际合约中,应该添加onlyOwner修饰符IERC20(token).safeTransfer(msg.sender, amount);}// 接收ETHreceive() external payable {}
}

流动性管理合约

创建 src/UniswapV2LiquidityManager.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "./UniswapV2Query.sol";/*** @title UniswapV2LiquidityManager* @dev 用于管理Uniswap V2流动性的合约* @notice 这个合约提供了添加和移除流动性的功能,包括自动计算最优数量*/
contract UniswapV2LiquidityManager is UniswapV2Query {using SafeMath for uint256;using SafeERC20 for IERC20;// 事件:流动性添加event LiquidityAdded(address indexed provider,address tokenA,address tokenB,uint256 amountA,uint256 amountB,uint256 liquidity,uint256 timestamp);// 事件:流动性移除event LiquidityRemoved(address indexed provider,address tokenA,address tokenB,uint256 amountA,uint256 amountB,uint256 liquidity,uint256 timestamp);/*** @dev 添加流动性* @param tokenA 代币A地址* @param tokenB 代币B地址* @param amountADesired 期望的代币A数量* @param amountBDesired 期望的代币B数量* @param amountAMin 最小的代币A数量* @param amountBMin 最小的代币B数量* @param deadline 交易截止时间* @return amountA 实际添加的代币A数量* @return amountB 实际添加的代币B数量* @return liquidity 获得的流动性代币数量*/function addLiquidity(address tokenA,address tokenB,uint256 amountADesired,uint256 amountBDesired,uint256 amountAMin,uint256 amountBMin,uint256 deadline) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2LiquidityManager: EXPIRED");// 转移代币到本合约IERC20(tokenA).safeTransferFrom(msg.sender, address(this), amountADesired);IERC20(tokenB).safeTransferFrom(msg.sender, address(this), amountBDesired);// 授权Router使用代币IERC20(tokenA).safeApprove(UNISWAP_V2_ROUTER, amountADesired);IERC20(tokenB).safeApprove(UNISWAP_V2_ROUTER, amountBDesired);// 调用Router添加流动性(amountA, amountB, liquidity) = IUniswapV2Router02(UNISWAP_V2_ROUTER).addLiquidity(tokenA,tokenB,amountADesired,amountBDesired,amountAMin,amountBMin,msg.sender,deadline);// 返还剩余代币if (amountADesired > amountA) {IERC20(tokenA).safeTransfer(msg.sender, amountADesired - amountA);}if (amountBDesired > amountB) {IERC20(tokenB).safeTransfer(msg.sender, amountBDesired - amountB);}emit LiquidityAdded(msg.sender, tokenA, tokenB, amountA, amountB, liquidity, block.timestamp);}/*** @dev 添加ETH流动性* @param token 代币地址* @param amountTokenDesired 期望的代币数量* @param amountTokenMin 最小的代币数量* @param amountETHMin 最小的ETH数量* @param deadline 交易截止时间* @return amountToken 实际添加的代币数量* @return amountETH 实际添加的ETH数量* @return liquidity 获得的流动性代币数量*/function addLiquidityETH(address token,uint256 amountTokenDesired,uint256 amountTokenMin,uint256 amountETHMin,uint256 deadline) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2LiquidityManager: EXPIRED");// 转移代币到本合约IERC20(token).safeTransferFrom(msg.sender, address(this), amountTokenDesired);// 授权Router使用代币IERC20(token).safeApprove(UNISWAP_V2_ROUTER, amountTokenDesired);// 调用Router添加ETH流动性(amountToken, amountETH, liquidity) = IUniswapV2Router02(UNISWAP_V2_ROUTER).addLiquidityETH{value: msg.value}(token,amountTokenDesired,amountTokenMin,amountETHMin,msg.sender,deadline);// 返还剩余代币if (amountTokenDesired > amountToken) {IERC20(token).safeTransfer(msg.sender, amountTokenDesired - amountToken);}// 返还剩余ETHif (msg.value > amountETH) {payable(msg.sender).transfer(msg.value - amountETH);}emit LiquidityAdded(msg.sender, token, WETH, amountToken, amountETH, liquidity, block.timestamp);}/*** @dev 移除流动性* @param tokenA 代币A地址* @param tokenB 代币B地址* @param liquidity 要移除的流动性数量* @param amountAMin 最小的代币A数量* @param amountBMin 最小的代币B数量* @param deadline 交易截止时间* @return amountA 实际获得的代币A数量* @return amountB 实际获得的代币B数量*/function removeLiquidity(address tokenA,address tokenB,uint256 liquidity,uint256 amountAMin,uint256 amountBMin,uint256 deadline) external returns (uint256 amountA, uint256 amountB) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2LiquidityManager: EXPIRED");// 获取Pair地址address pairAddress = getPairAddress(tokenA, tokenB);// 转移流动性代币到本合约IERC20(pairAddress).safeTransferFrom(msg.sender, address(this), liquidity);// 授权Router使用流动性代币IERC20(pairAddress).safeApprove(UNISWAP_V2_ROUTER, liquidity);// 调用Router移除流动性(amountA, amountB) = IUniswapV2Router02(UNISWAP_V2_ROUTER).removeLiquidity(tokenA,tokenB,liquidity,amountAMin,amountBMin,msg.sender,deadline);emit LiquidityRemoved(msg.sender, tokenA, tokenB, amountA, amountB, liquidity, block.timestamp);}/*** @dev 移除ETH流动性* @param token 代币地址* @param liquidity 要移除的流动性数量* @param amountTokenMin 最小的代币数量* @param amountETHMin 最小的ETH数量* @param deadline 交易截止时间* @return amountToken 实际获得的代币数量* @return amountETH 实际获得的ETH数量*/function removeLiquidityETH(address token,uint256 liquidity,uint256 amountTokenMin,uint256 amountETHMin,uint256 deadline) external returns (uint256 amountToken, uint256 amountETH) {// 验证截止时间require(deadline >= block.timestamp, "UniswapV2LiquidityManager: EXPIRED");// 获取Pair地址address pairAddress = getPairAddress(token, WETH);// 转移流动性代币到本合约IERC20(pairAddress).safeTransferFrom(msg.sender, address(this), liquidity);// 授权Router使用流动性代币IERC20(pairAddress).safeApprove(UNISWAP_V2_ROUTER, liquidity);// 调用Router移除ETH流动性(amountToken, amountETH) = IUniswapV2Router02(UNISWAP_V2_ROUTER).removeLiquidityETH(token,liquidity,amountTokenMin,amountETHMin,msg.sender,deadline);emit LiquidityRemoved(msg.sender, token, WETH, amountToken, amountETH, liquidity, block.timestamp);}/*** @dev 计算最优的添加流动性数量* @param tokenA 代币A地址* @param tokenB 代币B地址* @param amountADesired 期望的代币A数量* @param amountBDesired 期望的代币B数量* @return amountA 最优的代币A数量* @return amountB 最优的代币B数量*/function quoteOptimalAmounts(address tokenA,address tokenB,uint256 amountADesired,uint256 amountBDesired) public view returns (uint256 amountA, uint256 amountB) {(uint256 reserveA, uint256 reserveB) = getReserves(tokenA, tokenB);if (reserveA == 0 && reserveB == 0) {// 如果池子是空的,使用期望的数量return (amountADesired, amountBDesired);}uint256 amountBOptimal = quote(amountADesired, reserveA, reserveB);if (amountBOptimal <= amountBDesired) {// 使用代币A的数量计算最优的代币B数量return (amountADesired, amountBOptimal);} else {// 使用代币B的数量计算最优的代币A数量uint256 amountAOptimal = quote(amountBDesired, reserveB, reserveA);assert(amountAOptimal <= amountADesired);return (amountAOptimal, amountBDesired);}}/*** @dev 根据储备量计算最优数量 (Uniswap V2公式)* @param amountA 输入数量* @param reserveA 输入代币储备量* @param reserveB 输出代币储备量* @return amountB 输出数量*/function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) public pure returns (uint256 amountB) {require(amountA > 0, "UniswapV2LiquidityManager: INSUFFICIENT_AMOUNT");require(reserveA > 0 && reserveB > 0, "UniswapV2LiquidityManager: INSUFFICIENT_LIQUIDITY");amountB = amountA.mul(reserveB) / reserveA;}
}

测试策略与实施

单元测试编写

创建 test/UniswapV2Query.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "forge-std/Test.sol";
import "../src/UniswapV2Query.sol";/*** @title UniswapV2QueryTest* @dev UniswapV2Query合约的单元测试*/
contract UniswapV2QueryTest is Test {UniswapV2Query public query;// 主网地址address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;function setUp() public {// 创建主网分叉vm.createSelectFork(vm.rpcUrl("mainnet"));// 部署查询合约query = new UniswapV2Query();}function testGetPairAddress() public {// 测试获取WETH/DAI交易对地址address pairAddress = query.getPairAddress(WETH, DAI);// 已知的WETH/DAI交易对地址address knownPair = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11;// 验证地址计算正确assertEq(pairAddress, knownPair);}function testGetReserves() public {// 测试获取WETH/DAI储备量(uint256 reserveWETH, uint256 reserveDAI) = query.getReserves(WETH, DAI);// 验证储备量大于0assertGt(reserveWETH, 0);assertGt(reserveDAI, 0);// 输出储备量信息console.log("WETH Reserve:", reserveWETH);console.log("DAI Reserve:", reserveDAI);}function testGetPrice() public {// 测试获取价格 (1 WETH = ? DAI)uint256 amountIn = 1 ether; // 1 WETHuint256 amountOut = query.getPrice(WETH, DAI, amountIn);// 验证输出数量大于0assertGt(amountOut, 0);// 输出价格信息console.log("1 WETH =", amountOut / 1e18, "DAI");}function testPairExists() public {// 测试检查交易对是否存在bool exists = query.pairExists(WETH, DAI);// 验证交易对存在assertTrue(exists);// 测试不存在的交易对bool notExists = query.pairExists(WETH, address(0x123));// 验证交易对不存在assertFalse(notExists);}function testSortTokens() public {// 测试代币地址排序(address token0, address token1) = query.sortTokens(WETH, DAI);// 验证排序正确assertEq(token0, DAI);assertEq(token1, WETH);// 测试反向排序(address token0Reverse, address token1Reverse) = query.sortTokens(DAI, WETH);// 验证排序一致assertEq(token0, token0Reverse);assertEq(token1, token1Reverse);}
}

集成测试编写

创建 test/UniswapV2Swapper.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "forge-std/Test.sol";
import "../src/UniswapV2Swapper.sol";/*** @title UniswapV2SwapperTest* @dev UniswapV2Swapper合约的集成测试*/
contract UniswapV2SwapperTest is Test {UniswapV2Swapper public swapper;// 主网地址address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;// 测试用户address user = address(0x123);function setUp() public {// 创建主网分叉vm.createSelectFork(vm.rpcUrl("mainnet"));// 部署Swapper合约swapper = new UniswapV2Swapper();// 给测试用户一些WETHdeal(WETH, user, 10 ether);}function testSwapExactTokensForTokens() public {// 切换至测试用户vm.startPrank(user);// 授权Swapper合约使用WETHIERC20(WETH).approve(address(swapper), 1 ether);// 记录初始余额uint256 initialWETH = IERC20(WETH).balanceOf(user);uint256 initialDAI = IERC20(DAI).balanceOf(user);// 执行兑换uint256 amountOut = swapper.swapExactTokensForTokens(WETH,DAI,1 ether, // 1 WETH0, // 最小输出数量block.timestamp + 1 hours);// 记录最终余额uint256 finalWETH = IERC20(WETH).balanceOf(user);uint256 finalDAI = IERC20(DAI).balanceOf(user);// 验证WETH减少assertEq(initialWETH - finalWETH, 1 ether);// 验证DAI增加assertGt(finalDAI, initialDAI);assertEq(finalDAI - initialDAI, amountOut);// 输出兑换信息console.log("WETH spent:", 1 ether / 1e18);console.log("DAI received:", amountOut / 1e18);vm.stopPrank();}function testSwapExactETHForTokens() public {// 切换至测试用户vm.startPrank(user);// 记录初始余额uint256 initialETH = user.balance;uint256 initialDAI = IERC20(DAI).balanceOf(user);// 执行ETH兑换uint256 amountOut = swapper.swapExactETHForTokens{value: 1 ether}(DAI,0, // 最小输出数量block.timestamp + 1 hours);// 记录最终余额uint256 finalETH = user.balance;uint256 finalDAI = IERC20(DAI).balanceOf(user);// 验证ETH减少assertEq(initialETH - finalETH, 1 ether);// 验证DAI增加assertGt(finalDAI, initialDAI);assertEq(finalDAI - initialDAI, amountOut);// 输出兑换信息console.log("ETH spent:", 1 ether / 1e18);console.log("DAI received:", amountOut / 1e18);vm.stopPrank();}function testSwapWithSlippageProtection() public {// 切换至测试用户vm.startPrank(user);// 设置较低的滑点容忍度 (0.1%)swapper.setMaxSlippage(10);// 授权Swapper合约使用WETHIERC20(WETH).approve(address(swapper), 1 ether);// 计算预计输出uint256 expectedOut = swapper.getPrice(WETH, DAI, 1 ether);// 执行兑换(应该会失败,因为滑点太低)vm.expectRevert();swapper.swapExactTokensForTokens(WETH,DAI,1 ether,0, // 最小输出数量block.timestamp + 1 hours);vm.stopPrank();}function testEmergencyWithdraw() public {// 给Swapper合约一些代币deal(DAI, address(swapper), 1000 ether);// 记录初始余额uint256 initialBalance = IERC20(DAI).balanceOf(address(this));// 执行紧急提取swapper.emergencyWithdraw(DAI, 1000 ether);// 记录最终余额uint256 finalBalance = IERC20(DAI).balanceOf(address(this));// 验证代币已提取assertEq(finalBalance - initialBalance, 1000 ether);}
}

分叉测试编写

创建 test/ForkTest.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "forge-std/Test.sol";
import "../src/UniswapV2LiquidityManager.sol";/*** @title ForkTest* @dev 主网分叉测试*/
contract ForkTest is Test {UniswapV2LiquidityManager public liquidityManager;// 主网地址address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;// 测试用户address user = address(0x123);function setUp() public {// 创建主网分叉vm.createSelectFork(vm.rpcUrl("mainnet"));// 部署流动性管理器liquidityManager = new UniswapV2LiquidityManager();// 给测试用户一些代币deal(USDC, user, 10000 * 10**6); // 10,000 USDCdeal(DAI, user, 10000 ether); // 10,000 DAIdeal(WETH, user, 10 ether); // 10 WETH}function testAddLiquidity() public {// 切换至测试用户vm.startPrank(user);// 授权流动性管理器使用代币IERC20(USDC).approve(address(liquidityManager), 1000 * 10**6);IERC20(DAI).approve(address(liquidityManager), 1000 ether);// 记录初始余额uint256 initialUSDC = IERC20(USDC).balanceOf(user);uint256 initialDAI = IERC20(DAI).balanceOf(user);// 添加流动性(uint256 amountA, uint256 amountB, uint256 liquidity) = liquidityManager.addLiquidity(USDC,DAI,1000 * 10**6, // 1000 USDC1000 ether, // 1000 DAI0, // 最小USDC数量0, // 最小DAI数量block.timestamp + 1 hours);// 记录最终余额uint256 finalUSDC = IERC20(USDC).balanceOf(user);uint256 finalDAI = IERC20(DAI).balanceOf(user);// 验证代币减少assertEq(initialUSDC - finalUSDC, amountA);assertEq(initialDAI - finalDAI, amountB);// 验证流动性大于0assertGt(liquidity, 0);// 输出流动性信息console.log("USDC added:", amountA / 10**6);console.log("DAI added:", amountB / 1e18);console.log("Liquidity received:", liquidity);vm.stopPrank();}function testAddLiquidityETH() public {// 切换至测试用户vm.startPrank(user);// 授权流动性管理器使用代币IERC20(USDC).approve(address(liquidityManager), 1000 * 10**6);// 记录初始余额uint256 initialUSDC = IERC20(USDC).balanceOf(user);uint256 initialETH = user.balance;// 添加ETH流动性(uint256 amountToken, uint256 amountETH, uint256 liquidity) = liquidityManager.addLiquidityETH{value: 1 ether}(USDC,1000 * 10**6, // 1000 USDC0, // 最小USDC数量0, // 最小ETH数量block.timestamp + 1 hours);// 记录最终余额uint256 finalUSDC = IERC20(USDC).balanceOf(user);uint256 finalETH = user.balance;// 验证代币减少assertEq(initialUSDC - finalUSDC, amountToken);assertEq(initialETH - finalETH, amountETH);// 验证流动性大于0assertGt(liquidity, 0);// 输出流动性信息console.log("USDC added:", amountToken / 10**6);console.log("ETH added:", amountETH / 1e18);console.log("Liquidity received:", liquidity);vm.stopPrank();}function testRemoveLiquidity() public {// 首先添加流动性testAddLiquidity();// 切换至测试用户vm.startPrank(user);// 获取Pair地址address pairAddress = liquidityManager.getPairAddress(USDC, DAI);// 获取流动性数量uint256 liquidity = IERC20(pairAddress).balanceOf(user);// 授权流动性管理器使用流动性代币IERC20(pairAddress).approve(address(liquidityManager), liquidity);// 记录初始余额uint256 initialUSDC = IERC20(USDC).balanceOf(user);uint256 initialDAI = IERC20(DAI).balanceOf(user);// 移除流动性(uint256 amountA, uint256 amountB) = liquidityManager.removeLiquidity(USDC,DAI,liquidity,0, // 最小USDC数量0, // 最小DAI数量block.timestamp + 1 hours);// 记录最终余额uint256 finalUSDC = IERC20(USDC).balanceOf(user);uint256 finalDAI = IERC20(DAI).balanceOf(user);// 验证代币增加assertEq(finalUSDC - initialUSDC, amountA);assertEq(finalDAI - initialDAI, amountB);// 输出移除流动性信息console.log("USDC received:", amountA / 10**6);console.log("DAI received:", amountB / 1e18);vm.stopPrank();}function testQuoteOptimalAmounts() public {// 计算最优数量(uint256 amountA, uint256 amountB) = liquidityManager.quoteOptimalAmounts(USDC,DAI,1000 * 10**6, // 1000 USDC1000 ether // 1000 DAI);// 验证数量大于0assertGt(amountA, 0);assertGt(amountB, 0);// 输出最优数量信息console.log("Optimal USDC:", amountA / 10**6);console.log("Optimal DAI:", amountB / 1e18);}
}

运行测试:

# 运行单元测试
forge test --match-test testGetPairAddress -vvv# 运行集成测试
forge test --match-test testSwapExactTokensForTokens -vvv# 运行分叉测试
forge test --match-test testAddLiquidity -vvv# 运行所有测试
forge test -vvv

测试网部署与测试

Goerli 测试网部署

  1. 配置测试网环境

    # 创建测试网部署脚本
    mkdir -p script/deploy
    touch script/deploy/DeployGoerli.s.sol
    
  2. 编写部署脚本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";
    import "../../src/UniswapV2Query.sol";
    import "../../src/UniswapV2Swapper.sol";
    import "../../src/UniswapV2LiquidityManager.sol";/*** @title DeployGoerli* @dev 部署合约到Goerli测试网*/
    contract DeployGoerli is Script {function run() external {// 从环境变量获取部署者私钥uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");// 开始广播交易vm.startBroadcast(deployerPrivateKey);// 部署UniswapV2QueryUniswapV2Query query = new UniswapV2Query();console.log("UniswapV2Query deployed at:", address(query));// 部署UniswapV2SwapperUniswapV2Swapper swapper = new UniswapV2Swapper();console.log("UniswapV2Swapper deployed at:", address(swapper));// 部署UniswapV2LiquidityManagerUniswapV2LiquidityManager liquidityManager = new UniswapV2LiquidityManager();console.log("UniswapV2LiquidityManager deployed at:", address(liquidityManager));// 停止广播vm.stopBroadcast();}
    }
    
  3. 执行部署

    # 部署到Goerli测试网
    forge script script/deploy/DeployGoerli.s.sol:DeployGoerli --rpc-url goerli --broadcast --verify -vvvv# 如果需要验证合约,添加Etherscan API密钥
    ETHERSCAN_API_KEY=your-etherscan-api-key forge script script/deploy/DeployGoerli.s.sol:DeployGoerli --rpc-url goerli --broadcast --verify -vvvv
    

测试网功能验证

  1. 创建验证脚本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";/*** @title VerifyGoerli* @dev 验证Goerli测试网上的合约功能*/
    contract VerifyGoerli is Script {// Goerli测试网上的合约地址address constant QUERY_ADDRESS = 0x...;address constant SWAPPER_ADDRESS = 0x...;address constant LIQUIDITY_MANAGER_ADDRESS = 0x...;// Goerli测试网上的代币地址address constant WETH = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6;address constant DAI = 0x...; // 需要部署测试DAI代币function run() external {// 从环境变量获取测试用户私钥uint256 userPrivateKey = vm.envUint("TEST_PRIVATE_KEY");// 开始广播交易vm.startBroadcast(userPrivateKey);// 测试查询功能UniswapV2Query query = UniswapV2Query(QUERY_ADDRESS);address pairAddress = query.getPairAddress(WETH, DAI);console.log("WETH/DAI Pair Address:", pairAddress);// 测试兑换功能UniswapV2Swapper swapper = UniswapV2Swapper(SWAPPER_ADDRESS);// 授权Swapper使用WETHIERC20(WETH).approve(SWAPPER_ADDRESS, 0.1 ether);// 执行兑换uint256 amountOut = swapper.swapExactTokensForTokens(WETH,DAI,0.1 ether,0,block.timestamp + 1 hours);console.log("DAI received:", amountOut);// 停止广播vm.stopBroadcast();}
    }
    
  2. 执行验证

    # 运行验证脚本
    forge script script/deploy/VerifyGoerli.s.sol:VerifyGoerli --rpc-url goerli --broadcast -vvvv
    

升级测试

  1. 创建可升级合约版本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
    import "./UniswapV2Query.sol";/*** @title UniswapV2SwapperUpgradeable* @dev 可升级版本的UniswapV2Swapper*/
    contract UniswapV2SwapperUpgradeable is Initializable, OwnableUpgradeable, UniswapV2Query {using SafeMath for uint256;using SafeERC20 for IERC20;// 最大滑点容忍度uint256 public maxSlippageBps;// 事件:代币兑换event Swap(address indexed sender,address tokenIn,address tokenOut,uint256 amountIn,uint256 amountOut,uint256 timestamp);/*** @dev 初始化函数*/function initialize() public initializer {__Ownable_init();maxSlippageBps = 100; // 默认1%滑点}/*** @dev 设置最大滑点容忍度*/function setMaxSlippage(uint256 slippageBps) external onlyOwner {require(slippageBps <= 500, "SLIPPAGE_TOO_HIGH");maxSlippageBps = slippageBps;}// 其他函数与UniswapV2Swapper类似...
    }
    
  2. 创建升级部署脚本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";
    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
    import "../../src/upgradeable/UniswapV2SwapperUpgradeable.sol";/*** @title DeployUpgradeable* @dev 部署可升级合约到Goerli测试网*/
    contract DeployUpgradeable is Script {function run() external {uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");vm.startBroadcast(deployerPrivateKey);// 部署实现合约UniswapV2SwapperUpgradeable implementation = new UniswapV2SwapperUpgradeable();// 部署代理合约ERC1967Proxy proxy = new ERC1967Proxy(address(implementation),abi.encodeWithSelector(UniswapV2SwapperUpgradeable.initialize.selector));console.log("Implementation deployed at:", address(implementation));console.log("Proxy deployed at:", address(proxy));vm.stopBroadcast();}
    }
    
  3. 测试升级功能

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";
    import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
    import "../../src/upgradeable/UniswapV2SwapperUpgradeable.sol";
    import "../../src/upgradeable/UniswapV2SwapperUpgradeableV2.sol";/*** @title TestUpgrade* @dev 测试合约升级功能*/
    contract TestUpgrade is Script {address constant PROXY_ADDRESS = 0x...;function run() external {uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");vm.startBroadcast(deployerPrivateKey);// 创建代理合约实例ERC1967Proxy proxy = ERC1967Proxy(payable(PROXY_ADDRESS));UniswapV2SwapperUpgradeable swapper = UniswapV2SwapperUpgradeable(address(proxy));// 检查当前版本console.log("Current max slippage:", swapper.maxSlippageBps());// 部署新版本实现合约UniswapV2SwapperUpgradeableV2 implementationV2 = new UniswapV2SwapperUpgradeableV2();// 升级代理合约swapper.upgradeTo(address(implementationV2));// 初始化新版本swapper.initializeV2();// 检查新功能console.log("New feature value:", swapper.newFeature());vm.stopBroadcast();}
    }
    

安全审计

静态分析工具

  1. 使用Slither进行静态分析

    # 安装Slither
    pip3 install slither-analyzer# 分析合约
    slither src/UniswapV2Swapper.sol
    
  2. 使用Mythril进行安全分析

    # 安装Mythril
    pip3 install mythril# 分析合约
    myth analyze src/UniswapV2Swapper.sol --solc-json remappings.json
    
  3. 使用Foundry内置安全检查

    # 使用Foundry进行安全检查
    forge inspect src/UniswapV2Swapper.sol:UniswapV2Swapper storage-layout
    forge inspect src/UniswapV2Swapper.sol:UniswapV2Swapper methods
    

手动代码审查

  1. 重入攻击检查

    // 检查所有外部调用是否遵循"检查-效果-交互"模式
    function swapExactTokensForTokens(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint256 deadline
    ) external returns (uint256 amountOut) {// 检查阶段require(deadline >= block.timestamp, "EXPIRED");require(pairExists(tokenIn, tokenOut), "PAIR_NOT_EXISTS");// 效果阶段uint256 expectedAmountOut = getPrice(tokenIn, tokenOut, amountIn);uint256 calculatedMinAmountOut = minAmountOut > 0 ? minAmountOut : expectedAmountOut.mul(10000 - maxSlippageBps).div(10000);require(expectedAmountOut >= calculatedMinAmountOut,"INSUFFICIENT_OUTPUT_AMOUNT");// 交互阶段IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);IERC20(tokenIn).safeApprove(UNISWAP_V2_ROUTER, amountIn);// ...
    }
    
  2. 整数溢出检查

    // 使用SafeMath防止整数溢出
    using SafeMath for uint256;function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountOut) 
    {require(amountIn > 0, "INSUFFICIENT_INPUT_AMOUNT");require(reserveIn > 0 && reserveOut > 0, "INSUFFICIENT_LIQUIDITY");// 使用SafeMath进行数学运算uint256 amountInWithFee = amountIn.mul(997);uint256 numerator = amountInWithFee.mul(reserveOut);uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);amountOut = numerator / denominator;
    }
    
  3. 访问控制检查

    // 添加onlyOwner修饰符保护关键功能
    function setMaxSlippage(uint256 slippageBps) external onlyOwner {require(slippageBps <= 500, "SLIPPAGE_TOO_HIGH");maxSlippageBps = slippageBps;
    }function emergencyWithdraw(address token, uint256 amount) external onlyOwner {IERC20(token).safeTransfer(msg.sender, amount);
    }
    

常见漏洞检查

  1. 价格操纵攻击

    // 添加滑点保护
    function swapExactTokensForTokens(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint256 deadline
    ) external returns (uint256 amountOut) {// 计算预计输出uint256 expectedAmountOut = getPrice(tokenIn, tokenOut, amountIn);// 应用滑点保护uint256 calculatedMinAmountOut = minAmountOut > 0 ? minAmountOut : expectedAmountOut.mul(10000 - maxSlippageBps).div(10000);require(expectedAmountOut >= calculatedMinAmountOut,"INSUFFICIENT_OUTPUT_AMOUNT");// ...
    }
    
  2. 前端运行攻击防护

    // 设置合理的截止时间
    function swapExactTokensForTokens(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint256 deadline
    ) external returns (uint256 amountOut) {// 验证截止时间require(deadline >= block.timestamp, "EXPIRED");// 设置合理的截止时间(不要太长)require(deadline <= block.timestamp + 1 hours, "DEADLINE_TOO_LONG");// ...
    }
    
  3. 拒绝服务攻击防护

    // 避免在循环中进行外部调用
    function batchSwap(address[] calldata tokensIn,address[] calldata tokensOut,uint256[] calldata amountsIn,uint256[] calldata minAmountsOut,uint256 deadline
    ) external {require(tokensIn.length == tokensOut.length, "ARRAY_LENGTH_MISMATCH");require(tokensIn.length == amountsIn.length, "ARRAY_LENGTH_MISMATCH");require(tokensIn.length == minAmountsOut.length, "ARRAY_LENGTH_MISMATCH");// 限制批量操作的大小require(tokensIn.length <= 10, "BATCH_TOO_LARGE");for (uint256 i = 0; i < tokensIn.length; i++) {swapExactTokensForTokens(tokensIn[i],tokensOut[i],amountsIn[i],minAmountsOut[i],deadline);}
    }
    

正式网部署

主网部署准备

  1. 最终安全审查

    # 运行最终安全检查
    slither src/ --exclude-dependencies
    myth analyze src/UniswapV2Swapper.sol
    forge test --fork-url mainnet -vvv
    
  2. 部署检查清单

    # 部署检查清单
    echo "主网部署检查清单:"
    echo "1. 合约已通过所有测试"
    echo "2. 安全审计已完成"
    echo "3. 环境变量已正确设置"
    echo "4. 部署脚本已测试"
    echo "5. 备份计划已准备"
    
  3. 配置主网部署脚本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";
    import "../src/UniswapV2Query.sol";
    import "../src/UniswapV2Swapper.sol";
    import "../src/UniswapV2LiquidityManager.sol";/*** @title DeployMainnet* @dev 部署合约到以太坊主网*/
    contract DeployMainnet is Script {function run() external {// 记录开始时间console.log("开始主网部署...");console.log("区块号:", block.number);console.log("时间戳:", block.timestamp);// 从环境变量获取部署者私钥uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");address deployer = vm.addr(deployerPrivateKey);console.log("部署者地址:", deployer);// 检查Gas价格uint256 gasPrice = block.gasprice;console.log("当前Gas价格:", gasPrice);// 开始广播交易vm.startBroadcast(deployerPrivateKey);// 部署UniswapV2Queryconsole.log("正在部署UniswapV2Query...");UniswapV2Query query = new UniswapV2Query();console.log("UniswapV2Query已部署:", address(query));// 部署UniswapV2Swapperconsole.log("正在部署UniswapV2Swapper...");UniswapV2Swapper swapper = new UniswapV2Swapper();console.log("UniswapV2Swapper已部署:", address(swapper));// 部署UniswapV2LiquidityManagerconsole.log("正在部署UniswapV2LiquidityManager...");UniswapV2LiquidityManager liquidityManager = new UniswapV2LiquidityManager();console.log("UniswapV2LiquidityManager已部署:", address(liquidityManager));// 停止广播vm.stopBroadcast();// 记录部署完成console.log("主网部署完成!");console.log("总Gas消耗:", tx.gasprice * tx.gaslimit);}
    }
    

主网合约部署

  1. 执行主网部署

    # 部署到以太坊主网
    forge script script/deploy/DeployMainnet.s.sol:DeployMainnet --rpc-url mainnet --broadcast --verify -vvvv# 使用更高的Gas价格
    forge script script/deploy/DeployMainnet.s.sol:DeployMainnet --rpc-url mainnet --broadcast --verify --gas-price 1000000000 -vvvv
    
  2. 验证合约

    # 验证已部署的合约
    forge verify-contract <CONTRACT_ADDRESS> src/UniswapV2Swapper.sol:UniswapV2Swapper --etherscan-api-key $ETHERSCAN_API_KEY --chain-id 1# 批量验证所有合约
    ./scripts/verify-all.sh
    

验证与监控

  1. 创建验证脚本

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "forge-std/Script.sol";/*** @title VerifyMainnet* @dev 验证主网上的合约功能*/
    contract VerifyMainnet is Script {// 主网合约地址address constant QUERY_ADDRESS = 0x...;address constant SWAPPER_ADDRESS = 0x...;address constant LIQUIDITY_MANAGER_ADDRESS = 0x...;// 主网代币地址address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;function run() external {// 从环境变量获取测试用户私钥uint256 userPrivateKey = vm.envUint("TEST_PRIVATE_KEY");address user = vm.addr(userPrivateKey);console.log("测试用户地址:", user);// 开始广播交易vm.startBroadcast(userPrivateKey);// 测试查询功能UniswapV2Query query = UniswapV2Query(QUERY_ADDRESS);address pairAddress = query.getPairAddress(WETH, DAI);console.log("WETH/DAI交易对地址:", pairAddress);(uint256 reserveWETH, uint256 reserveDAI) = query.getReserves(WETH, DAI);console.log("WETH储备量:", reserveWETH);console.log("DAI储备量:", reserveDAI);uint256 price = query.getPrice(WETH, DAI, 1 ether);console.log("1 WETH价格:", price / 1e18, "DAI");// 测试兑换功能UniswapV2Swapper swapper = UniswapV2Swapper(SWAPPER_ADDRESS);// 授权Swapper使用WETHIERC20(WETH).approve(SWAPPER_ADDRESS, 0.01 ether);console.log("已授权Swapper使用WETH");// 记录初始余额uint256 initialWETH = IERC20(WETH).balanceOf(user);uint256 initialDAI = IERC20(DAI).balanceOf(user);console.log("初始WETH余额:", initialWETH / 1e18);console.log("初始DAI余额:", initialDAI / 1e18);// 执行兑换uint256 amountOut = swapper.swapExactTokensForTokens(WETH,DAI,0.01 ether, // 0.01 WETH0, // 最小输出数量block.timestamp + 1 hours);console.log("兑换完成,获得DAI:", amountOut / 1e18);// 记录最终余额uint256 finalWETH = IERC20(WETH).balanceOf(user);uint256 finalDAI = IERC20(DAI).balanceOf(user);console.log("最终WETH余额:", finalWETH / 1e18);console.log("最终DAI余额:", finalDAI / 1e18);// 验证余额变化require(finalWETH == initialWETH - 0.01 ether, "WETH余额不正确");require(finalDAI == initialDAI + amountOut, "DAI余额不正确");console.log("所有测试通过!");// 停止广播vm.stopBroadcast();}
    }
    
  2. 设置监控和警报

    # 创建监控脚本
    touch scripts/monitor.sh
    chmod +x scripts/monitor.sh
    
    #!/bin/bash
    # scripts/monitor.sh# 监控合约余额
    CONTRACT_ADDRESS="0x..."
    ABI='[{"constant":true,"inputs":[],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"type":"function"}]'# 获取合约余额
    BALANCE=$(cast call $CONTRACT_ADDRESS "getBalance()" --rpc-url $MAINNET_RPC_URL)
    echo "合约余额: $BALANCE"# 检查余额是否低于阈值
    if [ $BALANCE -lt 1000000000000000000 ]; thenecho "警告: 合约余额过低!"# 发送通知curl -X POST -H "Content-Type: application/json" \-d '{"text":"合约余额过低: '$BALANCE'"}' \$SLACK_WEBHOOK_URL
    fi
    
  3. 设置定时监控任务

    # 添加cron任务 (每小時执行一次)
    crontab -e
    # 添加以下行
    0 * * * * /bin/bash /path/to/uniswapv2-integration/scripts/monitor.sh
    

维护与升级

监控与警报

  1. 设置事件监控

    // 在合约中添加事件
    event AdminWithdraw(address indexed admin, address token, uint256 amount, uint256 timestamp);
    event SlippageUpdated(uint256 oldSlippage, uint256 newSlippage, uint256 timestamp);function emergencyWithdraw(address token, uint256 amount) external onlyOwner {uint256 balance = IERC20(token).balanceOf(address(this));require(amount <= balance, "Insufficient balance");IERC20(token).safeTransfer(owner(), amount);// 记录管理员提取事件emit AdminWithdraw(owner(), token, amount, block.timestamp);
    }function setMaxSlippage(uint256 slippageBps) external onlyOwner {require(slippageBps <= 500, "SLIPPAGE_TOO_HIGH");// 记录滑点更新事件emit SlippageUpdated(maxSlippageBps, slippageBps, block.timestamp);maxSlippageBps = slippageBps;
    }
    
  2. 创建事件监听脚本

    // scripts/monitor-events.js
    const { ethers } = require("ethers");
    require("dotenv").config();const provider = new ethers.providers.JsonRpcProvider(process.env.MAINNET_RPC_URL);
    const contractAddress = process.env.CONTRACT_ADDRESS;// 合约ABI (只需要事件部分)
    const abi = ["event AdminWithdraw(address indexed admin, address token, uint256 amount, uint256 timestamp)","event SlippageUpdated(uint256 oldSlippage, uint256 newSlippage, uint256 timestamp)"
    ];const contract = new ethers.Contract(contractAddress, abi, provider);// 监听AdminWithdraw事件
    contract.on("AdminWithdraw", (admin, token, amount, timestamp) => {console.log(`管理员提取: ${admin} 提取了 ${amount} 个代币 ${token}`);// 发送警报sendAlert(`管理员提取警报: ${admin} 提取了 ${amount} 个代币 ${token}`);
    });// 监听SlippageUpdated事件
    contract.on("SlippageUpdated", (oldSlippage, newSlippage, timestamp) => {console.log(`滑点更新: 从 ${oldSlippage} 更新为 ${newSlippage}`);// 发送警报sendAlert(`滑点更新警报: 从 ${oldSlippage} 更新为 ${newSlippage}`);
    });function sendAlert(message) {// 发送到Slack或其他通知服务console.log("发送警报:", message);
    }console.log("开始监听合约事件...");
    

紧急响应计划

  1. 创建紧急暂停功能

    // 在合约中添加暂停功能
    bool public paused;modifier whenNotPaused() {require(!paused, "CONTRACT_PAUSED");_;
    }function pause() external onlyOwner {paused = true;emit ContractPaused(block.timestamp);
    }function unpause() external onlyOwner {paused = false;emit ContractUnpaused(block.timestamp);
    }// 在关键函数中添加暂停检查
    function swapExactTokensForTokens(address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,uint256 deadline
    ) external whenNotPaused returns (uint256 amountOut) {// 函数逻辑...
    }
    
  2. 创建紧急响应脚本

    #!/bin/bash
    # scripts/emergency-pause.sh# 紧急暂停合约
    echo "执行紧急暂停..."# 调用合约的pause函数
    cast send $CONTRACT_ADDRESS "pause()" \--rpc-url $MAINNET_RPC_URL \--private-key $EMERGENCY_KEYecho "合约已暂停"# 发送通知
    curl -X POST -H "Content-Type: application/json" \-d '{"text":"合约已紧急暂停"}' \$SLACK_WEBHOOK_URL
    

合约升级策略

  1. 使用可升级合约模式

    // 可升级合约示例
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.21;import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
    import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";contract UniswapV2SwapperUpgradeable is Initializable, OwnableUpgradeable, PausableUpgradeable 
    {// 初始化函数function initialize() public initializer {__Ownable_init();__Pausable_init();}// 新版本添加的功能function newFeature() public pure returns (string memory) {return "This is a new feature added in upgrade";}// 升级后初始化函数function initializeV2() public reinitializer(2) {// 新版本的初始化逻辑}
    }
    
  2. 创建升级管理脚本

    #!/bin/bash
    # scripts/upgrade-contract.sh# 合约升级脚本
    echo "开始合约升级..."# 部署新实现合约
    NEW_IMPL=$(forge create src/UniswapV2SwapperUpgradeable.sol:UniswapV2SwapperUpgradeable \--rpc-url $MAINNET_RPC_URL \--private-key $DEPLOYER_KEY \| grep "Deployed to:" | awk '{print $3}')echo "新实现合约已部署: $NEW_IMPL"# 升级代理合约
    cast send $PROXY_ADDRESS "upgradeTo(address)" $NEW_IMPL \--rpc-url $MAINNET_RPC_URL \--private-key $UPGRADER_KEYecho "代理合约已升级"# 初始化新版本
    cast send $PROXY_ADDRESS "initializeV2()" \--rpc-url $MAINNET_RPC_URL \--private-key $UPGRADER_KEYecho "新版本已初始化"# 验证升级
    cast call $PROXY_ADDRESS "newFeature()" --rpc-url $MAINNET_RPC_URLecho "合约升级完成"
    
  3. 测试升级过程

    # 在测试网上测试升级
    forge script script/upgrade/TestUpgrade.s.sol:TestUpgrade --rpc-url goerli --broadcast -vvvv# 验证升级后功能
    forge script script/upgrade/VerifyUpgrade.s.sol:VerifyUpgrade --rpc-url goerli -vvvv
    

这份完整的技术指南涵盖了从环境搭建到正式网部署的全过程,包括详细的代码注释、测试策略、安全审计和升级方案。在实际应用中,请根据具体需求调整和完善各个部分。

http://www.dtcms.com/a/341801.html

相关文章:

  • 【自记】Power BI 中 DISTINCT 和 ALLNOBLANKROW 的区别说明
  • 比特分割 + 尖峰保留:FlashCommunication V2 实现任意比特通信与 3.2× 加速
  • 一键授权登录
  • Windows暂停更新10年最简单的设置
  • UNet改进(33):基于CBAM原理与PyTorch实战指南
  • 可信数据空间关键技术和功能架构研究
  • RAG流程全解析:从数据到精准答案
  • 地区电影市场分析:用Python爬虫抓取猫眼_灯塔专业版各地区票房
  • 不止效率工具:AI 在创意领域的 “叛逆生长”—— 从文案生成到艺术创作的突围
  • 【蒸蒸日上】专栏前言
  • 我的创作纪念日-2048天
  • 动态规划----6.单词拆分
  • 关于 Flask 3.0+的 框架的一些复习差异点
  • 在 Linux 和 Docker 中部署 MinIO 对象存储
  • 深入解析:生产环境 SQL 数据库的架构设计与工程实践
  • 税务专业人员能力构建与发展路径指南
  • ubuntu系统上的conda虚拟环境导出方便下次安装
  • 【网络运维】Linux 文本搜索利器: grep命令
  • JavaBean中首字母小写第二个字母大写属性转换异常详解
  • GIT总结一键式命令清单(顺序执行)
  • redis---常用数据类型及内部编码
  • 官网SSO登录系统的企业架构设计全过程
  • 七十四、【Linux数据库】MySQL数据库存储引擎
  • 11让LLM更懂FunctionCalling返回值
  • S32K3 的图形化配置和EB配置mcal差异
  • week2-[二维数组]排队
  • MySQL/Kafka数据集成同步,增量同步及全量同步
  • Windows 如何清理右键菜单?电脑桌面右键菜单里出现一个清理内存 怎么去掉?
  • 数据结构中邻接矩阵中的无向图和有向图
  • 流固耦合|01流固耦合分类