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: 84/183
Findings: 2
Award: $32.69
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Maroutis
Also found by: 0x486776, 0xShitgem, 0xabhay, 0xleadwizard, 0xlemon, 0xnilay, 0xtankr, 3docSec, AM, Aamir, Abdessamed, Al-Qa-qa, AlexCzm, Circolors, CodeWasp, Daniel526, Egis_Security, Emmanuel, Giorgio, Honour, Hueber, Infect3d, Krace, KupiaSec, LeoGold, Limbooo, PoeAudits, SBSecurity, SpicyMeatball, T1MOH, The-Seraphs, TheSavageTeddy, TheSchnilch, Topmark, VAD37, ZanyBonzy, adam-idarrha, bhilare_, btk, carlitox477, cinderblock, dimulski, falconhoof, grearlake, gumgumzum, iamandreiski, itsabinashb, josephdara, ke1caM, kennedy1030, ljj, n0kto, n4nika, neocrao, oakcobalt, petro_1912, pontifex, poslednaya, shaflow2, shikhar229169, web3km, ych18, zhaojohnson, zigtur
0.2831 USDC - $0.28
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L241-L286 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L67-L91
collatRatio
is an important factor that reflects the ratio of specific DNFT's TVL to minted DYAD.
It limits the amount of assets DNFT owner can withdraw from their vaults.
However, vaults
and vaultsKerosene
may contain the same Vault at the same time, so their values may be added twice to getTotalUsdValue and collatRatio.
This breaks the collateral mechanism of the DYAD protocol.
While adding a vault in vaults
array, it only checks if vault has been registered in vaultLicenser
and it has already been added.
function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); @> if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); @> if (!vaults[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
Likewise, while adding a kerosene vault, it only checks if vault has been registered in keroseneManager
and it has already been added.
function addKerosene( uint id, address vault ) external isDNftOwner(id) { if (vaultsKerosene[id].length() >= MAX_VAULTS_KEROSENE) revert TooManyVaults(); @> if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed(); @> if (!vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
And, in Deploy.V2 contract, we can see that ethVault
, and wstEth
are added in kerosineManager
and vaultLicenser
.
KerosineManager kerosineManager = new KerosineManager(); kerosineManager.add(address(ethVault)); kerosineManager.add(address(wstEth)); ... vaultLicenser.add(address(ethVault)); vaultLicenser.add(address(wstEth)); vaultLicenser.add(address(unboundedKerosineVault));
If DNFT owner adds ethVault
into vaults
and vaultsKerosene
, then getTotalUsdVaule
and collatRatio
is twice of real TVL in ethVault
.
Therefore, owners can mint DYAD with less collateral.
Manual review
Prevent to store vault together in vaults
and vaultsKerosene
by checking the vault existence in the two kinds of vaults while adding vault.
function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); -- if (!vaults[id].add(vault)) revert VaultAlreadyAdded(); ++ if (vaultsKerosene[id].contains(vault) || !vaults[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); } function addKerosene( uint id, address vault ) external isDNftOwner(id) { if (vaultsKerosene[id].length() >= MAX_VAULTS_KEROSENE) revert TooManyVaults(); if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed(); -- if (!vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); ++ if (vaults.contains(vault) || !vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
Math
#0 - c4-pre-sort
2024-04-28T06:48:06Z
JustDravee marked the issue as duplicate of #105
#1 - c4-pre-sort
2024-04-29T09:06:27Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T11:37:18Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-28T15:17:12Z
koolexcrypto removed the grade
#4 - c4-judge
2024-05-28T15:17:15Z
koolexcrypto marked the issue as satisfactory
#5 - c4-judge
2024-05-28T15:17:27Z
koolexcrypto marked the issue as not a duplicate
#6 - c4-judge
2024-05-28T15:17:34Z
koolexcrypto marked the issue as duplicate of #1133
🌟 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
UnboundedKeroseneVault allows owner to withdraw assets, but owner can't withdraw and redeem assets from that vault if value is much than nonKeroseneValue - dyadMinted
.
When withdrawing assets from the vault, the DYAD protocol requires that non-kerosene value must not be less than the minted DYAD token.
However, the withdraw
function does not check whether the withdrawing vault is kerosene or not.
If the withdrawing vault is a kind of kerosene, nonKeroseneVaule does not change after the asset is withdrawn from the vault.
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(); L150: if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault. Withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
As we can see in L150, nonKeroseneValue is decreased by the value of withdrawal assets regardless of type of vault. If vault is kerosene and value is greater than the difference between non Kerosene Value and minted dyad token, then owner can't withdraw kerosene(unbounded) asset.
Manual review
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(); ++ uint value; ++ if (vaults.contains(vault)) ++ 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(); }
Error
#0 - c4-pre-sort
2024-04-26T21:24:29Z
JustDravee marked the issue as duplicate of #397
#1 - c4-pre-sort
2024-04-29T08:48:09Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:22:30Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:33:04Z
koolexcrypto changed the severity to 3 (High Risk)