Polynomial Protocol contest - sorrynotsorry'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: 34/35

Findings: 1

Award: $78.86

🌟 Selected for report: 0

🚀 Solo Findings: 0

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-L215 https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/LiquidityPool.sol#L264-L280

Vulnerability details

Impact

Processing deposits and withdrawals can be griefed, possbile DoS

Proof of Concept

LiquidityPool contract utilizes queueDeposit() and queueWithdraw() functions to slate the liquidity deposits and withdrawals. Accordingly, the users are given Deposit and Withdraw Id's which are to be processed by calling processDeposits() and processWithdraws()

Both queueDeposit() and queueWithdraw() functions are as below;

queueDeposit();

function queueDeposit(uint256 amount, address user)
    external
    override
    nonReentrant
    whenNotPaused("POOL_QUEUE_DEPOSIT")
{
    QueuedDeposit storage newDeposit = depositQueue[nextQueuedDepositId];
    newDeposit.id = nextQueuedDepositId++;
    newDeposit.user = user;
    newDeposit.depositedAmount = amount;
    newDeposit.requestedTime = block.timestamp;
    totalQueuedDeposits += amount;
    SUSD.safeTransferFrom(msg.sender, address(this), amount);

    emit InitiateDeposit(newDeposit.id, msg.sender, user, amount);
}

And queueWithdraw();

function queueWithdraw(uint256 tokens, address user)
    external
    override
    nonReentrant
    whenNotPaused("POOL_QUEUE_WITHDRAW")
{
    require(liquidityToken.balanceOf(msg.sender) >= tokens);
    QueuedWithdraw storage newWithdraw = withdrawalQueue[nextQueuedWithdrawalId];
    newWithdraw.id = nextQueuedWithdrawalId++;
    newWithdraw.user = user;
    newWithdraw.withdrawnTokens = tokens;
    newWithdraw.requestedTime = block.timestamp;
    totalQueuedWithdrawals += tokens;
    liquidityToken.burn(msg.sender, tokens);

    emit InitiateWithdrawal(newWithdraw.id, msg.sender, user, tokens);
}

And the ID's distributed in these functions are handled in processDeposits() and processWithdraws() functions as below;

processDeposits();

function processDeposits(uint256 count) external override nonReentrant whenNotPaused("POOL_PROCESS_DEPOSITS") {
    assert(queuedDepositHead + count - 1 < nextQueuedDepositId);
    uint256 tokenPrice = getTokenPrice();

    for (uint256 i = 0; i < count; i++) {
        QueuedDeposit storage current = depositQueue[queuedDepositHead];

        if (current.requestedTime == 0 || block.timestamp < current.requestedTime + minDepositDelay) {
            return;
        }
        uint256 tokensToMint = current.depositedAmount.divWadDown(tokenPrice);

        current.mintedTokens = tokensToMint;
        totalQueuedDeposits -= current.depositedAmount;
        totalFunds += current.depositedAmount;
        liquidityToken.mint(current.user, tokensToMint);

        emit ProcessDeposit(current.id, current.user, current.depositedAmount, tokensToMint, current.requestedTime);

        current.depositedAmount = 0;
        queuedDepositHead++;
    }
}

And processWithdraws();

function processWithdraws(uint256 count) external override nonReentrant whenNotPaused("POOL_PROCESS_WITHDRAWS") {
    assert(queuedWithdrawalHead + count - 1 < nextQueuedWithdrawalId);

    for (uint256 i = 0; i < count; i++) {
        uint256 tokenPrice = getTokenPrice();
        QueuedWithdraw storage current = withdrawalQueue[queuedWithdrawalHead];

        if (current.requestedTime == 0 || block.timestamp < current.requestedTime + minWithdrawDelay) {
            return;
        }

        uint256 availableFunds = uint256(int256(totalFunds) - usedFunds);

        if (availableFunds == 0) {
            return;
        }

        uint256 susdToReturn = current.withdrawnTokens.mulWadDown(tokenPrice);

        // Partial withdrawals if not enough available funds in the vault
        // Queue head is not increased
        if (susdToReturn > availableFunds) {
            current.returnedAmount = availableFunds;
            uint256 tokensBurned = availableFunds.divWadUp(tokenPrice);
            totalQueuedWithdrawals -= tokensBurned;
            current.withdrawnTokens -= tokensBurned;
            totalFunds -= availableFunds;
            SUSD.safeTransfer(current.user, availableFunds);

            emit ProcessWithdrawalPartially(
                current.id, current.user, tokensBurned, availableFunds, current.requestedTime
                );

            return;
        } else {
            // Complete full withdrawal
            current.returnedAmount = susdToReturn;
            totalQueuedWithdrawals -= current.withdrawnTokens;
            totalFunds -= susdToReturn;
            SUSD.safeTransfer(current.user, susdToReturn);

            emit ProcessWithdrawal(
                current.id, current.user, current.withdrawnTokens, susdToReturn, current.requestedTime
                );

            current.withdrawnTokens = 0;
        }
        queuedWithdrawalHead++;
    }
}

As can be seen in queueDeposit() and queueWithdraw() functions, both functions accept Zero amounts. Accordingly one can queue a deposit of 0 tokens. And if it's intended to attack the protocol, this action can be done multiple times to inflate the deposit queue at the cost of transaction gas fees. Same can be done to withdraw queue while holding some liquidity tokens. As a result, the process of deposits and withdrawals will be griefed since nextQueuedDepositId and nextQueuedWithdrawalId will reach higher numbers.

Tools Used

Manual Review

Do not allow queueing zero-amount deposits and withdrawals.

#0 - c4-judge

2023-03-22T17:45:07Z

JustDravee marked the issue as duplicate of #122

#1 - JustDravee

2023-03-22T17:58:33Z

Note: Marking as duplicate for now but this mentions both queueWithdraw and queueDeposit compared to https://github.com/code-423n4/2023-03-polynomial-findings/issues/122 which only mentions queueWithdraw. This one is more complete but lacks a coded POCs compared to the other. Still valid.

#2 - c4-judge

2023-05-03T01:29:36Z

JustDravee marked the issue as satisfactory

#3 - c4-judge

2023-05-16T00:04:39Z

JustDravee changed the severity to 2 (Med Risk)

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