Platform: Code4rena
Start Date: 16/01/2024
Pot Size: $80,000 USDC
Total HM: 37
Participants: 178
Period: 14 days
Judge: Picodes
Total Solo HM: 4
Id: 320
League: ETH
Rank: 25/178
Findings: 2
Award: $553.93
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: neocrao
Also found by: 00xSEV, 0x11singh99, 0x3b, 0xAlix2, 0xRobocop, 0xSmartContractSamurai, 0xanmol, AgileJune, Drynooo, HALITUS, Imp, J4X, KHOROAMU, Kalyan-Singh, MSaptarshi, RootKit0xCE, The-Seraphs, agadzhalov, aman, ayden, cu5t0mpeo, erosjohn, ewah, jasonxiale, jesjupyter, juancito, klau5, memforvik, okolicodes, parrotAudits0, rudolph, t0x1c, wangxx2026, zhaojohnson
1.6255 USDC - $1.63
https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L97 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L187
When reserve1 of a pool is equal to zero, any call to
addLiquidity
will cause the pool to reset the reserves ratio. (https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L97)
Zero of reserve1 can be done since of this missed checking about reserves.reserve1 (https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L187)
So even with a properly set slippage guard, a depositor calling addLiquidity can be front
run by a sole liquidity holder, which would cause the depositor to lose funds.
(It's just from Tail of bits's No. 3 except for bringing down reserve1 to zero)
Eve is the sole liquidity holder in the WETH/DAI pool, which has reserves of 100 WETH and 200,000 DAI, for a ratio of 1:2,000.
Manual Review
We need to fix the condition correctly. https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L187
before require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve0 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal"); after require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve1 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal");
Invalid Validation
#0 - c4-judge
2024-02-01T22:43:28Z
Picodes marked the issue as duplicate of #647
#1 - c4-judge
2024-02-19T15:39:01Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2024-02-19T15:41:18Z
Picodes marked the issue as satisfactory
552.2953 USDC - $552.30
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L146 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L178-L180 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L192
The calling of function depositLiquidityAndIncreaseShare()
with useZapping = true
can be failed due to underflow on _zapSwapAmount()
The calling of function depositLiquidityAndIncreaseShare()
with useZapping = true
invokes _zapSwapAmount()
to calculate zap swap amount.
BTW, after shifting, r1*z0 < r0*z1
is possible in two cases, so the line (https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L192) causes underflow and reverts.
There are cases where z0 can be zero after shift. while passing the condition (https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L178-L180) if shift variable is more than z0's bits count, it's possible
function testDualZapInLiquidity() public { // setup TestERC20 tokenA = new TestERC20( "TEST", 18 ); TestERC20 tokenB = new TestERC20( "TEST", 18 ); uint256 zapAmountA = 2**80; uint256 zapAmountB = 2**91; vm.prank(address(dao)); poolsConfig.whitelistPool( pools, tokenA, tokenB); // make initial deposits tokenA.approve(address(collateralAndLiquidity),type(uint256).max); tokenB.approve(address(collateralAndLiquidity),type(uint256).max); collateralAndLiquidity.depositLiquidityAndIncreaseShare(tokenA, tokenB, zapAmountA, zapAmountB, 0, block.timestamp, false); vm.warp( block.timestamp + 1 hours ); // Add more liquidity vm.expectRevert(stdError.arithmeticError); collateralAndLiquidity.depositLiquidityAndIncreaseShare(tokenA, tokenB, 2**9, 2**18, 0, block.timestamp, true); vm.warp( block.timestamp + 1 hours ); // get actual reserves (uint256 actualAddedAmountA, uint256 actualAddedAmountB) = pools.getPoolReserves(tokenA, tokenB); assertEq(zapAmountA, actualAddedAmountA); assertEq(zapAmountB, actualAddedAmountB); }
There are cases where r1*z0 > r0*z1
, but after shifting. it becomes r1*z0 < r0*z1
We can get x, y, a, b from 2xy + x + y = 2ab
to meet (2x + 1) * (2y + 1) > 2a * 2b, x* y < a * b
function testDualZapInLiquidity() public { // setup TestERC20 tokenA = new TestERC20( "TEST", 18 ); TestERC20 tokenB = new TestERC20( "TEST", 18 ); uint256 zapAmountA = 130478777887922532071812 * 2 * 2**15; uint256 zapAmountB = (2**81 + 1) * 2**10; vm.prank(address(dao)); poolsConfig.whitelistPool( pools, tokenA, tokenB); // make initial deposits tokenA.approve(address(collateralAndLiquidity),type(uint256).max); tokenB.approve(address(collateralAndLiquidity),type(uint256).max); collateralAndLiquidity.depositLiquidityAndIncreaseShare(tokenA, tokenB, zapAmountA, zapAmountB, 0, block.timestamp, false); vm.warp( block.timestamp + 1 hours ); // Add more liquidity vm.expectRevert(stdError.arithmeticError); collateralAndLiquidity.depositLiquidityAndIncreaseShare(tokenA, tokenB, 49 * 2**10, 454 * 2**10, 0, block.timestamp, true); // get actual reserves (uint256 actualAddedAmountA, uint256 actualAddedAmountB) = pools.getPoolReserves(tokenA, tokenB); assertEq(zapAmountA, actualAddedAmountA); assertEq(zapAmountB, actualAddedAmountB); }
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L146 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L178-L180 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/PoolMath.sol#L192
Manual Review, Foundry
if (r1 * z0 < r0 * z1) { return 0; } uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 );
Under/Overflow
#0 - c4-judge
2024-02-02T14:58:01Z
Picodes marked the issue as duplicate of #707
#1 - c4-judge
2024-02-19T15:36:39Z
Picodes marked the issue as satisfactory
#2 - c4-judge
2024-02-19T15:53:50Z
Picodes marked the issue as duplicate of #232
#3 - c4-judge
2024-02-21T16:58:43Z
Picodes changed the severity to 2 (Med Risk)