这个曾经审计过,但是没看出来问题。当时看到的是一个mint lottery的合约好像,没有找到真正的漏洞点。在这里分析下出现问题的原因。
首先攻击Tx
第一步 攻击者先用280w刀的USDC来兑换Nala token
然后调用了LotteryTicket的transferToken来mint lottery ticket,这里会mint 很多ticket然后拿转走你的usdc来添加流动性,相当于是LP token。
function transferToken(uint amount) public returns (bool) {
IUniswapV2Router02 swapRouter = IUniswapV2Router02(ROUTER_ADDRESS);
require(amount > 0, "Amount must more than 0 USDT");
require(amount % MIN_DEPOSIT == 0, "Amount must be a multiple of 50 USDT");
uint allowmount= coinUsdt.allowance(msg.sender,address(this));
require(allowmount>=amount, "Insufficient authorization limit");
// 转账 USDT 到合约
require(coinUsdt.transferFrom(msg.sender,address(this),amount), "USDT transfer failed");
uint ticket_count=amount/MIN_DEPOSIT;
require(coinTicket.mint(msg.sender,ticket_count * 10 ** 6), "Ticket mint failed");
uint amountIn=amount*50/100;
address[] memory path = new address[](2);
path[0] = tokenUSDT;
path[1] = tokenNATA;
coinUsdt.approve(ROUTER_ADDRESS, amount);
swapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
amountIn,
1,
path,
address(this),
block.timestamp+600
);
uint amountADesired=amountIn*997/1000;
// 批准 Uniswap 路由合约转移 U 和 NATA 代币用于添加流动性
coinUsdt.approve(ROUTER_ADDRESS, amount);
coinNata.approve(ROUTER_ADDRESS, coinNata.totalSupply());
// 3. 调用Pair合约获取储备数据
address token0 = IUniswapV2Pair(pairAddress).token0();
if(token0==tokenUSDT){
( reserveUSDT, reserveNATA,) = IUniswapV2Pair(pairAddress).getReserves();
}else {
(reserveNATA, reserveUSDT,) = IUniswapV2Pair(pairAddress).getReserves();
}
//uint
amountBDesired = (amountADesired * reserveNATA) / reserveUSDT;
if(flag1){
swapRouter.addLiquidity(
tokenUSDT,
tokenNATA,
amountADesired,
amountBDesired,
1,
1,
address(this),
block.timestamp+600
);
}
return true;
}
之后调用了DestructionOfLotteryTickets 来销毁第一步mint 给你的ticket(相当于LP)
问题原因
让我们看两段关键的代码
1.
// 转账 USDT 到合约
require(coinUsdt.transferFrom(msg.sender,address(this),amount), "USDT transfer failed");
uint ticket_count=amount/MIN_DEPOSIT;
require(coinTicket.mint(msg.sender,ticket_count * 10 ** 6), "Ticket mint failed");
2.
uint ticket_count=_amountTickets/MIN_TICKET;
uint amountUSDTALL=ticket_count*MIN_DEPOSIT;
uint amountUSDT=amountUSDTALL/2*997/1000;
uint256 totalSupplyLP=IERC20(pairAddress).totalSupply();
uint liquidity = (amountUSDT * totalSupplyLP) / reserveUSDT;
第一步中的mint ticket 看的是你提供了多少USDC,第二部中的 destroy ticket 把对应的USDT份额所占多少USDT乘以totalSupplyLP得到需要多少份额的LP来销毁对应的lp token。
注意攻击者在真正开始销毁LP之前进行了Nala 换回 USDC的操作。
所以在这个公式中uint liquidity = (amountUSDT * totalSupplyLP) / reserveUSDT; reserveUSDT基本没变amountUSDT基本没变,totalSupplyLP增多的情况下导致了liquidity的份额会变多,从而得到了多销毁份额的利润。