You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Uniswap addLiquidityETH传大额ETH的风险及合约分费实现疑问

Uniswap手续费分配合约的风险疑问

我正在开发一款在Uniswap买卖环节收取手续费的智能合约,部分手续费会返还至流动性池(LP),剩余部分分配给3个接收方。为实现手续费动态分配,我设计了如下流程:

  • 将所有收取的手续费兑换为ETH;
  • 调用addLiquidityETH时传入全部兑换所得ETH(多余ETH会自动返回),以此添加LP份额;
  • 按比例将剩余ETH分配给3个接收方。

这套方案可拆分合约内所有可用ETH(包括转入合约的ETH),但根据Uniswap文档,addLiquidityETH会按交易执行时的价格以理想比例添加资产,我不确定该实现的风险与后果,同时想了解调用addLiquidityETH传入大额ETH参数的风险。以下是代码片段:

function _distributeFee() internal nonReentrant {
    swapTokensForETH(totalDevAmount + totalMarketingAmount + totalOwnerAmount + (totalLiquidity / 2));

    uint256 swapEthBalance = address(this).balance;

    if (totalLiquidity > 0) {
        addLiquidity((totalLiquidity / 2), swapEthBalance);
        totalLiquidity = 0;
    }

    swapEthBalance = address(this).balance;
    
    // send fees to their recipients
    uint256 devFeeETHAmount = swapEthBalance / (devFeePercent + marketingFeePercent + ownerFeePercent) * (devFeePercent);
    uint256 marketingFeeETHAmount = swapEthBalance / (devFeePercent + marketingFeePercent + ownerFeePercent) * (marketingFeePercent);
    uint256 ownerFeeETHAmount = swapEthBalance / (devFeePercent + marketingFeePercent + ownerFeePercent) * (ownerFeePercent);
    payable(devFeeRecipient).transfer(devFeeETHAmount);
    payable(marketingFeeRecipient).transfer(marketingFeeETHAmount);
    payable(ownerFeeRecipient).transfer(ownerFeeETHAmount);
}

function swapTokensForETH(uint256 tokenAmount) private {
    address[] memory path = new address[](2);
    path[0] = address(this);
    path[1] = uniswapV2Router.WETH();
    _approve(address(this), address(uniswapV2Router), tokenAmount);
    uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
        tokenAmount,
        0, 
        path,
        address(this),
        block.timestamp
    );
}

function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
    // approve token transfer to cover all possible scenarios
    _approve(address(this), address(uniswapV2Router), tokenAmount);

    // add the liquidity
    uniswapV2Router.addLiquidityETH{value: ethAmount}(
        address(this),
        tokenAmount,
        0, // slippage is unavoidable
        0, // slippage is unavoidable
        address(0), // send LP token to burn address
        block.timestamp
    );
}

核心风险分析

1. addLiquidityETH传入全额ETH的潜在问题

  • 价格滑点与资金错配:Uniswap V2的addLiquidityETH会依据当前池内代币与ETH的储备比例,计算添加流动性所需的精确资产量。若传入的ETH远多于对应tokenAmount所需的量,多余ETH虽会返回,但交易过程中若池内价格因MEV抢跑、大额交易等因素波动,可能导致实际用于LP的ETH和代币比例偏离预期,甚至出现代币量不足,大部分ETH被退回,LP添加量远低于计划的情况。
  • 不必要的Gas消耗:传入大额ETH会触发Uniswap完整的流动性计算逻辑,即便大部分ETH被退回,仍需支付对应计算步骤的Gas费。当ETH金额极大时,Gas消耗会显著上升,可能导致交易因Gas不足失败,或吃掉大量手续费利润。

2. 手续费兑换与LP添加的时序风险

  • ETH余额计算误差:swapTokensForETH使用swapExactTokensForETHSupportingFeeOnTransferTokens直接将ETH转入合约,但如果合约此前存在用户误转的残留ETH,swapEthBalance = address(this).balance会包含这部分非手续费资金,导致后续addLiquidity传入的ETH金额混入无关资金,打乱LP分配比例。
  • LP份额不可逆锁仓:代码中将LP代币发送至销毁地址(address(0)),这部分流动性会永久锁仓。虽能减少流通量,但完全丧失后续调整LP策略(如移除流动性)的操作空间,属于不可逆决策。

3. 手续费分配的精度与资金滞留问题

  • 整数除法截断误差:Solidity的整数除法会丢弃小数部分,用swapEthBalance / 总百分比 * 单项百分比的计算方式,会导致当swapEthBalance无法被总百分比整除时,少量ETH留在合约中,长期积累会造成资金滞留。
  • 无剩余资金处理逻辑:分配后若合约仍有剩余ETH,代码未提供任何处理方式,这部分资金会永久留在合约内,无法被利用或提取。

4. 安全风险

  • 重入风险:虽使用nonReentrant修饰器,但transfer函数在处理某些合约接收方时仍可能触发重入(Solidity 0.8+的transfer限制了Gas,风险较低,但仍需警惕)。
  • 授权权限冗余:addLiquidity函数每次调用都重新授权tokenAmount给Uniswap路由,若tokenAmount为大额,单次授权虽无直接风险,但如果合约存在其他漏洞,可能被恶意利用转移代币。

优化建议

  • 计算精准ETH需求量:调用addLiquidityETH前,通过Uniswap的getReserves函数获取当前池的储备量,计算对应tokenAmount所需的精确ETH数量,仅传入该金额,避免多余ETH的处理和Gas浪费。
  • 隔离手续费与残留ETH:在兑换手续费前记录合约初始ETH余额,兑换后用address(this).balance - initialBalance得到手续费兑换的ETH金额,避免混入合约内的其他资金。
  • 修复精度问题:先计算总分配金额,依次扣除各接收方的份额后,将剩余ETH分配给其中一方(如合约所有者),确保合约无资金滞留。
  • 保留LP调整灵活性:考虑将LP代币发送至可控地址而非直接销毁,以便后续根据需求调整流动性策略。

内容的提问来源于stack exchange,提问作者Lopjf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 18:24:56