第三章:Uniswap V2核心机制:交易流程拆解、流动性池设计、ERC20与ETH的配对处理
好,咱们直接进入正题。Uniswap V2,说白了就是一套跑在链上的自动做市商协议。你不需要挂单,不需要对手方,直接跟一个智能合约池子交易。听起来很神奇?其实核心逻辑就三块:交易怎么算、池子怎么建、ETH怎么处理。我一个个拆给你看。
3.1 交易流程拆解:从输入到输出的数学魔法
先问个问题:你在Uniswap上拿Token A换Token B,合约里到底发生了什么?
核心公式就一个:x * y = k。x是A的储备量,y是B的储备量,k是常数。交易前后,这个乘积保持不变。嗯,这里要注意,实际交易会收手续费,所以k会略微增大,但原理不变。
假设池子里有100个A和200个B,k=20000。你想用1个A换B。交易后,A变成101个,那B应该变成多少?
101 * y' = 20000 → y' = 20000 / 101 ≈ 198.02
所以你实际拿到的B是:200 - 198.02 = 1.98个。但等等,你投入了1个A,为什么只拿到1.98个B?因为滑点!你交易量越大,滑点越明显。我在项目中遇到过有人拿大额交易测试,结果滑点高得吓人,直接亏了一笔。
核心要点:交易量越大,对池子储备的影响越大,滑点越高。这就是为什么大额交易要拆分成多笔。
代码实现上,Uniswap V2的swap函数会先计算输入金额对应的输出金额,然后更新储备量,最后转账。我习惯把计算逻辑单独抽出来,方便测试。
// 计算输出金额
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
uint amountInWithFee = amountIn * 997;
uint numerator = amountInWithFee * reserveOut;
uint denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}
看到那个997和1000了吗?0.3%的手续费就是这么来的。输入金额先扣掉0.3%,剩下的才参与计算。我刚开始看这段代码时,觉得这个设计很巧妙——手续费在计算前就扣除了,不会影响k值的稳定性。
3.2 流动性池设计:LP Token与储备比例
流动性池子,说白了就是一个资金池。你存入两种代币,获得LP Token作为凭证。池子设计有几个关键点:
- 比例恒定:首次添加流动性时,两种代币的比例决定了初始价格。之后添加,必须按当前比例来。
- LP Token铸造:你存入的流动性占总池子的比例,决定了你能获得多少LP Token。
- 手续费分配:每次交易的手续费会累积到池子里,LP Token持有者可以按比例提取。
举个例子:池子初始有100个A和200个B,总价值假设是1000美元。你存入10个A和20个B,占总流动性的10%,那你就能获得总LP Token供应量的10%。
个人经验:我曾经在测试网上部署过Uniswap V2,发现一个坑——首次添加流动性时,如果比例设置不对,会导致价格剧烈波动。建议先用小额测试,确认比例没问题再大额投入。
LP Token的设计也很讲究。它本身是一个ERC20代币,可以转账、交易,甚至用在其他DeFi协议里做抵押品。我见过有人把LP Token质押到其他池子里赚双重收益,但风险也翻倍——一旦原池子出问题,LP Token可能归零。
3.3 ERC20与ETH的配对处理:WETH的巧妙设计
这里有个关键问题:ETH本身不是ERC20代币,它没有transfer函数,不能直接放进Uniswap池子里。那怎么实现ETH和ERC20的交易?
Uniswap V2的解决方案是:用WETH(Wrapped Ether)作为桥梁。WETH是一个ERC20代币,1:1锚定ETH。你把ETH存进去,获得WETH;把WETH销毁,拿回ETH。
所以,Uniswap V2里其实没有ETH/DAI这样的交易对,而是WETH/DAI。前端会帮你做一层包装:你输入ETH,合约自动帮你换成WETH,然后执行交易,最后再把WETH换回ETH(如果需要的话)。
避坑指南:我曾经在写合约时,直接往Uniswap池子里转ETH,结果交易失败了。后来才发现,必须通过WETH合约来包装。记住:池子里只认ERC20,不认原生ETH。
代码层面,Uniswap V2的Pair合约有一个swap函数,支持ETH和ERC20的混合处理。如果交易涉及ETH,合约会调用WETH的deposit或withdraw函数。我建议你在写集成代码时,先判断交易对是否涉及WETH,再决定是否要包装或解包。
// 处理ETH的包装和解包
if (token0 == WETH) {
IWETH(WETH).deposit{value: amount0Out}();
IWETH(WETH).transfer(to, amount0Out);
} else if (token1 == WETH) {
IWETH(WETH).deposit{value: amount1Out}();
IWETH(WETH).transfer(to, amount1Out);
}
这个设计其实很优雅。它让Uniswap V2可以统一处理所有ERC20代币,同时保留了ETH的原生特性。你想想看,如果没有WETH,那ETH和ERC20的交易逻辑就要单独写一套,代码复杂度会翻倍。
3.4 知识体系总览
为了让你更直观地理解Uniswap V2的核心机制,我画了一张流程图。它展示了交易、流动性、ETH处理这三块是怎么串联起来的。
这张图把Uniswap V2的核心逻辑串起来了。交易流程是引擎,流动性池是燃料,ETH处理是适配器。三者缺一不可。
3.5 实战中的注意事项
最后,分享几个我在实际项目中踩过的坑:
- 滑点保护:用户交易时,一定要设置滑点容忍度。我见过有人没设滑点,结果被MEV机器人夹击,亏了20%。
- 流动性添加:首次添加流动性时,两种代币的比例决定了初始价格。如果比例跟市场价偏差太大,会被套利者瞬间搬平。
- ETH处理:如果你在合约里直接跟Uniswap交互,记得用WETH。我曾经犯过这个错,交易一直失败,查了半天才发现是ETH没包装。
总结一下:Uniswap V2的核心就是x*y=k这个公式,加上LP Token的流动性管理,再加上WETH的桥接设计。理解这三块,你就掌握了链上自动做市商的精髓。
好了,这一章就到这里。记住,纸上得来终觉浅,绝知此事要躬行。建议你找个测试网,亲手部署一个Uniswap V2池子,跑几笔交易试试。实践出真知。