Polynomial Protocol contest - 0xbepresent's results

The DeFi Derivatives Powerhouse.

General Information

Platform: Code4rena

Start Date: 13/03/2023

Pot Size: $72,500 USDC

Total HM: 33

Participants: 35

Period: 7 days

Judge: Dravee

Total Solo HM: 16

Id: 222

League: ETH

Polynomial Protocol

Findings Distribution

Researcher Performance

Rank: 21/35

Findings: 2

Award: $254.10

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: peakbolt

Also found by: 0xRobocop, 0xbepresent, auditor0517, kaden

Labels

bug
3 (High Risk)
partial-50
edited-by-warden
duplicate-153

Awards

175.2447 USDC - $175.24

External Links

Lines of code

https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L494 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L511

Vulnerability details

Impact

The protocol does not adequately increase the calculated fees to the totalFunds storage variable in the open short operations consequently the liquidity providers receive less yields and the protocol receive less funds. Those calculated fees are lost.

Proof of Concept

The fees are calculated in the 511 code line but those are not increased to the totalFunds storage variable:

File: LiquidityPool.sol
491:     /// @notice Called by exchange when a new short position is created
492:     /// @param amount Amount of square perp being shorted
493:     /// @param user Address of the user
494:     function openShort(uint256 amount, address user, bytes32 referralCode)
495:         external
496:         override
497:         onlyExchange
498:         nonReentrant
499:         returns (uint256 totalCost)
500:     {
501:         (uint256 markPrice, bool isInvalid) = getMarkPrice();
502:         require(!isInvalid);
503: 
504:         uint256 tradeCost = amount.mulWadDown(markPrice);
505:         uint256 fees = orderFee(-int256(amount));
506:         totalCost = tradeCost - fees;
507: 
508:         SUSD.safeTransfer(user, totalCost);
509: 
510:         uint256 hedgingFees = _hedge(-int256(amount), false);
511:         uint256 feesCollected = fees - hedgingFees;
512:         uint256 externalFee = feesCollected.mulWadDown(devFee);
513: 
514:         SUSD.safeTransfer(feeReceipient, externalFee);
515: 
516:         usedFunds += int256(totalCost + hedgingFees + externalFee);
517:         require(usedFunds <= 0 || totalFunds >= uint256(usedFunds));
518: 
519:         emit RegisterTrade(referralCode, feesCollected, externalFee);
520:         emit OpenShort(markPrice, amount, fees);
521:     }

Tools used

VScode

Increment the feesCollected to the totalFunds storage variable in the openShort() function.

#0 - c4-judge

2023-03-21T13:41:51Z

JustDravee marked the issue as duplicate of #153

#1 - JustDravee

2023-05-02T22:44:53Z

Partial for lack of coded POC on a high

#2 - c4-judge

2023-05-02T22:45:20Z

JustDravee marked the issue as partial-50

Findings Information

🌟 Selected for report: bytes032

Also found by: 0xbepresent, PaludoX0, juancito, peanuts, sorrynotsorry

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-123

Awards

78.8601 USDC - $78.86

External Links

Lines of code

https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L200 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L219

Vulnerability details

Impact

The LiquidityPool.sol::queueDeposit() function helps to queue the user deposit and then the deposit will be processed by the LiquidityPool.sol::processDeposits() function after the delay is completed. The deposits are used to cover the margin required in every operation.

The problem is that an attacker can create many deposits with zero amounts, so it is possible to flood the depositQueue with zero amount deposits and the processDeposits() function will cost more work/gas to process.

Please see the next scenario:

  1. For some reason there are not sUSD in the LiquidityPool.
  2. The protocol needs to cover his margins using the increaseMargin() function but is not possible because there are not any sUSD in the LiquidityPool.
  3. The liquidity providers use queueDeposit() function because the function doesn't have fees.
  4. The attacker frontruns the liquidity providers and he floods the depositQueue with zero amounts causing the processing of the processDeposits() function to cost more work/gas.
  5. The protocol can't hedge his exposure and they need to use the deposit() function instead.

At the end, the attacker can cause the processDeposits() function to take more work/gas to process, causing valid deposits to be unavailable.

Proof of Concept

I created a basic test where you can see that it is possible to queue deposits with amount zero:

