UniswapV1自学系列06-手续费机制实现前言在前面的系列文章中,我们已经实现了基础的流动性管理和代币交换功能。本文将深入探讨UniswapV1的手续费机制,包括手续费的收取方式、分配机制以及具体的代码实现。核心问题分析在实现手续费机制之前,我们需要思考几个关键问题:1.
在前面的系列文章中,我们已经实现了基础的流动性管理和代币交换功能。本文将深入探讨 UniswapV1 的手续费机制,包括手续费的收取方式、分配机制以及具体的代码实现。
在实现手续费机制之前,我们需要思考几个关键问题:
尽管这些问题看起来复杂,但实际上我们已经具备了解决所有问题的基础设施。
我们可以采用一种巧妙的方式来处理手续费:不需要额外的支付,只需要从发送到合约的 ETH 或代币中直接扣除手续费。
核心思路:
交易所储备本身就是一个天然的手续费累积池:
储备池的双重作用:
这意味着储备会随着时间增长,使得常数乘积公式不再"恒定"。但这并不会破坏机制的有效性,因为:
现在我们可以回答第一个问题:手续费以交易资产的货币形式支付。
流动性提供者的收益包括:
Uniswap V1 实际收取 0.3% 的手续费,但为了便于测试观察效果,我们设置为 1%。
在价格计算函数中添加手续费逻辑非常简单,只需要添加几个乘数:
/**
* @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
在我们的实现中:
inputAmountWithFee = inputAmount * 99
相当于 inputAmount * (100-1)
这种实现方式巧妙地避免了浮点数运算,同时保持了计算的精确性。
在传统的 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);
}
通过测试我们可以观察到:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!