合约地址:
UniswapV2Router02(sashimi):https://etherscan.io/address/0xe4fe6a45f354e845f954cddee6084603cedb9410#code
UniswapV2Router02(uniswap):https://etherscan.io/address/0x7a250d5630b4cf539739df2c5dacb4c659f2488d#code
SashimiInvestment:https://etherscan.io/address/0x3f966fa1c0606e19047ed72068d2857677e07ef4#code
攻击交易:
https://etherscan.io/tx/0xd6a816cc291b24267c03c23c730a84a2699f32a7cf714c8cbe3e47321c76b08f
在UniswapV2Router02(sashimi)
合约中函数swapExactTokensForETHSupportingFeeOnTransferTokens
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
address pair = UniswapV2Library.pairFor(factory, path[0], path[1]);
_transferIn(msg.sender,pair,path[0],amountIn);
uint balanceBefore = getTokenInPair(pair,WETH);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint balanceAfter = getTokenInPair(pair,WETH);
uint amountOut = balanceBefore.sub(balanceAfter); ///获得pai的balance差
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
_transferETH(to, amountOut);
}
再看UniswapV2Router02(uniswap)合约中函数swapExactTokensForETHSupportingFeeOnTransferTokens
的源码
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
两者差异在sashimi合约中将所有pair计算后放入合约自身内计算,同时swapExactTokensForETHSupportingFeeOnTransferTokens只考虑了token:eth的单池转换,如果path为
token1:token2:eth时就会导致发送代币后无法接收到eth,同样如果路径为(代币以A,B,C代替,E代表Eth),A:E:B:C:E,就会导致代币A兑换的E流入E:B的池子;同时将A:E内池子的Eth差值传给攻击者,这时如果E:B为攻击者创建的池子,就可以撤掉池子再拿一份eth,
所以攻击者创建3个代币。
A:E中放入大量eth。其余池子仅放入少量eth。
后调用swapExactTokensForETHSupportingFeeOnTransferTokens
通过卖出大量A,将大量Eth传入E:B池中,B在传入B:C池…
同时因为sashimi合约中将所有pair计算后放入合约自身内计算,所以通过eth购买其他代币入usdt,dai等继续冲入eth到合约内,再提取eth,来榨取其他代币。