Closed sherlock-admin4 closed 1 month ago
First of all, FlapperUniV2
calls pair.sync()
as that flapper deposits in the pool (https://github.com/makerdao/dss-flappers/blob/sherlock-contest/src/FlapperUniV2.sol#L119), then we want to avoid any type of manipulation when increasing the LP position.
FlapperUniV2SwapOnly
doesn't use pair.sync()
on purpose as the only benefit to call it is to realize possible donations to the pool, the extra gas for this isn't worth it for the extreme rare cases where it could be profited a donation.
chaduke
High
FlapperUniV2.exec() might use stale reserve data to perform the swap, as a result, it is subject to reserve manipulation exploit.
Summary
FlapperUniV2.exec() might use stale reserve data to perform the swap. It calls _getReserves(), which does not call sync(). Therefore, the reserve data used to calculate the amount of output for the swap might not be accurate. The swap function inside exec() is subject to reserve manipulation exploit.
The attack can be replayed indefinately. Therefore, I mark this as high.
Root Cause
the
_getReserves()
function is used to get the reserve information for the token pair. However, it does not call pair.sync(), therefore, the returned reserve information could have been outdated.https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/dba30d7a676c20dfed3bda8c52fd6702e2e85bb1/dss-flappers/src/FlapperUniV2SwapOnly.sol#L107C14-L110
Internal pre-conditions
None
External pre-conditions
Some users might send some tokens to the pair contract, as a result, there is discrepancy between the real balances of the tokens and the reserve numbers.
Attack Path
Suppose we have: DAI Reserve: 83819875867994573841908993 MKR Reserve: 30055990947180442086389
User Input: The exec() function is called with lot = 5707000000000000000000. The expected amount of MKR to be received is 2040128810233333416 MKR
However, suppose the REAL balance for DAI is 83819875867994573841908993, and the REAL balance for MKR is 30206270901916344296820 (0.5% more). In other words, if sync() has been called first to sync the reserves. Then, the expected amount of MKR to be received is 2050329454284500083.
Unfortunately, due to the lack of sync() calling inside the _reserves() function, the
FlapperUniV2SwapOnly.receiver
will receive only 2040128810233333416 MKR, which is 6761774306807008 MKR LESS.Impact
FlapperUniV2.exec() might use stale reserve data to perform the swap, as a result, it is subject to reserve manipulation exploit. the
FlapperUniV2SwapOnly.receiver
might receive more or less MKR tokens than expected.PoC
For the following code, please try three cases:
1) case 1: reserves have been synced
2) case 2: reserves are out of sync, the actual MKR balance is 0.5% more. function testManipulateReserves() public{ deal(MKR, UNIV2_DAI_MKR_PAIR, GemLike(MKR).balanceOf(UNIV2_DAI_MKR_PAIR) * 1005/ 1000); vow.flap(); }
3) case 3: the MKR reserve is increased by 0.5% and synced.
The full code is as follows:
Mitigation
Add
pair().sync()
to _getReserve():