UniswapV1 自学系列 06 - 手续费机制实现

  • 青山
  • 发布于 18小时前
  • 阅读 56

UniswapV1自学系列06-手续费机制实现前言在前面的系列文章中,我们已经实现了基础的流动性管理和代币交换功能。本文将深入探讨UniswapV1的手续费机制,包括手续费的收取方式、分配机制以及具体的代码实现。核心问题分析在实现手续费机制之前,我们需要思考几个关键问题:1.

UniswapV1 自学系列 06 - 手续费机制实现

前言

在前面的系列文章中,我们已经实现了基础的流动性管理和代币交换功能。本文将深入探讨 UniswapV1 的手续费机制,包括手续费的收取方式、分配机制以及具体的代码实现。

核心问题分析

在实现手续费机制之前,我们需要思考几个关键问题:

1. 手续费收取方式

  • 我们应该使用 ETH 还是 ERC20 代币收取手续费?
  • 流动性提供者的奖励应该以什么形式支付?

2. 手续费收集机制

  • 如何从每次交换中收取少量固定手续费?
  • 手续费的计算和扣除应该在哪个环节进行?

3. 手续费分配机制

  • 如何按照贡献比例向流动性提供者分配累积的手续费?
  • 分配算法应该如何设计?

尽管这些问题看起来复杂,但实际上我们已经具备了解决所有问题的基础设施。

设计思路分析

手续费收取策略

我们可以采用一种巧妙的方式来处理手续费:不需要额外的支付,只需要从发送到合约的 ETH 或代币中直接扣除手续费

核心思路:

  • 交易者已经向交易所合约发送了 ETH 或代币
  • 我们可以直接从这些资产中扣除手续费,而不是要求额外支付
  • 这种方式简化了交互流程,提升了用户体验

手续费存储机制

交易所储备本身就是一个天然的手续费累积池:

储备池的双重作用:

  1. 流动性储备:维护 AMM 机制所需的资产池
  2. 手续费累积池:自动积累所有交易产生的手续费

这意味着储备会随着时间增长,使得常数乘积公式不再"恒定"。但这并不会破坏机制的有效性,因为:

  • 手续费相对于储备规模较小
  • 无法通过操控手续费来显著改变储备比例
  • 增长的储备实际上增强了系统的稳定性

手续费分配原则

现在我们可以回答第一个问题:手续费以交易资产的货币形式支付

流动性提供者的收益包括:

  • 等比例的 ETH 和代币
  • 与其 LP 代币份额成比例的累积手续费

代码实现

手续费参数设置

Uniswap V1 实际收取 0.3% 的手续费,但为了便于测试观察效果,我们设置为 1%。

getAmount 函数改进

在价格计算函数中添加手续费逻辑非常简单,只需要添加几个乘数:

/**
 * @notice 计算考虑手续费后的输出金额
 * @param inputAmount 输入金额
 * @param inputReserve 输入资产储备量
 * @param outputReserve 输出资产储备量
 * @return 扣除手续费后的输出金额
 */
function getAmount(
    uint256 inputAmount,
    uint256 inputReserve,
    uint256 outputReserve
) private pure returns (uint256) {
    require(inputReserve > 0 && outputReserve > 0, "invalid reserves");

    // 计算扣除手续费后的输入金额(1% 手续费,保留 99%)
    uint256 inputAmountWithFee = inputAmount * 99;

    // 基于常数乘积公式计算输出金额
    uint256 numerator = inputAmountWithFee * outputReserve;
    uint256 denominator = (inputReserve * 100) + inputAmountWithFee;

    return numerator / denominator;
}

手续费计算原理

由于 Solidity 不支持浮点数运算,我们需要使用整数运算技巧来实现手续费计算。

理论公式:

扣费后金额 = 输入金额 × (100 - 手续费率) / 100

Solidity 实现:

扣费后金额 = (输入金额 × (100 - 手续费率)) / 100

