Platform: Code4rena
Start Date: 12/12/2022
Pot Size: $36,500 USDC
Total HM: 8
Participants: 103
Period: 7 days
Judge: berndartmueller
Id: 193
League: ETH
Rank: 68/103
Findings: 1
Award: $40.26
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Jeiwan
Also found by: 0xxm, 9svR6w, BAHOZ, Bobface, CRYP70, Chom, HE1M, Junnon, RaymondFam, UNCHAIN, __141345__, bytehat, carlitox477, caventa, cccz, chaduke, hansfriese, hihen, koxuan, mauricio1802, minhquanym, minhtrng, nicobevi, obront, shung, unforgiven, wait
40.2564 USDC - $40.26
When a user adds liquidity they need to provide the two assets from the pool. The number of LPToken
received by the user is computed in the addQuote
function. When there is liquidity in the pool, the addQuote
function will compute, per each provided asset, the number of LPToken
that represent the same share between the provided asset and the reserve, and will return the minimum of the two numbers. This means that the user sends some tokens to the pool that are not returned as LPToken
, those tokens are still sent to the pool and distributed automatically on a pro-rata basis between all the LPToken
holders.
This mechanism can create bad incentives, LPToken
holders may try to create these scenarios in order to get more value for their LPTokens
. There are multiple scenarios that can happen, below I described one, that from my point of view, is the most suitable to happen:
NFT1
and WETH
NFT1
getting in return 1 fractional token (1e18)
from the pool.1 fractional token (1e18)
and 1 WETH (1e18)
to the pool. Alice sets the minimum LPToken
she is willing to receive to 0 LPToken
(assuming that the pool does not have liquidity yet).1 fractional token (1e18)
and 0.1 WETH (1e17)
, Bob receives approximately 0.3 LPToken
in return.fractional token
sent by her represents 100%
of the reserve of fractional token
in the pool, and the amount of WETH
sent by her represents 1000%
of the reserve of the pool. For computing the amount of LPToken
she receives, the minimum percentage of shares is used, so she receives 100%
of the existing LPToken
, which is the same amount received by Bob previously.2 fractional tokens (2e18)
and 1.1 WETH (11e17)
. And both Alice and Bob have the same amount of LPTokens
, which means they have the same shares over the pool's funds.1 fractional token (1e18)
and 0.55 WETH (55e16)
.Note: It is important to note that Alice could have avoided this setting a better value for the minimum amount of LPToken
expected.
The next code can be copied into a solidity file in the test
folder. After adding it, the test can be run to check a scenario similar to the one explained above.
pragma solidity ^0.8.17; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "./shared/Fixture.t.sol"; contract TestExploit is Fixture { address public Alice = address(0x11111); address public Bob = address(0x22222); function setUp() public { // Give fractional tokens to Alice deal(address(p), Alice, 1e18, true); // Give USD to Alice deal(address(usd), Alice, 1e18, true); // Give fractional tokens to Bob deal(address(p), Bob, 1e18, true); // Give USD to Bob deal(address(usd), Bob, 1e18, true); } function testExploit() public { // Storing initiali USD balance of Bob uint256 bobInitialUSDBalance = usd.balanceOf(Bob); // Bob frontruns Alice adding liquidity to the pool. vm.startPrank(Bob); usd.approve(address(p), 1e17); p.add(1e17, 1e18, 0); vm.stopPrank(); // Alice adds liquidity vm.startPrank(Alice); usd.approve(address(p), 1e18); p.add(1e18, 1e18, 0); vm.stopPrank(); // Bob burns his liquidity vm.startPrank(Bob); p.remove(lpToken.balanceOf(Bob), 0, 0); vm.stopPrank(); // Storing final USD balance of Bob uint256 bobFinalUSDBalance = usd.balanceOf(Bob); // Checking if Bob made profits assert(bobFinalUSDBalance > bobInitialUSDBalance); } }
When adding liquidity, only transfer from the users the number of tokens that are being used for backing the LPTokens
, any surplus token should not be sent to the pool. In the case of ETH
, the surplus amount should be refunded to the user. I think this could be done similarly to how it is done in Uniswap V2 https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/UniswapV2Router02.sol#L33
#0 - c4-judge
2022-12-28T14:19:28Z
berndartmueller marked the issue as duplicate of #376
#1 - c4-judge
2023-01-10T09:02:14Z
berndartmueller changed the severity to 3 (High Risk)
#2 - c4-judge
2023-01-10T09:02:19Z
berndartmueller marked the issue as satisfactory