Popcorn contest - minhtrng's results

A multi-chain regenerative yield-optimizing protocol.

General Information

Platform: Code4rena

Start Date: 31/01/2023

Pot Size: $90,500 USDC

Total HM: 47

Participants: 169

Period: 7 days

Judge: LSDan

Total Solo HM: 9

Id: 211

League: ETH

Popcorn

Findings Distribution

Researcher Performance

Rank: 46/169

Findings: 4

Award: $345.36

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: waldenyan20

Also found by: 0xRobocop, KIntern_NA, Ruhum, cccz, hansfriese, mert_eren, minhtrng, peanuts

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
upgraded by judge
duplicate-424

Awards

185.0021 USDC - $185.00

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L305-L314

Vulnerability details

Impact

In MultiRewardStaking.changeRewardSpeed the new rewardsEndTimetamp is calculated based on the current balance of reward tokens in the contract. However, a fraction of this balance might already be accrued and accounted as reward, but just has not been claimed yet. This can cause more tokens to be claimable than the contract contains, leading to a "first come first serve" scenario for users concerning rewards.

Proof of Concept

The function MultiRewardStaking.changeRewardSpeed uses the current reward token balance of the contract as input for the calculation of the new rewardsEndTimeStamp:

uint256 remainder = rewardToken.balanceOf(address(this));

uint32 prevEndTime = rewards.rewardsEndTimestamp;
uint32 rewardsEndTimestamp = _calcRewardsEnd(
    prevEndTime > block.timestamp ? prevEndTime : block.timestamp.safeCastTo32(),
    rewardsPerSecond,
    remainder
);
rewardInfos[rewardToken].rewardsPerSecond = rewardsPerSecond;
rewardInfos[rewardToken].rewardsEndTimestamp = rewardsEndTimestamp;

The idea is that the current balance of reward tokens is what still needs to be distributed (as the name remainder implies). However, as mentioned under #Impact a fraction of this balance might already be attributed to users and is just unclaimed. Using the full balance will lead to rewardsEndTimeStamp being later than it should be, causing more tokens to be accounted for claiming than the contract holds:

function _calcRewardsEnd(
    ...
    //amount = rewardToken.balanceOf(address(this));
    return (block.timestamp + (amount / uint256(rewardsPerSecond))).safeCastTo32();
}

Tools Used

Manual Review

Only the fraction of reward tokens that has not yet accrued should be used to calculate the rewardsEndTimestamp when changing the rewardspeed. A separate variable might be necessary to track the amount of tokens that are eligible to be claimed. That variable would need to be incremented on all _accrueStatic calls that occur when totalSupply > 0.

#0 - c4-judge

2023-02-16T03:56:45Z

dmvt marked the issue as duplicate of #156

#1 - c4-sponsor

2023-02-18T11:59:19Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T16:05:16Z

dmvt changed the severity to QA (Quality Assurance)

#3 - Simon-Busch

2023-02-23T16:15:33Z

Revert back to M as requested by @dmvt

#4 - c4-judge

2023-02-23T22:32:03Z

dmvt marked the issue as satisfactory

#5 - c4-judge

2023-02-25T14:31:49Z

dmvt marked the issue as not a duplicate

#6 - c4-judge

2023-02-25T14:31:57Z

dmvt marked the issue as duplicate of #191

#7 - c4-judge

2023-02-25T14:48:44Z

dmvt changed the severity to 3 (High Risk)

#8 - c4-judge

2023-02-25T15:27:46Z

dmvt marked the issue as partial-50

#9 - c4-judge

2023-03-01T00:41:54Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: immeas

Also found by: KIntern_NA, bin2chen, minhtrng, rbserver, rvierdiiev, ustas, yellowBirdy

Labels

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

Awards

69.3758 USDC - $69.38

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L496-L499

Vulnerability details

Impact

The Vault contract syncs the highWaterMark on deposit, mint and withdraw, which can be used to avoid paying the performance fees, by performing any of these actions with a tiny amount. After Clarification with the sponsor, this was caused by a merge gone wrong and is not supposed to be there

Proof of Concept

