Platform: Code4rena
Start Date: 18/04/2024
Pot Size: $36,500 USDC
Total HM: 19
Participants: 183
Period: 7 days
Judge: Koolex
Id: 367
League: ETH
Rank: 70/183
Findings: 4
Award: $51.93
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xAlix2
Also found by: 0xfox, 0xlemon, 0xnev, 3docSec, Aamir, Abdessamed, Dudex_2004, Egis_Security, Evo, FastChecker, Honour, Jorgect, KupiaSec, Limbooo, MrPotatoMagic, SpicyMeatball, TheSchnilch, alix40, bhilare_, favelanky, forgebyola, ke1caM, kennedy1030, koo, oakcobalt, petro_1912, shikhar229169
32.4128 USDC - $32.41
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L150
There is an explicit invariant within the DYAD system, that is
At least $1 of non-Kerosene collateral per DYAD minted in their Note upon the successful execution of the mint or withdrawal transaction.
This check is implemented as per the following within withdrawals:
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); uint value = amount * _vault.assetPrice() * 1e18 / 10**_vault.oracle().decimals() / 10**_vault.asset().decimals(); @> if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
The issue here is, during withdrawals from unbounded kerosene vaults, the value of kerosene is also included for the exogenous collateral sufficiency check.
The result is that even if there are sufficient exogenous collateral to meet minimum collateral ratio after withdrawal, the withdrawal will revert, potentially blocking withdrawals of kerosene unintentionally.
Manual Analysis
If kerosene withdrawals are intiated, set value
to zero, given the collateral ratio check would be sufficient in ensuring healthy CR ratio.
function withdraw( uint id, address vault, uint amount, address to, + bool withdrawKerosene ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); + uint value; + if (withdrawKerosene) { + value = 0; + } else { + value = amount * _vault.assetPrice() + * 1e18 + / 10**_vault.oracle().decimals() + / 10**_vault.asset().decimals(); + } - uint value = amount * _vault.assetPrice() - * 1e18 - / 10**_vault.oracle().decimals() - / 10**_vault.asset().decimals(); if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
DoS
#0 - c4-pre-sort
2024-04-26T21:20:27Z
JustDravee marked the issue as duplicate of #397
#1 - c4-pre-sort
2024-04-29T08:48:26Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:22:32Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:33:04Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: 0xAlix2
Also found by: 0x175, 0x486776, 0xnev, 3docSec, 3th, Aamir, Abdessamed, AlexCzm, Angry_Mustache_Man, Circolors, DedOhWale, Emmanuel, Giorgio, Honour, Jorgect, KupiaSec, Maroutis, Myrault, SBSecurity, Stefanov, T1MOH, VAD37, Vasquez, adam-idarrha, alix40, ducanh2706, falconhoof, iamandreiski, ke1caM, kennedy1030, koo, lian886, ljj, miaowu, pontifex, sashik_eth, shikhar229169, vahdrak1
7.3026 USDC - $7.30
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L221-L226
Notice in the liquidate()
function, collateral is only moved from liquidatee non-kerosene vaults (in vaults
mapping) to the liquidator note balance.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { uint cr = collatRatio(id); if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id)); uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr); @> uint numberOfVaults = vaults[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); vault.move(id, to, collateral); } emit Liquidate(id, msg.sender, to); }
In the previous vault manager, kerosene vaults were not licensed as collateral, so a CDP always start with at least CR of 150% non-kerosene collateral.
Now, CDP can start with at least 100% non-kerosene collateral + 50% kerosene value.
This can result in a lack of incentivization to liquidate specific CDPs backed by kerosene collateral even though liquidation bonus is present, leading to possible bad debt.
This issue can also be abused during volatile markets where price of collateral decreases, where users can front-run chainlink price updates and withdraw collateral for the price gap. This could also be exacerbated by depositing into bounded kerosene vaults where price is always 2x that of unbounded kerosene allowing users to maintain undercollaterized positions by abusing value of kerosene maintained.
We can make a comparision
VaultManagerV1: Assume 1 WETH = $2000 USD
VaultManagerV2:
Manual Analysis
Consider moving collateral from kerosene vaults as well stored within vaultsKerosene
Other
#0 - c4-pre-sort
2024-04-28T10:26:12Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:03:40Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:43:14Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: TheSavageTeddy
Also found by: 0x175, 0x486776, 0xnev, AamirMK, AlexCzm, ArmedGoose, BiasedMerc, CaeraDenoir, Egis_Security, Jorgect, KYP, MrPotatoMagic, PoeAudits, SBSecurity, SovaSlava, VAD37, adam-idarrha, alix40, carrotsmuggler, d_tony7470, dimulski, grearlake, josephdara, ljj, n0kto, okolicodes, sashik_eth, sil3th, turvy_fuzz
7.3512 USDC - $7.35
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L101
The maximum number of non-kerosene/kerosene vaults allowed to be added via remove()/removeKerosene()
is currently set as 5 represented by MAX_VAULTS/MAX_VAULTS_KEROSENE
. To remove a vault, the remaining asset in the vault must be zero as presented by the following check here and here
if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets();
Notice that depositing is permisionless, that is any user can deposit in place of other users to via deposit()
as long as NOTEs
While it is a good sanity check, in the future when multiple riskier assets/kerosene vaults are available (> 5 types of assets), where collateral price are more volatile, this could pose a problem if a user simply front-runs a remove call by depositing just 1 wei of asset into the vault for the user.
Take an example of a user having the maximum number of non-kerosene vaults (5) added, he wants to derisk from a volatile collateral (e.g. WETH) by redeeming all collateral and switching to a more stable vault such as a stablecoin vault (e.g. DAI, USDC etc ...), but cannot do so. If collateral prices keep falling, then the user either risk getting liquidated or is forced to burn additional DYAD to prevent getting liquidated instead of having the ability to switch vaults and redeem some collateral beforehand.
Manual Analysis
isDNftOwner
modifierDoS
#0 - c4-pre-sort
2024-04-28T20:02:55Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:26:02Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:38:16Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:39:26Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:33:06Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:33:11Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-05T21:33:16Z
koolexcrypto marked the issue as not a duplicate
#7 - c4-judge
2024-05-06T08:55:10Z
koolexcrypto marked the issue as duplicate of #118
#8 - c4-judge
2024-05-11T12:24:11Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: carrotsmuggler
Also found by: 0xAlix2, 0xSecuri, 0xblack_bird, 0xnev, AM, Al-Qa-qa, AlexCzm, Dudex_2004, Egis_Security, GalloDaSballo, Infect3d, Jorgect, KupiaSec, Ryonen, SpicyMeatball, T1MOH, VAD37, adam-idarrha, amaron, cu5t0mpeo, d3e4, darksnow, forgebyola, foxb868, itsabinashb, jesjupyter, nnez, peanuts, pontifex, wangxx2026, windhustler, zhuying
4.8719 USDC - $4.87
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L60-L64
The valuation of kerosene is calculated as:
whereby:
$K_{\text{value}} $ = Deterministic value of Kerosene $TVL$ = Total USD value of collateral in non-kerosene and kerosene vaults $D_{\text{supply}}$ = Total supply of DYAD minted ${K_{\text{supply}}}$ = Circulating supply of kerosene i.e kerosene distributed
Large liquidity providers can manipulate the deterministic value of kerosene by depositing large amounts of non-kerosene/kerosene collateral and withhold minting DYAD. This inflates the deterministic value of K.
These large LPs can possibly profit by targeting users that provides kerosene as collateral for DYAD by the following steps
deposit()
- Deposit large amounts of collateral into non-kerosene vaults to inflate TVL, but withhold minting DYAD. This inflates K valuewithdraw()
- Withdraw the large liquidity provided. This results in K value dropping sharplyliquidate()
- Target and liquidate users that provides kerosene as primary collateral for CDP using another account holding DYAD mintedExecuting a large deposits withdrawals and subsequently liquidate other users since kerosene prices can drop sharply, which consequently sharply decreases collateral ratio of users using kerosene as collateral. This means non-kerosene depositors can potentially force liquidations on kerosene depositors using two separate accounts.
Example:
liquidate
on user using surplus DYAD minted from non-kerosene vault, burning 10_000 DYAD and gaining ~73.4% of the users non-kerosene collateral
0.734 * 10000 = 7340
$ K_{\text{value}}$ = 3 million - 1 million / 100 million = $0.02 USDThis issue is possibly compounded because any existing users that provided kerosene as collateral could also now mint additional DYAD (as long as DYAD is backed 1:1 by non-kerosene collateral), whereby if they do so, $ K_{\text{value}}$ would drop further due to increase in $D_{\text{supply}}$. They are exposed to higher risk of getting liquidated as well as they are now exposed to a higher CDP based on previously inflated kerosene value.
Then when the large liquidity withdrawal is executed, liquidation might be even more profitable since $ K_{\text{value}}$ would drop even more.
The only external condition here is the CDP must partially be backed by kerosene as collateral.
Manual Analysis
I believe the fix is non-trivial, and valuation of K could be required to be refactored.
Other
#0 - c4-pre-sort
2024-04-28T06:02:32Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:06:19Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T09:59:11Z
koolexcrypto changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-05-08T11:50:06Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-08T12:51:27Z
koolexcrypto marked the issue as satisfactory