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

Findings: 1

Award: $69.38

🌟 Selected for report: 0

🚀 Solo Findings: 0

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/main/src/vault/Vault.sol#L496-L499 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L134-L140 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L170-L176 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/Vault.sol#L170-L176

Vulnerability details

Accrued performance fees are calculated of the difference between current value and the value recorded in highWaterMark.

DEPOSIT, MINT and WITHDRAW use syncFeeCheckpoint modifier which updates highWaterMark to current share value. If one of the above functions is called when there are any not yet taken performance fees they will be reset to 0 by virtue of setting highWaterMark to current share value.

As users will DEPOSIT, MINT and WITHDRAW possibly significantly more often than fees being taken most of the performance fees will be lost to the vault creator. It also be used to front run the takeManagementAndPerformanceFees calls.

Proof of Concept

Take the following test case where a deposit is performed after fee accrual event but before fee being taken.

function test__performanceFeeDisapearing() public { uint128 amount = 315576000; uint256 depositAmount = 1 ether; _setFees(0, 0, 0, 1e17); asset.mint(alice, depositAmount * 2); vm.startPrank(alice); asset.approve(address(vault), depositAmount); vault.deposit(depositAmount, alice); vm.stopPrank(); // Increase asset assets to trigger performanceFee asset.mint(address(adapter), amount); uint256 expectedFeeInAsseBefore = vault.accruedPerformanceFee(); vm.startPrank(alice); asset.approve(address(vault), depositAmount); vault.deposit(depositAmount, alice); vm.stopPrank(); uint256 expectedFeeInAssetAfter = vault.accruedPerformanceFee(); assertEq(expectedFeeInAssetAfter, expectedFeeInAsseBefore); }

it will with the following error:

Error: a == b not satisfied [uint] Expected: 31557600 Actual: 0

Tools Used

Forge

I can see two possible solutions:

  1. Take performance fees on every DEPOSIT, MINT, WITHDRAW and REDEEM.
  2. Move the performance fees to the adapter

I'm leaning towards the first as a more elegant one, keeping the separation of concerns between adapter and vault intact.

#0 - c4-judge

2023-02-16T06:10:09Z

dmvt marked the issue as primary issue

#1 - c4-sponsor

2023-02-17T09:39:59Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T00:12:29Z

dmvt marked the issue as satisfactory

#3 - c4-judge

2023-02-23T01:20:54Z

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

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