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: 2/80
Findings: 2
Award: $5,314.56
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: 0xhunter20
4400.588 USDC - $4,400.59
yieldFeeBalance
wouldn't be claimed after calling transferTokensOut()
due to the twab supply limit.
When _tokenOut == address(this)
, liquidatableBalanceOf()
mints shares to the receiver and accumulates yieldFeeBalance
accordingly.
But when it checks the maximum liquidatable amount in liquidatableBalanceOf(), it validates the twap supply limit with the liquidYield only and it might meet the supply limit while minting yieldFeeBalance like the below.
totalSupply = 6e28, yieldFeeBalance = 0, twabSupplyLimit = 2^96 - 1 = 7.9e28
and the vault has enough available yield.liquidatableBalanceOf(_tokenOut = address(this))
will return _maxAmountOut = 7.9e28 - 6e28 = 1.9e28
when _liquidYield > _maxAmountOut.transferTokensOut()
with _amountOut = 1.9e28
, _yieldFee will be added to yieldFeeBalance
but it can't be claimed as we met the twap supply limit already.Manual Review
liquidatableBalanceOf() shouldn't apply yieldFeePercentage
to compare with _maxAmountOut
when _tokenOut == address(this)
.
function liquidatableBalanceOf(address _tokenOut) public view returns (uint256) { uint256 _totalSupply = totalSupply(); uint256 _maxAmountOut; if (_tokenOut == address(this)) { // Liquidation of vault shares is capped to the TWAB supply limit. _maxAmountOut = _twabSupplyLimit(_totalSupply); } else if (_tokenOut == address(_asset)) { // Liquidation of yield assets is capped at the max yield vault withdraw plus any latent balance. _maxAmountOut = _maxYieldVaultWithdraw() + _asset.balanceOf(address(this)); } else { return 0; } // The liquid yield is computed by taking the available yield balance and multiplying it // by (1 - yieldFeePercentage), rounding down, to ensure that enough yield is left for the // yield fee. uint256 _liquidYield = _availableYieldBalance(totalAssets(), _totalDebt(_totalSupply)); if (_tokenOut == address(this)) { if (_liquidYield >= _maxAmountOut) { //compare before applying yieldFeePercentage _liquidYield = _maxAmountOut; } _liquidYield = _liquidYield.mulDiv(FEE_PRECISION - yieldFeePercentage, FEE_PRECISION); } else { _liquidYield = _liquidYield.mulDiv(FEE_PRECISION - yieldFeePercentage, FEE_PRECISION); if (_liquidYield >= _maxAmountOut) { //same as before _liquidYield = _maxAmountOut; } } return _liquidYield; }
Invalid Validation
#0 - c4-pre-sort
2024-03-13T01:50:55Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-03-13T01:56:07Z
raymondfam marked the issue as duplicate of #319
#2 - raymondfam
2024-03-13T01:58:58Z
On top of the comments on #319, _tokenOut is just a specifier whether to mint shares or to withdraw assets to the receiver. And the yieldFee added to yieldFeeBalance will be incorporated into totalDebt. The yield fee recipient will be able to claim it anytime unrestricted via the pull method.
#3 - c4-judge
2024-03-15T15:38:39Z
hansfriese marked the issue as not a duplicate
#4 - hansfriese
2024-03-18T14:51:06Z
The impact is the same as #91 but the flaw still exists after mitigating #91 because liquidatableBalanceOf()
doesn't use the newly accumulated yield fees while checking _twabSupplyLimit
.
#5 - c4-judge
2024-03-18T14:56:40Z
hansfriese marked the issue as satisfactory
#6 - trmid
2024-03-18T15:09:20Z
Similar to #91, this issue outlines the need for the TWAB supply limit checks to account for the yield fee balance so that the entire yield fee balance is always available to be realized as shares.
#7 - c4-sponsor
2024-03-18T15:09:25Z
trmid (sponsor) confirmed
#8 - c4-judge
2024-03-18T15:14:04Z
hansfriese marked the issue as selected for report
#9 - trmid
2024-03-20T02:14:54Z
🌟 Selected for report: pa6kuda
Also found by: 0xhunter20, Afriauditor
913.9683 USDC - $913.97
The yield fee recipient couldn't claim his yield fee shares due to the twap supply limit.
The yield fee recipient can claim already earned yield fee shares using claimYieldFeeShares().
Due to the TwabController's limits, we check the available supply using _twabSupplyLimit() but we don't check yieldFeeBalance
that can be claimed any time.
So the below case would be possible.
totalSupply = 6e28, yieldFeeBalance = 1e28, twabSupplyLimit = 2^96 - 1 = 7.9e28
.twabSupplyLimit - totalSupply = 1.9e28
.deposit()
with the above maximum amounts and totalSupply = twabSupplyLimit
now.Manual Review
_twabSupplyLimit()
should calculate the remaining supply using totalSupply + yieldFeeBalance
and deposit()
should validate the limit as well.
function _twabSupplyLimit(uint256 _totalSupply) internal pure returns (uint256) { unchecked { return type(uint96).max - _totalSupply - yieldFeeBalance; } }
Invalid Validation
#0 - c4-pre-sort
2024-03-13T01:49:24Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-03-13T01:49:39Z
raymondfam marked the issue as duplicate of #64
#2 - c4-judge
2024-03-18T01:56:26Z
hansfriese marked the issue as not a duplicate
#3 - c4-judge
2024-03-18T01:57:57Z
hansfriese changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-03-18T01:58:06Z
hansfriese marked the issue as duplicate of #91
#5 - c4-judge
2024-03-18T01:58:25Z
hansfriese marked the issue as unsatisfactory: Invalid
#6 - c4-judge
2024-03-18T14:34:21Z
hansfriese marked the issue as satisfactory