function testZeroQueueDeposit() public {
    // It is possible to flood the depositQueue with zero amounts in the queueDeposit()
    // causing a DOS in the processDeposits()
    // 1. Call the queueDeposit() with zero amounts
    // 2. Assert queuedDepositHead and nextQueuedDepositId
    // 3. Call the processDeposits()
    // 4. Assert the incremention of queuedDepositHead
    //
    // 1. Call the queueDeposit() with zero amounts
    //
    pool.queueDeposit(0, user_1);
    pool.queueDeposit(0, user_2);
    pool.queueDeposit(0, user_3);
    //
    // 2. Assert queuedDepositHead and nextQueuedDepositId
    //
    assertEq(pool.queuedDepositHead(), 1);
    assertEq(pool.nextQueuedDepositId(), 4);
    //
    // 3. Call the processDeposits()
    //
    vm.warp(block.timestamp + pool.minDepositDelay());
    pool.processDeposits(1);
    //
    // 4. Assert the incremention of queuedDepositHead
    //
    assertEq(pool.queuedDepositHead(), 2);
}

Tools used

VScode

Checks that the deposited amount is not zero in the queueDeposit() function.

#0 - c4-judge

2023-03-24T01:30:01Z

JustDravee marked the issue as duplicate of #122

#1 - JustDravee

2023-05-03T01:37:39Z

Considered as a duplicate of https://github.com/code-423n4/2023-03-polynomial-findings/issues/122 from the same wardens as others submitted both in the same finding Also, front-running isn't an issue on Optimism https://help.optimism.io/hc/en-us/articles/4444375174299-Is-transaction-front-running-possible-on-Optimism-

#2 - c4-judge

2023-05-05T12:47:27Z

JustDravee marked the issue as satisfactory

#3 - c4-judge

2023-05-16T00:04:37Z

JustDravee changed the severity to 2 (Med Risk)

Findings Information

🌟 Selected for report: bytes032

Also found by: 0xbepresent, PaludoX0, juancito, peanuts, sorrynotsorry

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
duplicate-123

Awards

78.8601 USDC - $78.86

External Links

Lines of code

https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L264 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L284 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/KangarooVault.sol#L227 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/KangarooVault.sol#L269

Vulnerability details

Impact

The queueWithdraw() function helps to the user to queue his withdrawals and the processWithdraws() function helps to process all the users withdrawals.

The problem here is that the attacker can flood the withdrawalQueue with zero amounts, causing the processWithdraws() cost more gas to process the user withdrawals. The same behaivour is happening in the KangarooVault.sol::initiateWithdrawal().

The withdrawals can be delayed by the attacker which generates mistrust in the protocol to the users.

Proof of Concept

I created a test in LiquidityPool.Deposits.t.sol where it is possible to flood the withdrawalQueue with zero amount via the queueWithdraw() function.

function testZeroQueueWithdraw() public {
    // It is possible to flood the withdrawalQueue with zero amounts in the queueWithdraw()
    // causing a DOS in the processWithdraws()
    //
    // 1. Deposit some money in order to have totalFunds
    //
    susd.mint(user_2, 1e20);
    vm.startPrank(user_2);
    susd.approve(address(pool), 1e20);
    pool.deposit(69e18, user_2);
    vm.stopPrank();
    //
    // 2. Call queueWithdraw with zero withdraw amount
    //
    pool.queueWithdraw(0, user_1);
    pool.queueWithdraw(0, user_2);
    pool.queueWithdraw(0, user_3);
    pool.queueWithdraw(0, address(1337));
    //
    // 3. Assert queuedWithdrawalHead() and nextQueuedWithdrawalId()
    //
    assertEq(pool.queuedWithdrawalHead(), 1);
    assertEq(pool.nextQueuedWithdrawalId(), 5);
    //
    // 4. Call processWithdraws()
    //
    vm.warp(block.timestamp + pool.minWithdrawDelay());
    pool.processWithdraws(1);
    //
    // 5. Assert the increase of queuedWithdrawalHead()
    //
    assertEq(pool.queuedWithdrawalHead(), 2);
}

Tools used

VScode

Check the zero amount value in the LiquidityPool.sol::queueWithdraw() and KangarooVault.sol::initiateWithdrawal() functions.

#0 - JustDravee

2023-03-22T17:32:28Z

Seems valid. It's more of a Grief attack than a DOS one but both are still very similar and considered as Medium severity findings.

The POC is easy to understand and runs well. Would've loved lines with vm.expectRevert to showcase a scenario/story of multiple griefing attacks over multiple minWithdrawDelay but this is already excellent.

#1 - c4-judge

2023-03-22T17:32:36Z

JustDravee marked the issue as satisfactory

#2 - c4-judge

2023-03-22T17:40:48Z

JustDravee marked the issue as primary issue

#3 - c4-sponsor

2023-04-04T10:58:10Z

mubaris marked the issue as sponsor confirmed

#4 - c4-judge

2023-05-03T01:28:21Z

JustDravee marked issue #123 as primary and marked this issue as a duplicate of 123

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter