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: 26/80
Findings: 2
Award: $132.61
🌟 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
On the PrizeVault
contract, the yield fee recipient calls the claimYieldFeeShares
function to receive yield fee shares
function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient { if (_shares == 0) revert MintZeroShares(); uint256 _yieldFeeBalance = yieldFeeBalance; if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance); yieldFeeBalance -= _yieldFeeBalance; // @audit wrong update, 'yieldFeeBalance` = 0 now _mint(msg.sender, _shares); emit ClaimYieldFeeShares(msg.sender, _shares); }
You can see that the update of yieldFeeBalance
is updated wrongly in which it is set to zero while it should be subtracted from _shares
If the fee recipient wants to receive a portion of the fee shares, the left shares will not be redeemable because the state is set to zero.
Consider the following example:
30
20
shares, he calls claimYieldFeeShares(20)
yieldFeeBalance
is updated as follows: yieldFeeBalance -= _yieldFeeBalance = 0
10
shares left.Manual Review
Two possible approaches to solve the issue:
yieldFeeBalance
update to be subtracted from _shares
function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient { if (_shares == 0) revert MintZeroShares(); uint256 _yieldFeeBalance = yieldFeeBalance; if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance); - yieldFeeBalance -= _yieldFeeBalance; + yieldFeeBalance -= _shares; _mint(msg.sender, _shares); emit ClaimYieldFeeShares(msg.sender, _shares); }
_shares
from function's params)function claimYieldFeeShares(uint256 _shares) external onlyYieldFeeRecipient { if (_shares == 0) revert MintZeroShares(); uint256 _yieldFeeBalance = yieldFeeBalance; if (_shares > _yieldFeeBalance) revert SharesExceedsYieldFeeBalance(_shares, _yieldFeeBalance); yieldFeeBalance -= _yieldFeeBalance; - _mint(msg.sender, _shares); + _mint(msg.sender, _yieldFeeBalance); emit ClaimYieldFeeShares(msg.sender, _shares); }
Error
#0 - c4-pre-sort
2024-03-11T21:39:56Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-11T21:40:03Z
raymondfam marked the issue as duplicate of #10
#2 - c4-pre-sort
2024-03-13T04:38:11Z
raymondfam marked the issue as duplicate of #59
#3 - c4-judge
2024-03-15T07:40:39Z
hansfriese marked the issue as satisfactory
🌟 Selected for report: Aymen0909
Also found by: 0xmystery, Abdessamed, FastChecker, Giorgio, Tripathi, btk, cheatc0d3, trachev, turvy_fuzz
131.1445 USDC - $131.14
The function PrizeVault::withdraw
withdraws assets
by burning shares
function withdraw( uint256 _assets, address _receiver, address _owner ) external returns (uint256) { uint256 _shares = previewWithdraw(_assets); _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets); return _shares; }
Assets and shares are meant to be 1:1, but if the vault controls less assets than what has been deposited, a share will be worth a proportional amount of the total assets:
if (_totalAssets >= totalDebt_) { return _assets; } else { // Follows the inverse conversion of `convertToAssets` return _assets.mulDiv(totalDebt_, _totalAssets, Math.Rounding.Up); }
The issue is that the withdraw
function does not protect users from burning too many shares when the contract controls less assets than the debts.
Too many shares can be burned that are not expected by users.
Manual Review
Consider checking for max shares to be burned when calling withdraw
. Below is the updated code:
function withdraw( uint256 _assets, address _receiver, address _owner, + uint256 _maxSharesOut ) external returns (uint256) { uint256 _shares = previewWithdraw(_assets); + if (_shares > _maxSharesOut) revert burningSharesExceedsMax(); _burnAndWithdraw(msg.sender, _receiver, _owner, _shares, _assets); return _shares; }
Error
#0 - c4-pre-sort
2024-03-11T23:36:58Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-03-11T23:37:06Z
raymondfam marked the issue as duplicate of #90
#2 - c4-pre-sort
2024-03-13T05:09:50Z
raymondfam marked the issue as duplicate of #274
#3 - c4-judge
2024-03-15T08:09:34Z
hansfriese marked the issue as satisfactory