Platform: Code4rena
Start Date: 12/07/2022
Pot Size: $35,000 USDC
Total HM: 13
Participants: 78
Period: 3 days
Judge: 0xean
Total Solo HM: 6
Id: 135
League: ETH
Rank: 4/78
Findings: 2
Award: $3,240.28
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: hansfriese
Also found by: panprog
1005.6038 USDC - $1,005.60
https://github.com/code-423n4/2022-07-swivel/blob/fd36ce96b46943026cb2dfcb76dfa3f884f51c18/VaultTracker/VaultTracker.sol#L124 https://github.com/code-423n4/2022-07-swivel/blob/fd36ce96b46943026cb2dfcb76dfa3f884f51c18/VaultTracker/VaultTracker.sol#L104-L106
When VaultTracker
calculates yield for mature markets, it is calculated as $maturityRate/savedRate - 1$. This works for the first call, but it also saves current rate. If current rate is higher than maturityRate
, then any subsequent call to any VaultTracker
function with yield calculation will revert because yield will be negative. This includes redeemInterest
function, effectively making the user lose all amount not yet redeemed.
Post maturity it's possible to call Swivel.combineTokens
, MarketPlace.transferVaultNotional
or execute an order calling Swivel.initiateVaultFillingVaultExit
, exitVaultFillingVaultInitiate
, exitVaultFillingZcTokenExit
, all of which save new exchange rate.
It's also possible to intentionally lock Swivel's fee vault the same way by executing an order calling initiateVaultFillingZcTokenInitiate
or initiateVaultFillingZcTokenInitiate
, both of which call transferVaultNotionalFee
, which locks Swivel's notional vault (redeem will now revert).
Possible loss of redeemable amount for the user scenario:
exitVaultFillingVaultInitiate
0
.removeNotional
: new exchange rate (exchangeRate
> maturityRate
) is saved into the vaultredeemInterest
: maturityRate
< vlt.exchangeRate
, yield is negative and revertsif (maturityRate > 0) { // Calculate marginal interest if (maturityRate > vlt.exchangeRate) { // do not let yield to be negative yield = ((maturityRate * 1e26) / vlt.exchangeRate) - 1e26; } } else {
#0 - JTraversa
2022-07-15T23:14:55Z
Duplicate of #116
#1 - robrobbins
2022-08-04T20:30:05Z
see #116
#2 - bghughes
2022-08-04T23:33:39Z
Duplicate of #116
🌟 Selected for report: panprog
2234.6752 USDC - $2,234.68
https://github.com/code-423n4/2022-07-swivel/blob/fd36ce96b46943026cb2dfcb76dfa3f884f51c18/Tokens/ZcToken.sol#L99 https://github.com/code-423n4/2022-07-swivel/blob/fd36ce96b46943026cb2dfcb76dfa3f884f51c18/Tokens/ZcToken.sol#L92
If maturityRate
is still 0
after maturity deadline (because no transactions setting maturityRate
have been executed yet), then previewWithdraw
calculated amount (used by ZcToken.withdraw
function) is 0
and thus withdraw
function will send 0
underlying tokens to user, which might be very confusing to user. Subsequent call to the same function will send him correct amount.
The same problem applies to all view functions in ZcToken
contract - they use saved market maturityRate
, which can be 0
even past deadline time and functions revert or return 0
in this case.
Incorrect withdrawal behaviour:
ZcToken
s.ZcToken.withdraw
with some underlying amount.withdraw
: calculates previewAmount
from previewWithdraw
previewWithdraw
: multiplication by maturityRate
returns 0Add getMaturityRate
function to ZcToken
, which will return either market's maturityRate
or (if it's 0
) current market's exchangeRate
. Use this function instead of maturityRate
everywhere across ZcToken
.
#0 - robrobbins
2022-08-10T18:12:36Z