Popcorn contest - peakbolt'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: 7/169

Findings: 4

Award: $1,935.79

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: rvierdiiev

Also found by: peakbolt

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-70

Awards

1740.5701 USDC - $1,740.57

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L496-L499 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L447-L460 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L138 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L174 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L215 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L257

Vulnerability details

Impact

In Vault.sol, the syncFeeCheckpoint modifier sets the highWaterMark to share price, which affects accruedPerformanceFee(), resulting in loss of fee for the owner.

A performance fee can be taken by the vault owner (shareValue - highWaterMark), when shareValue > highWaterMark. However, Vault.deposit(), Vault.mint() and Vault.withdraw() have syncFeeCheckpoint modifiers, which will reset the highWaterMark to share price before the performance fee is taken. So if deposit/mint/withdraw is performed after the vault share price increased, the vault owner will lose the performance fee and be shortchanged.

Note: redeem() does not have this issue as it is somehow missing the syncFeeCheckpoint.

modifier syncFeeCheckpoint() { _; highWaterMark = convertToAssets(1e18); } 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; }

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L496-L499 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L447-L460 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L138 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L174 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L215 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L257

Remove SyncFeeCheckpoint modifier.

#0 - c4-judge

2023-02-16T06:11:16Z

dmvt marked the issue as duplicate of #30

#1 - c4-judge

2023-02-16T06:16:43Z

dmvt marked the issue as not a duplicate

#2 - c4-judge

2023-02-16T06:16:52Z

dmvt marked the issue as duplicate of #118

#3 - c4-sponsor

2023-02-18T11:52:11Z

RedVeil marked the issue as sponsor confirmed

#4 - c4-judge

2023-02-23T11:18:39Z

dmvt marked the issue as not a duplicate

#5 - c4-judge

2023-02-23T11:18:57Z

dmvt marked the issue as duplicate of #70

#6 - c4-judge

2023-02-23T11:23:50Z

dmvt marked the issue as selected for report

#7 - c4-judge

2023-02-23T11:23:59Z

dmvt marked the issue as not selected for report

#8 - c4-judge

2023-02-23T11:24:05Z

dmvt marked issue #70 as primary and marked this issue as a duplicate of 70

#9 - c4-judge

2023-03-01T00:09:47Z

dmvt marked the issue as satisfactory

Findings Information

Awards

7.466 USDC - $7.47

Labels

bug
2 (Med Risk)
partial-50
sponsor confirmed
upgraded by judge
duplicate-558

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L438-L450 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L162 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L232

Vulnerability details

Impact

In AdapterBase.sol, the harvest() does not update lastHarvest after the harvest delegatecall to strategy. Due to that, the harvest cool down check does not have any effect as it will always be true and trigger the delegate call to strategy.

This will cause every _deposit() and _withdraw() calls to waste gas and tokens as they will call harvest() at the end.

function harvest() public takeFees { if ( address(strategy) != address(0) && ((lastHarvest + harvestCooldown) < block.timestamp) ) { // solhint-disable address(strategy).delegatecall( abi.encodeWithSignature("harvest()") ); } emit Harvested(); }

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L438-L450 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L162 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L232

Update the lastHarvest upon successful harvest call.

#0 - c4-sponsor

2023-02-19T11:28:51Z

RedVeil marked the issue as sponsor confirmed

#1 - c4-judge

2023-02-25T23:46:56Z

dmvt changed the severity to G (Gas Optimization)

#2 - c4-judge

2023-02-28T23:59:51Z

dmvt changed the severity to QA (Quality Assurance)

#3 - c4-judge

2023-03-01T00:00:56Z

dmvt changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-03-01T00:00:56Z

dmvt changed the severity to 2 (Med Risk)

#5 - c4-judge

2023-03-01T00:01:14Z

dmvt marked the issue as duplicate of #558

#6 - c4-judge

2023-03-01T00:01:19Z

dmvt marked the issue as partial-50

Findings Information

🌟 Selected for report: GreedyGoblin

Also found by: 0xNineDec, chaduke, jasonxiale, peakbolt

Labels

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

Awards

152.2651 USDC - $152.27

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L434 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L540-L546

Vulnerability details

Impact

In Vault.sol, accruedManagementFee() will compute the wrong management fee amount when the management fee is changed from zero to non-zero value.

As feesUpdatedAt is not updated in changeFees(), the management fee will be computed from the time of vault deployment, instead of new fee effective time.

A malicious vault owner could create a vault with zero management fee and increase it after a long period, taking it more fees than the user expected, and then withdraw the fees. Even though there is a quit period, users are likely to stay if the fees are reasonable as they are unaware of the management fee miscalculation.

Note: even if the management fee is initially non-zero, the fee calculation could still be miscalculated and backdated with the new fee settings. This is because feesUpdatedAt is updated during fee accrual, which could be before the new fee takes effect.

function accruedManagementFee() public view returns (uint256) { uint256 managementFee = fees.management; return managementFee > 0 ? managementFee.mulDiv( totalAssets() * (block.timestamp - feesUpdatedAt), SECONDS_PER_YEAR, Math.Rounding.Down ) / 1e18 : 0; } function changeFees() external { if (block.timestamp < proposedFeeTime + quitPeriod) revert NotPassedQuitPeriod(quitPeriod); emit ChangedFees(fees, proposedFees); fees = proposedFees; }

https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L434 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L540-L546

Proof of Concept

Add this test case to Vault.t.sol

function test__managementFee_peakbolt() public { //start with zero fees _setFees(0, 0, 0, 0); //alice deposit 1000 ETH uint256 depositAmount = 1000 ether; asset.mint(alice, depositAmount); vm.startPrank(alice); asset.approve(address(vault), depositAmount); vault.deposit(depositAmount, alice); vm.stopPrank(); // set 10% mgmt fee after 1 year, take effect after 3 day quit period vm.roll(block.timestamp + 365.25 days); _setFees(0, 0, 1e17, 0); // fee recipient balance should be zero as no fee collected so far assertEq(vault.balanceOf(feeRecipient),0); // vault total supply should be same as deposit amount as no shares minted for fees assertEq(vault.totalSupply(), depositAmount); // take management fee, which will be higher than expected vault.takeManagementAndPerformanceFees(); // vault.balanceOf(feeRecipient) expected to be zero as management fee just took effect. // however, this will fail as vault.accruedManagementFee() compute the fee from the vault creation date. assertEq(vault.balanceOf(feeRecipient), 0); }

In changeFees(), add takeFees modifier to compute the accrued fees before new fee take effect, it will also set feesUpdatedAt = block.timestamp.

#0 - c4-sponsor

2023-02-17T11:09:09Z

RedVeil marked the issue as sponsor confirmed

#1 - c4-judge

2023-02-23T00:27:55Z

dmvt marked the issue as satisfactory

#2 - c4-judge

2023-02-23T01:18:24Z

dmvt changed the severity to 2 (Med Risk)

#3 - c4-judge

2023-02-23T01:24:23Z

dmvt marked issue #466 as primary and marked this issue as a duplicate of 466

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