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: 37/183
Findings: 8
Award: $323.25
🌟 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/script/deploy/Deploy.V2.s.sol#L64-L65 https://github.com/code-423n4/2024-04-dyad/blob/main/script/deploy/Deploy.V2.s.sol#L93-L96 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L67-L91 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L156-L169 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L230-L286
Users can add collateral vaults to both vaults[id]
and vaultsKerosene[id]
. As a result, their collateral is calculated twice, leading to incorrect actions within the system.
As observed in lines L64, L65, L93
, and L94
of Deploy.V2.s.sol
, collateral vaults are added to both kerosineManager
and vaultLicenser
.
64 kerosineManager.add(address(ethVault)); 65 kerosineManager.add(address(wstEth)); [...] 93 vaultLicenser.add(address(ethVault)); 94 vaultLicenser.add(address(wstEth)); 95 vaultLicenser.add(address(unboundedKerosineVault)); // vaultLicenser.add(address(boundedKerosineVault));
Consequently, users can add collateral vaults to both vaults[id]
and vaultsKerosene[id]
, resulting in their collaterals being calculated twice as large, leading to incorrect system operations.
Let's walk through this scenario:
ethVault
, wstEth
to both vaults[id]
and vaultsKerosene[id]
. The restrictions at L75
and L78
do not prevent Alice from doing this because ethVault
and wstEth
have already been added to keroseneManager
and vaultLicenser
.function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); 75 if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); if (!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(); 78 if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed(); if (!vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
ETH
into ethVault
and $1000 worth of WSTETH
into the vault wstEth
.VaultManagerV2::mintDyad
with the second parameter amount
set to $2000 and receives $2000 dyad.function mintDyad( uint id, uint amount, address to ) external isDNftOwner(id) { uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount; if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); dyad.mint(id, to, amount); 167 if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); emit MintDyad(id, amount, to); }
VaultManagerV2::mintDyad
should revert the transaction because the collateral ratio is calculated as (1000 + 1000) / 2000 = 1
, which is less than MIN_COLLATERIZATION_RATIO = 1.5
. However, the value of collatRatio(id)
at L167
in VaultManagerV2::mintDyad
is calculated as 2, results in a success transaction.
This discrepancy occurs because:
VaultManagerV2::collatRatio
is calculated using getTotalUsdValue(id)
.function collatRatio( uint id ) public view returns (uint) { uint _dyad = dyad.mintedDyad(address(this), id); if (_dyad == 0) return type(uint).max; 238 return getTotalUsdValue(id).divWadDown(_dyad); }
getTotalUsdValue(id)
is determined by getNonKeroseneValue(id) + getKeroseneValue(id)
.function getTotalUsdValue( uint id ) public view returns (uint) { 247 return getNonKeroseneValue(id) + getKeroseneValue(id); }
getNonKeroseneValue(id)
should be $2000 and getKeroseneValue(id)
should be $0, getKeroseneValue(id)
is calculated as $2000 instead of $0 because ethVault
and wstEth
are already in vaultsKerosene[id]
.function getKeroseneValue( uint id ) public view returns (uint) { uint totalUsdValue; 276 uint numberOfVaults = vaultsKerosene[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaultsKerosene[id].at(i)); uint usdValue; if (keroseneManager.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
As a result, Alice's collateral ratio is calculated as 2 instead of 1, and she will not be liquidated even if her actual collateral is insufficient for liquidation.
Manual review
In Deploy.V2.s.sol
, collateral vaults should be added only into vaultLicenser
, not into kerosineManager
. And unboundedKerosineVault
should be added into kerosineManager
, not into vaultLicenser
.
- 64: kerosineManager.add(address(ethVault)); - 65: kerosineManager.add(address(wstEth)); + 64: kerosineManager.add(address(unboundedKerosineVault)); [...] 93: vaultLicenser.add(address(ethVault)); 94: vaultLicenser.add(address(wstEth)); - 95: vaultLicenser.add(address(unboundedKerosineVault));
Above recommendation is not perfect, because Vault.kerosine.unbounded::assetPrice
is based on kerosineManager.getVaults()
. So, Vault.kerosine.unbounded::assetPrice
should be improved as well.
Other
#0 - c4-pre-sort
2024-04-28T06:48:20Z
JustDravee marked the issue as duplicate of #105
#1 - c4-pre-sort
2024-04-29T09:06:29Z
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:14:06Z
koolexcrypto removed the grade
#4 - c4-judge
2024-05-28T15:14:15Z
koolexcrypto marked the issue as not a duplicate
#5 - c4-judge
2024-05-28T15:14:22Z
koolexcrypto marked the issue as duplicate of #1133
#6 - c4-judge
2024-05-28T15:14:26Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: MrPotatoMagic
Also found by: 0x175, 0x486776, 0x77, 0xAkira, 0xAsen, 0xDemon, 0xabhay, 0xblack_bird, 0xlemon, 0xloscar01, 0xtankr, 3docSec, 4rdiii, Abdessamed, AlexCzm, Angry_Mustache_Man, BiasedMerc, Circolors, Cryptor, DMoore, DPS, DedOhWale, Dinesh11G, Dots, GalloDaSballo, Giorgio, Honour, Imp, Jorgect, Krace, KupiaSec, Mrxstrange, NentoR, Pechenite, PoeAudits, Ryonen, SBSecurity, Sabit, T1MOH, TheFabled, TheSavageTeddy, Tychai0s, VAD37, Vasquez, WildSniper, ZanyBonzy, adam-idarrha, alix40, asui, blutorque, btk, c0pp3rscr3w3r, caglankaan, carrotsmuggler, d_tony7470, dimulski, dinkras, djxploit, falconhoof, forgebyola, grearlake, imare, itsabinashb, josephdara, kartik_giri_47538, ke1caM, kennedy1030, koo, lionking927, ljj, niser93, pep7siup, poslednaya, ptsanev, sashik_eth, shaflow2, steadyman, turvy_fuzz, ubl4nk, valentin_s2304, web3km, xyz, y4y, zhaojohnson, zigtur
0.0234 USDC - $0.02
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
An attacker can undo any legitimate withdrawals and redemption.
In VaultManagerV2::withdraw
, there is a validation for idToBlockOfLastDeposit[id]
at L143
.
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { 143 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(); }
If an attacker sets idToBlockOfLastDeposit[id]
to the current block number through front-running, then the owner of id
will be unable to withdraw his assets.
Let's consider the following scenario:
id
, initiates a transaction to call VaultManagerV2::withdraw
.VaultManagerV2::deposit
with the 3rd parameter amount
set to 1 wei
, using front-running.function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { 127 idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
As indicated at L127
of VaultManagerV2::deposit
, Bob sets idToBlockOfLastDeposit[id]
to the current block number. Consequently, Alice's transaction is reverted at L143
of VaultManagerV2::withdraw
.
Redemption is also impossible since the function VaultManagerV2::redeemDyad
calls the function VaultManagerV2::withdraw
.
Manual review
There are 2 ways to fix this problem.
id
can deposit into id
.function deposit( uint id, address vault, uint amount ) external isValidDNft(id) + isDNftOwner(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { - idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
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(); }
DoS
#0 - c4-pre-sort
2024-04-27T11:57:33Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:26:33Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:42:46Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:43:20Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T20:45:46Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-05-05T21:57:09Z
koolexcrypto marked the issue as nullified
#6 - c4-judge
2024-05-05T21:57:14Z
koolexcrypto marked the issue as not nullified
#7 - c4-judge
2024-05-08T15:26:27Z
koolexcrypto marked the issue as duplicate of #1001
#8 - c4-judge
2024-05-11T19:48:27Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Circolors
Also found by: 0x175, 0x486776, 0xAlix2, 0xSecuri, 0xShitgem, 0xfox, 0xlemon, 0xnilay, 3th, 4rdiii, Aamir, Al-Qa-qa, AlexCzm, Egis_Security, Evo, Honour, Infect3d, Josh4324, Limbooo, Mahmud, SBSecurity, TheSchnilch, ahmedaghadi, alix40, amaron, bbl4de, bhilare_, btk, carrotsmuggler, cinderblock, d3e4, dimulski, dinkras, ducanh2706, iamandreiski, itsabinashb, ke1caM, ljj, sashik_eth, shaflow2, steadyman, web3km, y4y
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
Users can't withdraw their kerosene.
In VaultManagerV2::withdraw
, there is a calculation of value
to be withdrawn at L146
.
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); 146 uint value = amount * _vault.assetPrice() * 1e18 148 / 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(); }
During the calculation, it calls _vault.oracle().decimals()
at L148
. However, if vault
is a kerosene vault, there is no oracle. Therefore, this calculation is reverted, resulting in the withdrawal of kerosene being reverted as well.
Manual review
Withdrawing kerosene never affects the value of getNonKeroseneValue(id)
, so the calculation of the value of kerosene to be withdrawn is unnecessary.
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() + uint value; + if(vaults[id].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(); }
Other
#0 - c4-pre-sort
2024-04-26T21:05:56Z
JustDravee marked the issue as duplicate of #1048
#1 - c4-pre-sort
2024-04-28T18:38:25Z
JustDravee marked the issue as duplicate of #830
#2 - c4-pre-sort
2024-04-29T08:45:55Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-11T20:05:59Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: shikhar229169
Also found by: 0x486776, 0xSecuri, 0xfox, 3th, Circolors, Honour, KupiaSec, Maroutis, Sancybars, Stormreckson, Strausses, ke1caM, kennedy1030
283.3687 USDC - $283.37
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205-L228
Anyone can manipulate the price of kerosene and keep their DNFT token healthy.
Anyone can mint his own DNFT token and deposit a large amount of collateral into it. Then, the total collateral value of the entire system becomes significantly larger, results in a large price of kerosene. Consequently, many DNFT tokens exit from liquidation. Even, for some DNFT tokens, although the collateral value falls below the dyad
amount minted via those DNFT tokens, it is not liquidated since it has expensive kerosenes. Then, if the situation improves, he can safely withdraw all his collateral in his new DNFT token since there is no dyad
minted via that id
.
Manual review
The liquidating condition should be improved.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { uint cr = collatRatio(id); - if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); + if (cr >= MIN_COLLATERIZATION_RATIO && getNonKeroseneValue(id) >= dyad.mintedDyad(address(this), id)) 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); }
Other
#0 - c4-pre-sort
2024-04-28T06:02:37Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:06:23Z
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:08Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-08T12:41:57Z
koolexcrypto marked the issue as not a duplicate
#5 - c4-judge
2024-05-08T12:42:10Z
koolexcrypto marked the issue as nullified
#6 - c4-judge
2024-05-08T12:42:14Z
koolexcrypto marked the issue as not nullified
#7 - c4-judge
2024-05-08T12:42:33Z
koolexcrypto marked the issue as duplicate of #338
#8 - c4-judge
2024-05-11T12:20:39Z
koolexcrypto marked the issue as satisfactory
#9 - c4-judge
2024-05-13T18:42:36Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: 0xAlix2
Also found by: 0x486776, 0xabhay, 0xlucky, 0xtankr, Abdessamed, Circolors, CodeWasp, DarkTower, Egis_Security, Giorgio, Infect3d, Krace, KupiaSec, Limbooo, Maroutis, NentoR, Ryonen, SpicyMeatball, T1MOH, TheFabled, TheSavageTeddy, TheSchnilch, VAD37, XDZIBECX, btk, carrotsmuggler, cu5t0mpeo, dimulski, gumgumzum, iamandreiski, imare, itsabinashb, ke1caM, kennedy1030, lian886, n4nika, oakcobalt, sashik_eth, shaflow2, steadyman, web3km, windhustler, zhaojohnson
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L50-L68
The price of Kerosence
is calculated significantly smaller than it should be. Consequently, this decreases the collateral ratios, leading to the unfair liquidation of many legitimate id
s.
In Vault.kerosine.unbounded::assetPrice
, the calculation of numerator
is incorrect at L65
.
function assetPrice() public view override returns (uint) { uint tvl; address[] memory vaults = kerosineManager.getVaults(); uint numberOfVaults = vaults.length; for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[i]); tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals()); } 65 uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
In fact, in the calculation of numerator
at L65
, tvl
should be subtracted by the total amount of dyad
minted through VaultManagerV2
. As a result, the return value of Vault.kerosine.unbounded::assetPrice
is significantly smaller than it should be. This directly affects the value of VaultManagerV2::getKeroseneValue
, causing the VaultManagerV2::collatRatio
to return a much smaller value, which can unfairly lead to the liquidation of legitimate id
s.
Manual review
At L65
of Vault.kerosine.unbounded::assetPrice
, dyad.totalSupply()
should be replaced by the total amount of dyad
minted through VaultManagerV2
.
Other
#0 - c4-pre-sort
2024-04-28T18:14:30Z
JustDravee marked the issue as duplicate of #308
#1 - c4-pre-sort
2024-04-29T08:48:05Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:08:18Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: 0xAlix2
Also found by: 0x486776, 0xabhay, 0xlucky, 0xtankr, Abdessamed, Circolors, CodeWasp, DarkTower, Egis_Security, Giorgio, Infect3d, Krace, KupiaSec, Limbooo, Maroutis, NentoR, Ryonen, SpicyMeatball, T1MOH, TheFabled, TheSavageTeddy, TheSchnilch, VAD37, XDZIBECX, btk, carrotsmuggler, cu5t0mpeo, dimulski, gumgumzum, iamandreiski, imare, itsabinashb, ke1caM, kennedy1030, lian886, n4nika, oakcobalt, sashik_eth, shaflow2, steadyman, web3km, windhustler, zhaojohnson
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L50-L68
If the total collateral value falls below the total dyad
amount, liquidation is not possible.
If the total collateral value is smaller than total dyad
amount, Vault.kerosine.unbounded::assetPrice
will be reverted at L65
.
function assetPrice() public view override returns (uint) { uint tvl; address[] memory vaults = kerosineManager.getVaults(); uint numberOfVaults = vaults.length; for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[i]); tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals()); } 65 uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
This directly affects Vault.kerosine::getUsdValue
, causing it to be reverted. Subsequently VaultManagerV2::collatRatio
is also reverted, which leads to the impossibility of liquidation since VaultManagerV2::liquidate
calls VaultManagerV2::collatRatio
.
Manual review
If the total collateral value is smaller than total dyad
amount, Vault.kerosine.unbounded::assetPrice
should return 0.
function assetPrice() public view override returns (uint) { uint tvl; address[] memory vaults = kerosineManager.getVaults(); uint numberOfVaults = vaults.length; for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[i]); tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals()); } + if(tvl < dyad.totalSupply()) return 0; uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
Other
#0 - c4-pre-sort
2024-04-28T18:44:52Z
JustDravee marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-04-28T18:44:55Z
JustDravee marked the issue as primary issue
#2 - shafu0x
2024-04-30T15:11:48Z
So we would basically make kerosene worthless if (tvl < dyad.totalSupply())
#3 - c4-judge
2024-05-04T21:21:00Z
koolexcrypto marked the issue as satisfactory
#4 - c4-judge
2024-05-08T08:32:03Z
koolexcrypto marked the issue as duplicate of #308
#5 - k4zanmalay
2024-05-15T20:41:05Z
I wonder why this issue and it's duplicates (#859, #722, #969, etc) were merged with #308? It describes a scenario where the total value locked (TVL) drops in value compared to the DYAD token's total supply due to price movements. The suggested solution in #308 will not resolve this issue because it only addresses the underflow caused by the existing total supply of DYAD and does not protect against price movements.
#6 - koolexcrypto
2024-05-21T12:01:48Z
Hi @k4zanmalay
Thank you for your input.
The recommendation doesn't change the fact that, the root cause is the same.
The suggestion in this issue could cause a problem for already existing total supply of DYAD issue.
CC: @shafu0x
#7 - shafu0x
2024-05-21T15:15:56Z
@koolexcrypto not sure I understand what you mean
🌟 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#L205-L228
The liquidator could incur loss during liquidation.
As you can see at L221
of VaultManagerV2::liquidate
, only collateral in vaults
is moved to to
, not kerosenes in vaultsKerosene
.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { uint cr = collatRatio(id); if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); 215 dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id)); uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); 219 uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr); 221 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); }
Let's follow this scenario:
id
, has $2500
of collateral, $500
of kerosene, and $2400
of dyad
. Her collateral ratio is (2500 + 500) / 2400 = 1.25
, indicating she is facing liquidation.VaultManagerV2::liquidate
.$2400
dyad
at L215
.L219
, liquidationAssetShare
is calculated as ((1.25 - 1) * 0.2 + 1) / 1.25 = 0.84
.84%
of Alice's collateral is then moved by looping through Alice's vaults
(L221
), not vaultsKerosene
.Therefore, Bob incurs a loss of $2400 - $2500 * 0.84 = $300
.
Manual review
Liquidation should move Kerosenes as well.
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); } + numberOfVaults = vaultsKerosene[id].length(); + for (uint i = 0; i < numberOfVaults; i++) { + Vault vault = Vault(vaultsKerosene[id].at(i)); + uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); + vault.move(id, to, collateral); + } emit Liquidate(id, msg.sender, to); }
Other
#0 - c4-pre-sort
2024-04-28T10:26:19Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:03:47Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:42:46Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Infect3d
Also found by: 0x486776, 0xAlix2, 0xleadwizard, 0xnilay, Abdessamed, ArmedGoose, Bauchibred, Bigsam, GalloDaSballo, HChang26, Myrault, OMEN, SBSecurity, T1MOH, ZanyBonzy, alix40, atoko, iamandreiski, jesjupyter, ke1caM, miaowu, peanuts, vahdrak1
17.2908 USDC - $17.29
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205-L228
If the collateral ratio of id
is smaller than 100%, nobody wants to liquidate it. As a result, the entire protocol doesn't work well.
If cr < 1e18
in VaultManagerV2::liquidate
, liquidationEquityShare
will be 0 at L218
.
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)); 217 uint cappedCr = cr < 1e18 ? 1e18 : cr; 218 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); }
So, the liquidator will incur loss. As a result, nobody wants to liquidate this id
. Then, from that id
, redeeming or withdrawing can't be done.
Manual review
There should be a mechanism to process id
s whose collateral ratio is smaller than 100%.
Other
#0 - c4-pre-sort
2024-04-28T11:07:59Z
JustDravee marked the issue as duplicate of #977
#1 - c4-pre-sort
2024-04-29T09:24:17Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:44:04Z
koolexcrypto changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-05-12T09:23:58Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-28T16:20:19Z
This previously downgraded issue has been upgraded by koolexcrypto
#5 - c4-judge
2024-05-28T16:21:52Z
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#L94-L104 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.sol#L42-L51
An attacker can reverse any vault removal, causing the owner of id
to be unable to remove unnecessary vaults.
In VaultManagerV2::remove
, there is a validation for Vault(vault).id2asset(id)
at L101
.
function remove( uint id, address vault ) external isDNftOwner(id) { 101 if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets(); if (!vaults[id].remove(vault)) revert VaultNotAdded(); emit Removed(id, vault); }
If an attacker sets Vault(vault).id2asset(id)
to a nonzero value through front-running, then the owner of id
will be unable to remove the vault.
Let's consider the following scenario:
id
, initiates a transaction to call VaultManagerV2::remove
.VaultManagerV2::deposit
with the 3rd parameter amount
set to 1 wei
, using front-running.function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); 130 _vault.deposit(id, amount); }
As indicated at L130
of VaultManagerV2::deposit
, it triggers Vault::deposit(id, 1)
.
function deposit( uint id, uint amount ) external onlyVaultManager { 49 id2asset[id] += amount; emit Deposit(id, amount); }
Then, at L49
of Vault::deposit
, id2asset[id]
is set to 1. This results in Alices's transaction being reverted at L101
of VaultManagerV2::remove
.
Manual review
VaultManagerV2::deposit
should only be callable by the owner of id
.
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) + isDNftOwner(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
Other
#0 - c4-pre-sort
2024-04-27T13:33:58Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:26:37Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:42:46Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:43:20Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T20:45:38Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-05-05T21:56:52Z
koolexcrypto marked the issue as nullified
#6 - c4-judge
2024-05-05T21:56:56Z
koolexcrypto marked the issue as not nullified
#7 - c4-judge
2024-05-05T21:57:01Z
koolexcrypto marked the issue as not a duplicate
#8 - c4-judge
2024-05-06T08:51:14Z
koolexcrypto marked the issue as duplicate of #118
#9 - c4-judge
2024-05-11T12:23:34Z
koolexcrypto marked the issue as satisfactory