为什么不能用uniswap的getAmountsOut计算价格
🦄

为什么不能用uniswap的getAmountsOut计算价格

Published
September 11, 2024
Author
daleewong

Intro

之前zongzi 被黑,里面涉及到根本原因是项目方误用了uniswap的getAmountsOut计算用户的奖励,继而被黑,下面仔细讲讲为什么不用getAmountsOut来计算价格
 

getAmountOut

This is the source code of getAmountOut
tips: all the analyse blow ignore the trade fee
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure virtual override returns (uint amountOut) { return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut); } //UniswapV2Library function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); uint amountInWithFee = amountIn.mul(997); uint numerator = amountInWithFee.mul(reserveOut); uint denominator = reserveIn.mul(1000).add(amountInWithFee); amountOut = numerator / denominator; }
notion image
 
 
函数是一条直线,计算逻辑其实是:按照两种token的reserve(存储量)比例,等比计算的。而swap的逻辑是什么呢?
 

swap

notion image
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); }
总结一下它的计算过程
notion image

对比

很明显,同样的amount in,swap会更少,因为它分母更大
notion image
画成函数图像更加直观
green curve : get amount out function , red curve: swap
green curve : get amount out function , red curve: swap
我们再来看getAmountOut. 在官方文档的说明
Given an input asset amount and an array of token addresses, calculates all subsequent maximum output token amounts by calling getReserves for each pair of token addresses in the path in turn, and using these to call getAmountOut.
Useful for calculating optimal token amounts before calling swap.
很明确的说了,是计算最大输出值,也就是说。实际输出会更少一些。

总结

uniswap 是非常常用的DEX。项目方的程序员开发之前一定要熟读源码,把所有细节都搞清楚再开发,不然很容易被黑。