The highWaterMark is used as a threshold to trigger the payment of a performance fee:

function accruedPerformanceFee() public view returns (uint256) {
    uint256 highWaterMark_ = highWaterMark;
    uint256 shareValue = convertToAssets(1e18);
    uint256 performanceFee = fees.performance;

    return
        performanceFee > 0 && shareValue > highWaterMark
            ? performanceFee.mulDiv(
                (shareValue - highWaterMark) * totalSupply(),
                1e36,
                Math.Rounding.Down
            )
            : 0;
}

The Vault contains the following modifier used for deposits, mints and withdraws:

modifier syncFeeCheckpoint() {
    _;
    highWaterMark = convertToAssets(1e18);
}

This modifier can be abused by performing tiny deposits, mints and withdraws to move the highWaterMark to current share values to avoid the triggering of the performance fee.

Tools Used

Manual Review

Remove the syncFeeCheckpoint modifier and all its usages from Vault

#0 - c4-judge

2023-02-16T06:11:45Z

dmvt marked the issue as duplicate of #30

#1 - c4-sponsor

2023-02-18T12:05:25Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T00:12:51Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: Ruhum

Also found by: 0xMirce, 0xRobocop, cccz, chaduke, gjaldon, minhtrng, rvierdiiev, ulqiorra

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor confirmed
duplicate-190

Awards

55.5006 USDC - $55.50

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L308-L312 https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L351-L360

Vulnerability details

Impact

The function _calcRewardsEnd is called with the previousEndTime as first parameter in MultiRewardStaking.changeRewardSpeed, which leads to wrong calculation of the new rewardsEndTimestamp, causing it to be later than it should be. This will lead to more rewards being accrued "on paper" than the contract contains.

Proof of Concept

The function MultiRewardStaking.changeRewardSpeed calls the _calcRewardsEnd function like this:

uint32 rewardsEndTimestamp = _calcRewardsEnd(
    prevEndTime > block.timestamp ? prevEndTime : block.timestamp.safeCastTo32(),
    rewardsPerSecond,
    remainder
);

If prevEndTime > block.timestamp == true then the following block in calcRewardsEnd will be executed:

if (rewardsEndTimestamp > block.timestamp)
    amount += uint256(rewardsPerSecond) * (rewardsEndTimestamp - block.timestamp);

A quick calculation why the lines above should not be executed in this case:

function _calcRewardsEnd(
uint32 rewardsEndTimestamp, // @note previousRewardsEndtime = 1000; block.Timestamp=700
uint160 rewardsPerSecond, //@note newRewardsPersecond = 10 (previous = 1) 
uint256 amount //@note currentRewardTokenBalance = 300
) internal returns (uint32) {
if (rewardsEndTimestamp > block.timestamp)
    //@note with values above: amount = 300 + 10*300 = 3300
    // this logic is only compatible with `fundReward` (where `amount` is meant to be added rewards, 
    //while the righthandside calculates current reward amounts left)
    amount += uint256(rewardsPerSecond) * (rewardsEndTimestamp - block.timestamp);

//@note 700 + 3300/10 = 1030 (should be 730, since no new tokens have entered the contract)
    //will try to distribute 330*10 = 3300 tokens even though there are only 300 in the contract
    //enables "theft" of rewards or permanent (?) lock of reward tokens (for some scenarios where each participant is entitled to more than the actual balance)
return (block.timestamp + (amount / uint256(rewardsPerSecond))).safeCastTo32();
}

Tools Used

Manual Review

Call calcRewardsEnd the same way as is done in the addRewardToken function: _calcRewardsEnd(0, rewardsPerSecond, amount); (first parameter = 0)

#0 - c4-judge

2023-02-16T03:56:54Z

dmvt marked the issue as duplicate of #156

#1 - c4-sponsor

2023-02-18T11:59:22Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T16:05:16Z

dmvt changed the severity to QA (Quality Assurance)

#3 - Simon-Busch

2023-02-23T16:16:05Z

Revert back to M as requested by @dmvt

#4 - c4-judge

2023-02-23T22:32:47Z

dmvt marked the issue as satisfactory

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