Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $36,500 USDC
Total HM: 9
Participants: 80
Period: 7 days
Judge: hansfriese
Total Solo HM: 2
Id: 332
League: ETH
Rank: 47/80
Findings: 1
Award: $1.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: DarkTower
Also found by: 0xJaeger, 0xJoyBoy03, 0xRiO, 0xkeesmark, 0xlemon, 0xmystery, Abdessamed, AcT3R, Afriauditor, AgileJune, Al-Qa-qa, Aymen0909, Daniel526, DanielTan_MetaTrust, Dots, FastChecker, Fitro, GoSlang, Greed, Krace, McToady, SoosheeTheWise, Tripathi, asui, aua_oo7, btk, crypticdefense, d3e4, dd0x7e8, dvrkzy, gesha17, iberry, kR1s, leegh, marqymarq10, n1punp, pa6kuda, radin100, sammy, smbv-1923, trachev, turvy_fuzz, valentin_s2304, wangxx2026, y4y, yotov721, yvuchev, zhaojie
1.4652 USDC - $1.47
The protocol can lose out on Yield Fee if the entirety of the Yield Fee is not claimed.
The Yield Fee is accrued gradually through calls to PrizeVault::transferTokensOut()
. Overtime, yieldFeeBalance
grows large enough to withdraw assets from the vault (via minting shares to the vault). However, If the yieldFeeRecipient
does not claim the entirety of the yieldFeeBalance
, the assets are no longer redeemable for the Yield Fee. This is the case because PrizeVault::claimYieldFeeShares()
subtracts _yieldFeeBalance
from yieldFeeBalance
instead of subtracting the shares that are being claimed. Line 617 of PrizeVault.sol
is where the error in calculation stems from:
yieldFeeBalance -= _yieldFeeBalance;
If we add the following test case it demonstrates the loss of Yield Fee:
function testLostYieldFee() public { uint256 amount = 1 ether; underlyingAsset.mint(alice, amount * 10); uint32 maxFee = 9e8; vault.setYieldFeePercentage(uint32(9e8)); vm.startPrank(alice); underlyingAsset.approve(address(vault), amount); vault.deposit(amount, alice); vm.stopPrank(); uint256 totalAssetsBefore = vault.totalAssets(); uint256 totalSupplyBefore = vault.totalSupply(); vm.startPrank(vault.liquidationPair()); _accrueYield(underlyingAsset, vault, 10 ether); uint256 availableYield = vault.availableYieldBalance(); vault.transferTokensOut(address(0), bob, address(underlyingAsset), 0.1 ether); vm.stopPrank(); uint256 startBalance = vault.balanceOf(address(this)); uint256 possibleYieldFee = vault.yieldFeeBalance(); vault.claimYieldFeeShares(1); uint256 newYieldFee = vault.yieldFeeBalance(); uint256 endBalance = vault.balanceOf(address(this)); console.log("possibleYieldFee : ", possibleYieldFee); console.log("newYieldFee : ", newYieldFee); console.log("startBalance : ", startBalance); console.log("endBalance : ", endBalance); }
OUTPUT:
possibleYieldFee : 900000000000000000 newYieldFee : 0 startBalance : 0 endBalance : 1
The output demonstrates that if the entirety of the YieldFee is not claimed at once, it is reset to 0 (newYieldFee
).
Manual Analysis & Foundry
Update PrizeVault::claimYieldFeeShares()
's calculation of yieldFeeBalance
. Instead of subtracting _yieldFeeBalance
, subtract _shares
instead.
function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient { if (_shares == 0) revert MintZeroShares(); uint256 _yieldFeeBalance = yieldFeeBalance; if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance); yieldFeeBalance -= _shares; // updates from "_yieldFeeBalance" to "_shares" _mint(msg.sender, _shares); emit ClaimYieldFeeShares(msg.sender, _shares); }
Error
#0 - c4-pre-sort
2024-03-11T21:28:37Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-11T21:29:10Z
raymondfam marked the issue as duplicate of #10
#2 - c4-pre-sort
2024-03-13T04:37:59Z
raymondfam marked the issue as duplicate of #59
#3 - c4-judge
2024-03-15T07:37:31Z
hansfriese changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-15T07:40:49Z
hansfriese marked the issue as satisfactory