QuantAMM

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

Users can make more than 100 deposits contrary to protocol design

Summary

In the natspec of UpliftOnlyExample.sol, it is stated that users are restricted to 100 deposits to avoid DoS issues. On the contrary, users can make more than 100 deposits breaking this invariant.

Vulnerability Details

The protocol's intention to restrict user deposits to 100 is mentioned here.

However, the logic used in UpliftOnlyExample::addLiquidityProportional to ensure this invariant is not broken is flawed. The vulnerability lies in the fact that the function checks if array length is greater than 100 allowing a user to make one deposit more after making 100 deposits.

The affected code is shown in the snippet below and can be found here

function addLiquidityProportional(
address pool,
uint256[] memory maxAmountsIn,
uint256 exactBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable saveSender(msg.sender) returns (uint256[] memory amountsIn) {
@> if (poolsFeeData[pool][msg.sender].length > 100) {
revert TooManyDeposits(pool, msg.sender);
}
// Do addLiquidity operation - BPT is minted to this contract.
amountsIn = _addLiquidityProportional(
pool,
msg.sender,
address(this),
maxAmountsIn,
exactBptAmountOut,
wethIsEth,
userData
);
uint256 tokenID = lpNFT.mint(msg.sender);
//this requires the pool to be registered with the QuantAMM update weight runner
//as well as approved with oracles that provide the prices
uint256 depositValue = getPoolLPTokenValue(
IUpdateWeightRunner(_updateWeightRunner).getData(pool),
pool,
MULDIRECTION.MULDOWN
);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
//this rounding favours the LP
lpTokenDepositValue: depositValue,
//known use of timestamp, caveats are known.
blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);
nftPool[tokenID] = pool;
}

Impact

Users can make more than 100 deposits contrary to protocol design thereby, breaking a protocol invariant.

Tools Used

Manual Review

Foundry

Proof of Concept:

  1. A user bob attempts to make 101 deposits

  2. bob makes all 101 deposits successfully showing that a protocol invariant is broken.

PoC Place the following code into `UpliftExample.t.sol`.
function test_SpomariaPoC_UserCanExceed100AddLiquidity() public {
BaseVaultTest.Balances memory balancesBefore = getBalances(bob);
uint256 numOfDeposits = 101;
uint256[] memory maxAmountsIn = [dai.balanceOf(bob)/numOfDeposits, usdc.balanceOf(bob)/numOfDeposits].toMemoryArray();
vm.prank(bob);
uint256[] memory amountsInFirst = upliftOnlyRouter.addLiquidityProportional(
pool,
maxAmountsIn,
bptAmount / 2,
false,
bytes("")
);
vm.stopPrank();
for(uint i = 1; i < numOfDeposits; i++){
int256[] memory prices = new int256[]();
for (uint256 i = 0; i < 2; ++i) {
prices[i] = (int256(i) * 1e18) / 2;
}
updateWeightRunner.setMockPrices(pool, prices);
skip(5 days);
vm.prank(bob);
upliftOnlyRouter.addLiquidityProportional(
pool,
maxAmountsIn,
bptAmount / 2,
false,
bytes("")
);
vm.stopPrank();
}
assertEq(upliftOnlyRouter.getUserPoolFeeData(pool, bob).length, numOfDeposits, "deposit length incorrect");
}

Recommendations

Consider modifying UpliftOnlyExample::addLiquidityProportional as given below to ensure users are restricted to 100 deposits and thereby preserve the invariant

function addLiquidityProportional(
address pool,
uint256[] memory maxAmountsIn,
uint256 exactBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable saveSender(msg.sender) returns (uint256[] memory amountsIn) {
- if (poolsFeeData[pool][msg.sender].length > 100) {
+ if (poolsFeeData[pool][msg.sender].length >= 100) {
revert TooManyDeposits(pool, msg.sender);
}
// Do addLiquidity operation - BPT is minted to this contract.
amountsIn = _addLiquidityProportional(
pool,
msg.sender,
address(this),
maxAmountsIn,
exactBptAmountOut,
wethIsEth,
userData
);
uint256 tokenID = lpNFT.mint(msg.sender);
//this requires the pool to be registered with the QuantAMM update weight runner
//as well as approved with oracles that provide the prices
uint256 depositValue = getPoolLPTokenValue(
IUpdateWeightRunner(_updateWeightRunner).getData(pool),
pool,
MULDIRECTION.MULDOWN
);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
//this rounding favours the LP
lpTokenDepositValue: depositValue,
//known use of timestamp, caveats are known.
blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);
nftPool[tokenID] = pool;
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid_Uplift_101_deposit_strict_equal

Only 1 more NFT won’t have any impact. Informational.

Support

FAQs

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

Give us feedback!