Platform: Code4rena
Start Date: 01/08/2023
Pot Size: $91,500 USDC
Total HM: 14
Participants: 80
Period: 6 days
Judge: gzeon
Total Solo HM: 6
Id: 269
League: ETH
Rank: 10/80
Findings: 3
Award: $1,120.80
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: said
Also found by: 0xmuxyz, LokiThe5th, Satyam_Sharma, T1MOH, Team_FliBit, radev_sw
482.4792 USDC - $482.48
Function poolMatchesOracle()
is called before deposit, withdraw, rebalance. This function overflows because of high sqrtPriceX96 on some pools, such as WBTC/ETH, WBTC/DAI
It is foundry test. Run command forge test --match-test testB -vvv --fork-url <MAINNET_RPC_URL>
And you'll see that calculation overflows
pragma solidity 0.8.19; import "forge-std/Test.sol"; import "../src/helper/V3Proxy.sol"; import "../interfaces/IUniswapV3Pool.sol"; contract V3ProxyTest is Test { function testB() public { //You can use WBTC/ETH https://info.uniswap.org/#/pools/0x4585fe77225b41b697c938b018e2ac67ac5a20c0 //OR WBTC/DAI https://info.uniswap.org/#/pools/0x391e8501b626c623d39474afca6f9e46c2686649 IUniswapV3Pool pool = IUniswapV3Pool(0x391E8501b626C623d39474AfcA6f9e46c2686649); (uint160 sqrtPriceX96,,,,,,) = pool.slot0(); uint decimals0 = ERC20(pool.token0()).decimals(); uint decimals1 = ERC20(pool.token1()).decimals(); uint priceX8 = 10**decimals0; // Overflow if dont scale down the sqrtPrice before div 2*192 priceX8 = priceX8 * uint(sqrtPriceX96 / 2 ** 12) ** 2 * 1e8 / 2**168; priceX8 = priceX8 / 10**decimals1; } }
Manual Review
Now you perform calculations with price X84, consider moving to price X64 for example or less
- priceX8 = priceX8 * uint(sqrtPriceX96 / 2 ** 12) ** 2 * 1e8 / 2**168; - priceX8 = priceX8 * uint(sqrtPriceX96 / 2 ** 32) ** 2 * 1e8 / 2**128;
Uniswap
#0 - c4-pre-sort
2023-08-09T10:35:48Z
141345 marked the issue as duplicate of #140
#1 - c4-judge
2023-08-20T17:32:23Z
gzeon-c4 changed the severity to 3 (High Risk)
#2 - c4-judge
2023-08-20T17:32:29Z
gzeon-c4 marked the issue as satisfactory
625.436 USDC - $625.44
In function swapTokensForExactETH()
there is parameter amountInMax
that specifies slippage. It is maximum that caller supposes to spend to receive exact amount of ETH. However if actual amountIn
is less, ProxyV3.sol won't transfer back excess.
As a result all swaps via this function are executed with maximum allowed slippage
Here you can see that whole amount amountInMax
is taken from user, but excess is never returned
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) { require(path.length == 2, "Direct swap only"); require(path[1] == ROUTER.WETH9(), "Invalid path"); ERC20 ogInAsset = ERC20(path[0]); //@audit TAKES WHOLE `amountInMax` amount @> ogInAsset.safeTransferFrom(msg.sender, address(this), amountInMax); ogInAsset.safeApprove(address(ROUTER), amountInMax); amounts = new uint[](2); amounts[0] = ROUTER.exactOutputSingle(ISwapRouter.ExactOutputSingleParams(path[0], path[1], feeTier, address(this), deadline, amountOut, amountInMax, 0)); // RETURN SWAPPED ETH amounts[1] = amountOut; ogInAsset.safeApprove(address(ROUTER), 0); IWETH9 weth = IWETH9(ROUTER.WETH9()); acceptPayable = true; weth.withdraw(amountOut); acceptPayable = false; payable(msg.sender).call{value: amountOut}(""); @> // DOESN'T RETURN `amountInMax - amounts[0]` AMOUNT OF ogInAsset token emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); }
Manual Review
Return back excess
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) { ... + ogInAsset.safeTransfer(msg.sender, amountInMax - amounts[0]); emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); }
Uniswap
#0 - c4-pre-sort
2023-08-09T08:23:27Z
141345 marked the issue as duplicate of #64
#1 - c4-judge
2023-08-20T17:31:50Z
gzeon-c4 changed the severity to 3 (High Risk)
#2 - c4-judge
2023-08-20T17:31:54Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: dd0x7e8
Also found by: Bughunter101, Fulum, Kaysoft, MatricksDeCoder, SanketKogekar, Sathish9098, T1MOH, Udsen, debo, fatherOfBlocks, grearlake, hpsb, j4ld1na, josephdara, parsely, pep7siup, piyushshukla, ravikiranweb3, shirochan
12.8772 USDC - $12.88
https://github.com/code-423n4/2023-08-goodentry/blob/71c0c0eca8af957202ccdbf5ce2f2a514ffe2e24/contracts/helper/V3Proxy.sol#L174 https://github.com/code-423n4/2023-08-goodentry/blob/71c0c0eca8af957202ccdbf5ce2f2a514ffe2e24/contracts/helper/V3Proxy.sol#L156 https://github.com/code-423n4/2023-08-goodentry/blob/71c0c0eca8af957202ccdbf5ce2f2a514ffe2e24/contracts/helper/V3Proxy.sol#L192
Low level call is used when ProxyV3.sol transfers ETH to msg.sender. However ProxyV3.sol doesn't check whether this call was successful or not. And if result is false, ETH will be stuck in ProxyV3.sol and user loses funds.
ProxyV3.sol transfers ETH to msg.sender when performs swaps with ETH:
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) { ... payable(msg.sender).call{value: amounts[1]}(""); emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); } function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) { ... payable(msg.sender).call{value: amountOut}(""); emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); } function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) { ... msg.sender.call{value: msg.value - amounts[0]}(""); emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); }
Manual Review
Revert when result is false
(bool success,) = payable(msg.sender).call{value: amountOut}(""); require(success, "Transfer failed");
call/delegatecall
#0 - c4-pre-sort
2023-08-09T02:07:46Z
141345 marked the issue as duplicate of #481
#1 - c4-pre-sort
2023-08-09T09:25:56Z
141345 marked the issue as duplicate of #83
#2 - c4-judge
2023-08-20T17:11:17Z
gzeon-c4 marked the issue as satisfactory