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: 64/183
Findings: 4
Award: $123.13
π 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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L156-L169 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L50 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.sol#L60
The protocol's vulnerability allows for the minting of dyad tokens using only kerosene
as collateral
, which contravenes the intended design that prohibits using kerosene alone for this purpose. Additionally, this flaw enables users to bypass the critical 150%
collateralization ratio requirement, threatening the protocol's financial stability. Exploitation of this bug could lead to the issuance of dyad tokens that are not sufficiently backed by collateral, potentially causing a devaluation of the dyad currency and jeopardizing the protocol's solvency.
The deploymentscript adds the unboundedKerosineVault
to the vaultLicenser
, which could be problematic because the intention is that users should not be able to mint dyad solely with kerosene
as collateral
a user adds the unboundedKerosineVault
using VaultManagerV2::add
and inside there is check
function add( uint id, address vault ) external isDNftOwner(id) { ///...... // @audit this check will passed for the unbounded vault because of the deployment script if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); //... }
and the check is passed now the user deposits 1 million kerosene using the VaultManagerV2::deposit
function. As of now when i am writing this report the cost of 1 KEROSENE = 0.03456 USDC
($0.0317) 1m kerosene = 34560.3 usdc. also this can be huge if the user is taking a flash loan to exploit.
User calls mintDyad with 36500
dyad as the amount and with their address
now inside the mintdyad
function it calls the getNonKeroseneValue()
function getNonKeroseneValue( uint id ) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaults[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint usdValue; if (vaultLicenser.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
since there is only one vault which is the unboundedkerosine vault
so it will call the getUsdValue inside this function it will call the Vault.kerosine.unbounded::assetprice()
:
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()); } uint numerator = tvl - dyad.totalSupply(); // 1000000000e18 - 950841953.47e18 = 49158046.53e18 uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; } }
KerosineManager has only two vault, weth & wstETH. so we will only calculating the assetprice()
of weth ans wstETH
now lets take an example , suppose
tvl of weth = 500 weth, tvl of wstETH = 500 stEth dyad totalsupply (as of the time of writing this report) = 632967400000000000000000 (in 18 decimal)
return value of assetPrice() for Weth
= 318542608000
return value of assetPrice() for wstETH
= 368088673998
now calculating the tvl using the Tvl formula
numetor = 3433156409990000000000000 - 632967400000000000000000
= 2800189009990000000000000
denominator = 1000000000e18 - 950841953.47e18 = 49158046.53e18
return = 2800189009990000000000000 * 1e8 / 49158046.53e18
= 5696298
(return value of assetPrice()
inside unbounded kerosine vault contract)
now, calculating the getUsdValue for Unbounded Vault
,
since there is a only one vault added by the user the getNonKeroseneValue
will only run for one vault
,so the totalUsdValue = 56962.98e18
.
now back to mintdyad
the check will pass because the newDyadminted
is 0 + 37000e18 (attacker is minting for the first time). and the getNonKeroseneValue(id)
is 56962.98e18
now the user minted the 36500
dyad,
dyad.mint(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); emit MintDyad(id, amount, to);
after minting dyad it will call, the collatRatio(id) inside this function it will return the getTotalUsdValue because the user _dyad
is non zero it is 37000e18
now inside the getTotalUsdValue
it return
function getTotalUsdValue( uint id ) public view returns (uint) { return getNonKeroseneValue(id) + getKeroseneValue(id); }
so, the getnonkerosene
is 56962.98e18
and getKeroseneValue
is zero
becuase user didnt add any exogenous collateral.
so the return value is 56962.98e18
returned to collatration for the ratio calculation = 56962.98e18.divwadDown(36500e18)
= 1.5606e18
(18 decimal)
now the check in mintDyad
:This check will pass, allowing the user to successfully mint dyad. Since the collatRatio returns 1.5606
is greater than the MIN_COLLATERIZATION_RATIO
, the check will pass, enabling the user to mint dyad using only kerosene tokens.
dyad.mint(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); emit MintDyad(id, amount, to);
Due to the addition of the unboundedKerosineVault
to the vaultLicenser
, an attacker can mint dyad tokens using only kerosene
as collateral.
This attack is profitable as 1 million kerosene costs $34,560.3
, and the user minted 36,500
dyad, with dyad being pegged 1:1 to stablecoins such as USDC, USDT, or DAI. Therefore, the profit from the attack would be $36,500 - $34,560.3 = $1,939.7
in stablecoin. The profitability could be significantly amplified if the exploiter utilizes a flash loan to scale the attack.
Manual Review
Modify the VaultManagerV2::add
function to include additional checks that prevent unbounded vaults from being used as collateral sources for minting dyad.
Context
#0 - c4-pre-sort
2024-04-28T19:57:05Z
JustDravee marked the issue as duplicate of #70
#1 - c4-pre-sort
2024-04-29T09:37:43Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:00:24Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-12T11:07:54Z
koolexcrypto marked the issue as not a duplicate
#4 - c4-judge
2024-05-12T11:07:58Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - 0xAbhay
2024-05-16T04:07:34Z
@koolexcrypto The identified vulnerability allows an attacker to exploit the minting mechanism of the protocol by bypassing the intended collateralization constraints. This is due to an error in the deployment script, which incorrectly licenses the unboundedKerosineVault, enabling users to use this vault as valid collateral despite it not being intended as such. An attacker can then add this vault, deposit kerosene, and mint dyad tokens solely with kerosene tokens. The flawed script allows the vault to pass the collateral check, leading to the successful minting of dyad tokens with insufficient collateral, undermining the financial stability of the protocol. This exploit can be further amplified using flash loans, significantly increasing the attacker's profit.
#6 - 0xAbhay
2024-05-17T04:01:55Z
@koolexcrypto The main invariant is that the user cannot mint dyad tokens using only kerosene. If they are trying to mint using solely kerosene, then there is a problem. #679 #1098 and #872 has similar issue please relook in to it
Thank you π
#7 - koolexcrypto
2024-05-22T10:16:39Z
Hi @0xAbhay
Thank you for your feedback.
The issue seems to be in the condition if (vaultLicenser.isLicensed(address(vault))) {
in getNonKeroseneValue
function, as it should ensure that only non-kerosene vaults only are used. Probably using keroseneManager.isLicensed
instead.
Could you please provide a PoC (coded) that demonstrates the issue above?
#8 - shafu0x
2024-05-22T13:35:37Z
Hi @0xAbhay
Thank you for your feedback.
The issue seems to be in the condition
if (vaultLicenser.isLicensed(address(vault))) {
ingetNonKeroseneValue
function, as it should ensure that only non-kerosene vaults only are used. Probably usingkeroseneManager.isLicensed
instead.Could you please provide a PoC (coded) that demonstrates the issue above?
exactly. this is the problem here.
#9 - 0xAbhay
2024-05-22T16:51:36Z
@koolexcrypto
Sure, let's break down the finding and the potential exploit step-by-step, focusing on how the minting of dyad tokens using only kerosene as collateral happens and why it is problematic for the protocol. We'll use the provided example to illustrate this process.
Deployment Script Issue:
Adding the Vault:
unboundedKerosineVault
using the VaultManagerV2::add
function.if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed();
unboundedKerosineVault
.Depositing Kerosene:
unboundedKerosineVault
using the VaultManagerV2::deposit
function.Minting Dyad Tokens:
mintDyad
function to mint 36,500 dyad tokens to their address.mintDyad
function, the getNonKeroseneValue()
function is called to calculate the collateral value excluding kerosene:
function getNonKeroseneValue(uint id) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaults[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint usdValue; if (vaultLicenser.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
Calculating the Asset Price:
getUsdValue
function for the unboundedKerosineVault
calculates the total value of the assets. This involves a detailed calculation using the assetPrice()
function:
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()); } uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
Example Calculation: Total Value Locked (TVL) in WETH and wstETH vaults is calculated:
Total TVL:
Adjusted numerator and denominator for asset price calculation:
Asset price calculation:
USD Value Calculation for Unbounded Vault:
assetPrice
:
Collateral Ratio Check:
mintDyad
function checks the collateralization ratio after minting:
This vulnerability arises due to the improper licensing of the unboundedKerosineVault
, allowing users to use kerosene alone as collateral. This bypasses the intended 150% collateralization ratio requirement, enabling the minting of undervalued dyad tokens and threatening the protocol's financial stability.
This is not a duplicate #1029. and for coded poc -> please refers to #679
#10 - 0xAbhay
2024-05-22T16:54:46Z
@koolexcrypto Also, why would a kerosene vault be added using the add function, which is used to add exogenous collateral? This finding shows how, due to a development script error, we can add unbounded kerosene using the add function and mint the DYAD using only kerosene thank you for all the hard work you have put in i appreciate your work and #832 is a similar finding and also has a good poc please check.
#11 - c4-judge
2024-05-28T14:47:44Z
koolexcrypto removed the grade
#12 - c4-judge
2024-05-28T14:47:55Z
koolexcrypto marked the issue as duplicate of #1133
#13 - c4-judge
2024-05-28T14:47:59Z
koolexcrypto marked the issue as satisfactory
π 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/cd48c684a58158de444b24854ffd8f07d046c31b/script/deploy/Deploy.V2.s.sol#L64-L65 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/script/deploy/Deploy.V2.s.sol#L93-L94 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L67 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L80 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L156-L169
The duplication bug that allows the same exogenous vault
to be counted in both getNonKeroseneValue
and getKeroseneValue
could lead to a significantly inflated collateral ratio, enabling users to mint more DYAD
than the actual collateral should permit. potentially resulting in under-collateralization and jeopardizing user funds.
If you look at the deployment script, the team has added the exogenous vault to the Kerosene Manager and also into the Vault License. Thus, the user can add the same exogenous vault using add and addKerosene.
<details> <summary>Case 1: Using inflated collateral to mint Dyad</summary>as per docs provide $1500
worth of wETH
or wstETH
, mint $1000
worth of Dyad stablecoins
.
here how the user can mint more than 150% of their deposit collateral
Deposit: User deposits 10 WETH
into a vault, with WETH priced at $3000
each, totaling $30000
worth of WETH
.
Mint DYAD: User attempts to mint 29998 DYAD
, which is just under the USD value of the deposited WETH
.
The mintDyad
function calls getNonKeroseneValue
to ensure the vault's USD value is sufficient to cover the new DYAD minting.
function getNonKeroseneValue( uint id ) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaults[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint usdValue; // @audit-> this check will pass for exogenous vaults becuase of the deployment script if (vaultLicenser.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
Calculate USD Value: getUsdValue calculates the USD value of the WETH in the vault:
For instance, if the asset price of WETH
in terms of USD is $3000
, the assetPrice
function would return 3000e8
, because the ETH/USD
price is conventionally expressed with 8 decimal places.
Verify Collateralization: The contract checks whether the vault's USD value exceeds the amount of DYAD requested for minting. This check passes because getKeroseneValue
returns 30000e18
, which is greater than the newDyadMinted amount of 29998e18
.
Minting Allowed: Since the vault value is sufficient, DYAD is minted.
Post-minting, the collatRatio is assessed:
getTotalUsdValue aggregates the values from getNonKeroseneValue
and getKeroseneValue
. If the same WETH vault
is referenced in both cases due to the user adding the same vault to both kerosene
and non-kerosene
sets, the total value would be 60000e18
. This duplication could lead to an inflated collateral ratio calculation.
Calculate Collateral Ratio: The collatRatio function divides the total USD value by the minted DYAD amount:
60000e18 / 29998e18 β 2.00013334e18
Collateral Ratio Check: The minted DYAD
amount is checked against the minimum collateralization ratio (MIN_COLLATERIZATION_RATIO = 1.5e18). Since 2.00013334e18 > 1.5e18
, the minting process is successful.
The user successfully mints 29998
DYAD with a collateral ratio above the required minimum, effectively allowing nearly 1:1
minting of DYAD to the USD value of the collateral.
This process relies on the assumption that both getNonKeroseneValue
and getKeroseneValue
reference the same Vault, which may not be the intended design. The system is designed to prevent under-collateralization, but if the same Vault is counted twice
, it could lead to overestimation of collateral and potential system risk.
A user deposits 10 WETH
into a vault
.
The current price of WETH is $3000
, so the total collateral value is $30,000
.
The user mints DYAD worth $20,000
, expecting to maintain a collateral ratio above the minimum of 150%.
Due to the bug, the same vault is counted in both getNonKeroseneValue
and getKeroseneValue
, doubling the perceived collateral value to $60,000
.
The user now sees a collateral ratio of 300% ($60,000 / $20,000) instead of the actual 150% ($30,000 / $20,000).
The user decides to withdraw assets based on the inflated collateral ratio.
They request a withdrawal
equivalent to $10,000
, expecting the system to maintain the minimum collateral ratio after the withdrawal.
However, because the actual collateral is only $30,000
, after withdrawing $10,000
worth of WETH, only $20,000
worth of WETH remains to back the $20,000 of minted DYAD.
The actual collateral ratio post-withdrawal is now 100% ($20,000 / $20,000), which is below the required 150%
.
This leaves the system under-collateralized and vulnerable to price fluctuations that could further diminish the collateral's value.
</details>Manual Review
Implement a check in add
and addKerosene
functions to verify that a vault is not already present in the other set before adding.
For the add
function:
function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); ++ if (vaultsKerosene[id].contains(vault)) revert VaultAlreadyInKerosene(); if (!vaults[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
For the addKerosene
function:
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 (vaults[id].contains(vault)) revert VaultAlreadyInNonKerosene(); if (!vaultsKerosene[id].add(vault)) revert VaultAlreadyAdded(); emit Added(id, vault); }
Context
#0 - c4-pre-sort
2024-04-28T07:01:57Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:29Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:23Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - 0xAbhay
2024-05-16T04:34:57Z
@koolexcrypto The vulnerability stems from a duplication bug that allows the same exogenous vault to be counted twice, both in calculations for non-Kerosene collateral (getNonKeroseneValue
) and Kerosene collateral (getKeroseneValue
). This oversight could lead to an inflated perception of the collateral ratio, enabling users to mint more DYAD stablecoins than their actual collateral value permits. Consequently, users could exploit this bug to effectively bypass the system's intended safeguards against under-collateralization. This occurs when users deposit assets into a vault, with the same vault mistakenly counted twice in collateral valuation calculations. As a result, the system erroneously assesses the collateral value, potentially allowing users to mint more DYAD than they should, posing risks of insolvency and financial loss.
#4 - koolexcrypto
2024-05-22T10:42:39Z
Hi @0xAbhay
Thank you for your input.
Could you please provide a PoC (coded) that demonstrates the issue above?
#5 - 0xAbhay
2024-05-22T17:17:21Z
@koolexcrypto Summary of the Duplication Bug in the DYAD Minting Process Issue Description
A duplication bug in the DYAD minting process allows the same exogenous vault to be counted in both getNonKeroseneValue
and getKeroseneValue
. This bug results in a significantly inflated collateral ratio, enabling users to mint more DYAD than the actual collateral should permit. This could lead to under-collateralization and jeopardize user funds.
Proof of Concept Adding Vaults:
The deployment script adds the exogenous vault to both the Kerosene Manager and the Vault License.
Users can add the same exogenous vault using both add and addKerosene. Minting DYAD with Inflated Collateral
:
A user deposits 10 WETH (worth $30,000) into a vault. The user mints 29,998 DYAD, just under the USD value of the deposited WETH. The mintDyad function calls getNonKeroseneValue to check the vault's USD value. getNonKeroseneValue calculates the USD value, which includes the exogenous vault.
Collateral Value Calculation:
The USD value of the WETH in the vault is correctly calculated based on the asset price (e.g., $3000 per WETH). The getTotalUsdValue function aggregates values from getNonKeroseneValue and getKeroseneValue, potentially doubling the perceived value if the same vault is counted in both.
Inflated Collateral Ratio:
If the vault value is duplicated, the perceived total value is $60,000 instead of $30,000. The collateral ratio appears as 200% instead of 150%.
Impact on Collateralization:
The inflated collateral ratio allows users to mint DYAD at almost a 1:1 ratio to the USD value of their collateral. Post-minting, the collateral ratio is incorrectly calculated, making it seem higher than it actually is.
Minting More DYAD than Permitted:
A user deposits $30,000 worth of WETH and mints 29,998 DYAD. Due to the duplication bug, the system thinks the collateral is worth $60,000. This falsely high collateral value allows the user to pass the collateralization check.
Excessive Withdrawal:
A user deposits $30,000 worth of WETH and mints $20,000 worth of DYAD, expecting a collateral ratio above 150%. The system erroneously calculates the collateral as $60,000, showing a 300% ratio. The user withdraws $10,000, reducing the actual collateral to $20,000. The true collateral ratio drops to 100%, below the required 150%, leaving the system under-collateralized.
Conclusion The duplication bug in the minting and collateral calculation process can lead to severe under-collateralization. Users can exploit this by minting more DYAD than their actual collateral should allow or by withdrawing excessive amounts, putting the system at significant risk. Fixing this bug is crucial to maintaining the integrity and security of the DYAD stablecoin system.
</details>#6 - 0xAbhay
2024-05-22T17:21:47Z
@koolexcrypto Sorry, I won't be able to provide the coded POC because I am on vacation. Additionally, this seems similar to issue #966. Thank you for your hard work. I hope you understand my situation.
This is my greatest attempt at the POC, as it was very difficult to write it on my phone.
#7 - 0xAbhay
2024-05-29T01:40:06Z
@koolexcrypto hey sir this is still marked as unsatisfactory please check whenever you are free thank you.
#8 - c4-judge
2024-05-29T09:07:59Z
koolexcrypto removed the grade
#9 - c4-judge
2024-05-29T09:08:33Z
koolexcrypto marked the issue as satisfactory
#10 - c4-judge
2024-05-29T11:20:15Z
koolexcrypto marked the issue as duplicate of #1133
π 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/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/49fb7174576e5147dc73d3222d16954aa641a2e0/src/core/VaultManagerV2.sol#L134-L153
The exploitation of the block.number
dependency bug can lead to a denial of service where legitimate users are unable to withdraw
their funds. This undermines the trust in the system and can lead to financial losses and reputational damage for the platform.
The VaultManagerV2::deposit
lacks a check to ensure that the caller is the DNft holder. This omission could potentially result in a serious problem.
This can be manupilated by miner
or non-miner
lets go through the one by one example
Here's how a miner could execute this attack:
Monitor Mempool: The miner monitors the mempool for withdraw
transactions related to a specific id.
Prepare Deposit: Upon detecting a withdraw
transaction, the miner prepares a deposit
transaction for the same id.
// @audit no check anyone can deposit for a valid id even if they are not the owner of DNft 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); }
The miner can use any account to perform this deposit, including their own.
Construct Block: When constructing the next block, the miner includes their own deposit transaction before the user's withdraw transaction.
Mine Block: The miner mines the block, ensuring that their deposit
transaction is included first.
Publish Block: The miner publishes the block to the blockchain.
Withdrawal Fails: When the block is confirmed, the withdraw
transaction is processed after the deposit transaction. The withdraw transaction fails because the check in VaultManagerV2::withdraw
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); // ... // .. }
now matches the current block number, triggering the DepositedInSameBlock
revert condition.
Here's a step-by-step explanation of how a non-miner might carry out this attack:
Monitor Mempool: The attacker monitors the mempool for withdraw
transactions related to a specific id using a Web3 provider or blockchain explorer API.
Craft Deposit Transaction: Upon detecting a withdraw
transaction, the attacker quickly creates a deposit
transaction for the same id
.
Increase Gas Price: The attacker sets a higher gas price for their deposit
transaction to incentivize miners to prioritize and include it in the next block before the pending withdraw transaction.
Broadcast Deposit Transaction: The attacker broadcasts their deposit
transaction to the network.
Miners Include Deposit First: Miners, motivated by the higher gas fees, include the attacker's deposit
transaction in the next block before the withdraw
transaction.
Withdrawal Fails: When the withdraw
transaction is processed, it fails because the check in VaultManagerV2::withdraw
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); // ... // .. }
matches the current block number due to the inclusion of the attacker's deposit
transaction, triggering the DepositedInSameBlock
revert condition.
Repeat: The miner
and non-miner
can continue this strategy for subsequent blocks, effectively preventing the user from withdrawing by ensuring that a deposit transaction for the same id always precedes any withdraw transaction in the block order
Manual Review
allow deposits only from the owner of the DNft, ensure that the VaultManagerV2::deposit
function includes a check to verify the caller's ownership status.
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); }
DoS
#0 - c4-pre-sort
2024-04-27T11:56:24Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:26:15Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:39:59Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:45:36Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:52:18Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:52:22Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:26:39Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:49:04Z
koolexcrypto marked the issue as satisfactory
#8 - c4-judge
2024-05-13T18:34:30Z
koolexcrypto changed the severity to 3 (High Risk)
π Selected for report: Limbooo
Also found by: 0xabhay, 0xleadwizard, AM, ArmedGoose, Evo, HChang26, Infect3d, Jorgect, MiniGlome, SpicyMeatball, TheSchnilch, ahmedaghadi, favelanky, pontifex
119.0149 USDC - $119.01
The lack of proper ownership verification in the deposit
function could lead to significant security vulnerabilities. An attacker could exploit this oversight to deposit assets into unauthorized or malicious vaults
using any DNft ID
, regardless of ownership. This could result in the unauthorized use of the platform, manipulation of vault balances, and potential loss of user funds.
Identify or deploy a malicious vault contract that could, for example, redirect deposited assets to an attacker-controlled address.
Without owning a particular DNft
, call the deposit
function with its ID
and the address of the malicious vault, along with the amount to deposit.
Since there is no ownership check,
//@audit-> Anyone can deposit for any other ID, even if they do not hold the corresponding NFT // attacker can cause issue by creating a non lincensed vault and deposit with the valid Dnft function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); //@audit-> lacks the Licensed vault check _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
the transaction could succeed, allowing the attacker to deposit
assets into the malicious vault.
Manual Review
Modify the deposit function to include the isDNftOwner
modifier and Licensed
vaults check for both kerosene and non kerosene.
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) ++ isDNftOwner(id) { ++ if (!vaultLicenser.isLicensed(vault) && !keroseneManager.isLicensed(vault)) revert NotLicensed(); idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
Context
#0 - c4-pre-sort
2024-04-28T03:46:29Z
JustDravee marked the issue as duplicate of #463
#1 - c4-pre-sort
2024-04-29T08:50:20Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-03T21:57:41Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - 0xAbhay
2024-05-16T04:41:10Z
@koolexcrypto hey how this finding is duplicate of #463 this finding describes
The vulnerability in the deposit function of the VaultManagerV2 contract stems from the lack of proper ownership verification and the absence of a check to ensure that the vault is licensed. This flaw allows an attacker to deposit assets into any vault using any DNft ID, regardless of whether they own the DNft. The attacker could exploit this by creating or identifying a malicious vault contract that redirects deposited assets to an attacker-controlled address. By calling the deposit function with the ID of a valid DNft and the address of the malicious vault, the attacker can deposit assets without restriction. This loophole can lead to unauthorized use of the platform, manipulation of vault balances, and potential loss of user funds. To mitigate this, the deposit function should include checks for DNft ownership and verify that the vault is licensed by adding the isDNftOwner modifier and validating the vault's license status.
#4 - c4-judge
2024-05-22T10:46:18Z
koolexcrypto marked the issue as not a duplicate
#5 - koolexcrypto
2024-05-22T10:49:27Z
Hi @0xAbhay
Thank you for your feedback.
significant security vulnerabilities. to deposit assets into unauthorized or malicious vaults using any DNft ID This could result in the unauthorized use of the platform, manipulation of vault balances, and potential loss of user funds
Could you please explain what's the impact? Those are general statements. This could possibly be a dup of #1266 . But no sufficient proof is provided nor specific impact.
#6 - 0xAbhay
2024-05-22T17:43:05Z
Hey @koolexcrypto vulnerability in the deposit
function of the VaultManagerV2.sol
contract has significant security implications, primarily due to the lack of proper ownership verification for the DNft and the absence of a check to ensure that the vault is licensed. Here's a detailed explanation of the impact:
Exploitation by Attackers: An attacker can call the deposit function with any DNft ID, even if they do not own the DNft associated with that ID. This allows the attacker to manipulate the vaults without needing to hold the corresponding DNft.
Unauthorized Use of Platform: This vulnerability enables attackers to interact with the platform in ways that are not intended by its design, allowing them to deposit assets into vaults they shouldn't have access to.
Redirection of Funds: An attacker could create a malicious vault contract that redirects any deposited assets to an address under their control. By exploiting the lack of ownership checks, the attacker can use any valid DNft ID to deposit assets into this malicious vault.
Manipulation of Vault Balances: Without ownership verification and vault licensing checks, the platform's vault balances can be manipulated. This can disrupt the normal functioning and integrity of the platform.
Systemic Risk: The broader platform might face systemic risks due to such unauthorized and potentially fraudulent activities, impacting all users and stakeholders.
</details>#7 - 0xAbhay
2024-05-22T17:43:39Z
@koolexcrypto Thank you for judging this. It seems similar to #1266. In summary, anyone can deposit into any vault even if they don't hold the NFT, and even if the vault is not an exogenous or endogenous vault.
#8 - koolexcrypto
2024-05-29T09:12:27Z
Thank you.
dup of #930 with a partial credit due to the fact that, it doesn't describe a specific impact although it mentions the bug .
#9 - c4-judge
2024-05-29T09:12:31Z
koolexcrypto removed the grade
#10 - c4-judge
2024-05-29T09:12:57Z
koolexcrypto changed the severity to 3 (High Risk)
#11 - c4-judge
2024-05-29T09:13:48Z
koolexcrypto marked the issue as duplicate of #930
#12 - c4-judge
2024-05-29T09:13:52Z
koolexcrypto marked the issue as satisfactory
#13 - c4-judge
2024-05-29T09:13:57Z
koolexcrypto marked the issue as partial-50
π 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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L134-L153 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L65
The potential underflow in the Vault.kerosine.unbounded::assetPrice()
function can lead to a denial of service where users are unable to withdraw
their assets
from the Unbounded kerosine vault
. This can be exploited by an attacker to manipulate the TVL, potentially causing financial loss to users.This could lead to loss of confidence in the system and financial loss for users if they are unable to access their assets when needed.
In Deployment Script a new WETH
or wstETH
vault is created without any initial assets, resulting in a TVL of zero. Additionally, the deployment script grants an unbounded vault
to the vault licenser
, so the unbounded vault can be added to the VaultManagerV2
using the add
function. This also satisfies the check of !vaultLicenser.isLicensed(vault).
A user deposits assets into the Unbounded kerosine vault
.
The user attempts to withdraw, triggering the vault.assetPrice()
function
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { ///........ uint value = amount * _vault.assetPrice() * 1e18 / 10**_vault.oracle().decimals() // oracle decimal is 8 / 10**_vault.asset().decimals(); ///...... }
Since the user is withdrawing from the Unbounded Kerosene vault
, the assetPrice()
function specific to the Unbounded vault
is called. This function's calculation depends on the TVL of the exogenous vaults (e.g., WETH, wstETH), which can be influenced by external users.
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()); } //@audit-> underflow reverts when tvl < totalsupply uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
Since the TVL is zero and the dyad total supply is greater than zero, the subtraction in the assetPrice calculation results in an underflow. Even if a user deposits WETH
and wstETH
, for a withdrawal to succeed, it must be ensured that the TVL is greater than dyad.totalSupply()
. If the TVL is less than dyad.totalSupply()
after a user's deposit, the transaction will fail, and the user will not be able to withdraw
.
User Prepares Withdrawal: A user initiates a withdrawal from a unbounded vault
with a significant TVL in exogenous Vault
.
Attacker Monitors: An attacker, holding a large portion of the assets in theexogenous Vault
, monitors the network for pending withdrawal transactions.
Attacker Front-Runs: Upon detecting a user's withdrawal transaction, the attacker submits their own withdrawal from the exogenousVault. and submit a transaction with a higher gas fee to ensure it gets processed first.
Attacker Withdraws Assets: The attacker's withdrawal transaction is confirmed first due to the higher gas fee, significantly reducing the TVL in the vault. now TVL < dyad.totalSupply()
User's Withdrawal Transaction Processes: When the user's withdrawal transaction is processed, it triggers the Vault.assetPrice()
ex(unbounded vault) function.
TVL Falls Below Dyad Total Supply: The attacker's withdrawal may have caused the TVL to fall below the dyad total supply.
Underflow in Asset Price Calculation: The assetPrice function calculates a negative value due to underflow, as the TVL is now less than the dyad total supply.
Transaction Reverts: The smart contract reverts the user's withdrawal transaction because of the underflow
, preventing the user from retrieving their assets and effectively locking them in the unboundedVault
.
In both scenarios, the attacker exploits the underflow vulnerability in the assetPrice
function, which assumes that the TVL will always be greater than the dyad total supply. This vulnerability can lead to a denial of service for users trying to withdraw their assets.
here's a example of how the TVL
could be manipulated to equal dyad.totalSupply()
, causing the assetPrice
to return zero and triggering a failure in the withdrawal process due to the NotEnoughExoCollat check, we would follow these steps:
Initial State: Assume the contract has a certain tvl (total value locked) and dyad.totalSupply()
that are not equal. The assetPrice is therefore greater than zero.
Deposit/Withdraw Manipulation: An actor (could be a user or a group) starts interacting with the exogenous vaults that contribute to the tvl calculation. They could either deposit
more assets into the exogenous vaults or withdraw
assets from them.
Equalizing TVL and Dyad Supply: The actor continues to adjust their deposits
and withdrawals
until the tvl
exactly equals dyad.totalSupply()
. This could be done by monitoring the contract state off-chain and performing transactions accordingly.
TVL Equals Dyad Supply: Once tvl
is equal to dyad.totalSupply()
, the numerator
in the assetPrice
calculation becomes zero tvl - dyad.totalSupply() = 0.
Asset Price Calculation: When the assetPrice
function is called, it returns zero because the numerator is zero, making the price calculation 0 * 1e8 / denominator
= 0.
Withdrawal Attempt: A user tries to withdraw their funds from the unbounded vault
. The withdrawal function calls getNonKeroseneValue(id)
, which relies on assetPrice
of vault whhich is unbounded vault here and to determine the USD value of the assets that are not part of the Kerosene collateral.
Zero Asset Price Impact: Since assetPrice
is zero, getNonKeroseneValue(id)
also returns zero. This means that the system believes there is no non-Kerosene collateral value.
NotEnoughExoCollat Check: The withdrawal function then performs a check to ensure that the user has enough exogenous collateral to cover the withdrawal after accounting for the Kerosene collateral (dyadMinted). The check is if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat;.
Revert Condition Met: Because getNonKeroseneValue(id)
returns zero, any non-zero value requested for withdrawal will cause the subtraction to underflow
(which is prevented by Solidity 0.8.x), or it will simply be less than dyadMinted
, thus triggering the NotEnoughExoCollat revert
.
Withdrawal Failure: The transaction is reverted, and the user is unable to withdraw their funds, effectively locking assets in the vault until the issue is resolved.
</details>NOTE: This could also affect the redeemDyad function in the same way it affects the withdraw
function. Users may not be able to redeemDyad
because the underflow
could cause the transaction to revert.
Manual Review
Validation Checks: Implement validation checks in the assetPrice
function to ensure that the TVL is always greater than or equal to dyad.totalSupply()
. If not, the function should return a meaningful error rather than allowing an underflow.
Case 3 solution:
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()); } uint numerator = tvl - dyad.totalSupply(); ++ require(tvl > 0 , "tvl should be greater than zero"); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
DoS
#0 - c4-pre-sort
2024-04-27T18:21:02Z
JustDravee marked the issue as duplicate of #958
#1 - c4-pre-sort
2024-04-29T08:39:36Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T13:48:42Z
koolexcrypto marked the issue as duplicate of #308
#3 - c4-judge
2024-05-11T20:09:38Z
koolexcrypto marked the issue as satisfactory
#4 - c4-judge
2024-05-13T18:34:04Z
koolexcrypto changed the severity to 3 (High Risk)
#5 - 0xAbhay
2024-05-16T04:25:39Z
@koolexcrypto i think this finding has more detail than #308
here is the explanation
Finding #308 describes a specific scenario related to the assetPrice() calculation in the Unbounded Kerosene vault, where the numerator calculation (tvl - dyad.totalSupply()) can lead to assets getting temporarily stuck in the Kerosene vaults until the TVL surpasses the Dyad's total supply. However, it focuses only on this particular situation without exploring other potential issues.
On the other hand, Finding #873 elaborates on the vulnerability in the assetPrice() function of the Vault.kerosine.unbounded contract in more detail. It outlines three distinct cases where this vulnerability can cause problems: when the TVL is less than, greater than, or equal to the Dyad's total supply. Each case describes a scenario where the underflow in the numerator calculation (tvl - dyad.totalSupply()) can lead to a denial of service situation, potentially causing financial loss and loss of confidence in the system.
In summary, Finding 308 provides a single scenario, while Finding 873 expands on the issue by presenting three distinct cases where the vulnerability can manifest due to the numerator calculation.
#6 - koolexcrypto
2024-05-29T09:01:45Z
Thank you for your feedback.
Case 1 and 2 are the same. The root cause is the underflow, the attacker in case 2 used the same bug in case 1.
While I appreciate your thoroughness in this report, I believe #308 is much easier to understand and comprehend.
π 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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L213 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L65
This vulnerability can be exploited to avoid liquidations, allowing under-collateralized positions to persist and potentially leading to insolvency risks for the vault. It undermines the integrity of the liquidation mechanism, which is crucial for maintaining the platform's financial health.
In Deployment Script a new WETH
or wstETH
vault is created without any initial assets, resulting in a TVL of zero. Additionally, the deployment script grants an unbounded vault
to the vault licenser
, so the unbounded vault can be added to the VaultManagerV2
using the add
function. This also satisfies the check of !vaultLicenser.isLicensed(vault).
a user has minted dyad worth $2000
using 1 ETH
as collateral at a price of $3000
per ETH. Additionally, the user has deposited 1 Kerosene
into the unbounded vault
. If the user's collateralization ratio (CR) falls below the minimum required CR, their position becomes eligible for liquidation.
Here's how the vulnerability can prevent liquidation:
Liquidation Trigger: The user's position becomes under-collateralized, triggering eligibility for liquidation.
Liquidation Process: Another party or the system itself initiates the liquidation process by calling the liquidate function.
Collateral Valuation: The VaultManagerV2::liquidate
function calls collatRatio -> getTotalUsdValue(id)
to determine the total USD value of the user's collateral.
Calling getNonKeroseneValue(id): Within getTotalUsdValue(id)
, the getNonKeroseneValue(id)
function is called to assess the value of non-Kerosene collateral, which is a unbounded vault here.
Underflow in assetPrice
: If the TVL of the unbounded vault is less than dyad.totalSupply, the assetPrice
calculation for the unbounded vault underflows
, potentially reverting the transaction.
The underflow causes the transaction to revert, preventing liquidation
If the TVL Total Value Locked of the UnboundedKerosineVault
is less than dyad.totalSupply()
, the calculation of assetPrice()
could underflow when subtracting dyad.totalSupply()
from tvl
. This underflow would cause the transaction to revert. Since TVL is influenced by external users through deposits and withdrawals, it can be manipulated by any user
.
Manual Review
If tvl is less than dyad.totalSupply(), return a minimum asset price
value that represents an under-collateralized position, triggering liquidation.
Context
#0 - c4-pre-sort
2024-04-27T18:18:10Z
JustDravee marked the issue as duplicate of #958
#1 - c4-pre-sort
2024-04-29T08:39:38Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T13:48:46Z
koolexcrypto marked the issue as duplicate of #308
#3 - c4-judge
2024-05-11T20:09:58Z
koolexcrypto marked the issue as satisfactory
#4 - c4-judge
2024-05-13T18:34:04Z
koolexcrypto changed the severity to 3 (High Risk)
#5 - 0xAbhay
2024-05-16T04:31:16Z
@koolexcrypto Finding #308 focuses on the withdraw function and how this can be the same issue please explain and also this is a duplicate of #224. this can be a separate finding from #308.
#6 - koolexcrypto
2024-05-22T10:33:26Z
Hi @0xAbhay
Please clarify what's the difference, otherwise, I can't guess what you mean.
also this is a duplicate of https://github.com/code-423n4/2024-04-dyad-findings/issues/224
Not sure what does that mean. If you have a comment on different issue, please post it there separately.
#7 - 0xAbhay
2024-05-22T17:28:22Z
@koolexcrypto The two vulnerabilities described in the findings are different in their nature and impact, although both relate to issues with the calculation of asset prices in the context of under-collateralization and potential liquidation problems. Here is an explanation of each vulnerability with examples:
<details>Vulnerability Details:
Impact: This vulnerability allows users to avoid liquidation even when they are under-collateralized, risking the financial health of the vault.
Proof of Concept: When the total value locked (TVL) of the unbounded vault is less than the total supply of DYAD, the asset price calculation (assetPrice()
) underflows, causing the liquidation transaction to revert.
Example:
Vulnerability Details:
UnboundedKerosineVault::assetPrice
will revert, leading to stuck Kerosene deposits.tvl - dyad.totalSupply()
. If TVL is less than DYAD's total supply, the calculation reverts.Finding 1008: Avoids liquidation due to an underflow error during asset price calculation. It involves a scenario where the collateral ratio check fails due to the asset price calculation reverting, preventing liquidation.
Example: A userβs position should be liquidated because their collateral value falls below the required threshold. However, due to the underflow error in calculating the asset price, the liquidation process reverts, allowing the user to avoid liquidation despite being under-collateralized.
Finding 308: Involves the practical inability to interact with the vault due to insufficient TVL relative to DYAD's total supply, leading to stuck collateral.
Example: Users migrate their collateral from V1 to V2, but because the TVL in V2 is less than the total DYAD supply, any interaction with the Kerosene vault (such as depositing or withdrawing) fails, resulting in stuck collateral until the TVL exceeds the DYAD supply.
Both findings highlight critical issues in the liquidation and asset management processes but differ in the specifics of their causes and impacts.
</details>#8 - 0xAbhay
2024-05-22T17:29:26Z
@koolexcrypto thanks for judging
#9 - koolexcrypto
2024-05-29T09:06:49Z
Thank you for your further clarification.
All mentioned issues share the same root cause, some describe variant impact.