在我们的实现中:

  • 手续费率为 1%
  • 保留比例为 99%
  • inputAmountWithFee = inputAmount * 99 相当于 inputAmount * (100-1)

这种实现方式巧妙地避免了浮点数运算,同时保持了计算的精确性。

手续费对 AMM 公式的影响

在传统的 AMM 公式中:

x * y = k(常数)

加入手续费后,公式变为:

(x + Δx) * (y - Δy) = k + 手续费增长

其中手续费会让储备池逐渐增长,但不会破坏价格发现机制的有效性。

测试验证

让我们通过测试来验证手续费机制的正确性。

测试准备

首先需要确保我们的测试环境已经正确配置:

# 编译合约
forge build

# 运行测试
forge test --match-test testFees -v

手续费测试用例

/**
 * @notice 测试手续费收取功能
 */
function testFees() public {
    // 添加初始流动性(不能提前转代币到合约)
    token.approve(address(exchange), 2000 ether);
    exchange.addLiquidity{value: 1000 ether}(2000 ether);

    // 记录交换前的储备
    uint256 ethReserveBefore = address(exchange).balance;
    uint256 tokenReserveBefore = token.balanceOf(address(exchange));

    // 切换到用户身份执行交换
    vm.startPrank(user);

    // 用户批准代币给交易所
    uint256 tokenAmount = 100 ether;
    token.approve(address(exchange), tokenAmount);

    // 记录用户交换前的 ETH 余额
    uint256 userEthBefore = user.balance;

    // 执行代币到 ETH 的交换
    exchange.tokenToEthSwap(tokenAmount, 1);

    // 计算用户实际收到的 ETH
    uint256 ethReceived = user.balance - userEthBefore;

    vm.stopPrank();

    // 记录交换后的储备
    uint256 ethReserveAfter = address(exchange).balance;
    uint256 tokenReserveAfter = token.balanceOf(address(exchange));

    // 验证手续费机制
    // ETH储备:应该精确等于初始储备减去用户收到的ETH
    assertEq(ethReserveAfter, ethReserveBefore - ethReceived);

    // Token储备:应该等于初始储备加上用户支付的token
    assertEq(tokenReserveAfter, tokenReserveBefore + tokenAmount);

    // 验证用户确实收到了ETH(并且由于1%手续费,收到的应该少于无手续费情况)
    assertTrue(ethReceived > 0);

    // 计算无手续费情况下应该收到的ETH
    uint256 ethWithoutFee = (tokenAmount * ethReserveBefore) / tokenReserveBefore;

    // 验证由于手续费,实际收到的ETH少于理论值
    assertTrue(ethReceived < ethWithoutFee);
}

测试结果分析

通过测试我们可以观察到:

  1. 储备增长:每次交易后,储备总量会略有增加
  2. 手续费积累:手续费以输入资产的形式累积在储备池中
  3. 流动性提供者受益:LP 代币的价值会随着手续费累积而增长

关键技术要点

1. 整数运算优化

  • 使用乘除法顺序来最小化精度损失
  • 避免浮点数运算带来的不确定性
  • 确保计算结果的可预测性

2. 储备管理策略

  • 手续费直接累积到储备池中
  • 无需额外的分配机制
  • 流动性提供者通过 LP 代币自动获得收益

3. 安全考虑

  • 输入验证确保储备不为零
  • 手续费率固定,防止操控
  • 计算过程透明可审计

最佳实践建议

开发建议

  1. 测试覆盖:为手续费机制编写全面的测试用例
  2. 参数调优:根据实际需求调整手续费率
  3. 文档完善:详细记录手续费计算逻辑

使用建议

  1. 理解机制:深入理解手续费如何影响交易价格
  2. 合理预期:认识到储备增长对价格的长期影响
  3. 风险管理:考虑手续费对大额交易的影响

项目仓库

https://github.com/RyanWeb31110/uniswapv1_tech

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
青山
青山
江湖只有他的大名,没有他的介绍。