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: 6/183
Findings: 6
Award: $766.31
🌟 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#L34-L35 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L67-L91 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L230-L239
The protocol features two types of vaults for users to deposit collateral: standard vaults and kerosene vaults. However, due to a flaw in the system, users can register the same vault in both the vaults
and vaultsKerosene
arrays. This duplication leads to the same collateral being counted twice in the calculation of the collateral ratio. As a result, users can inappropriately inflate their collateral value and borrow more debt than their actual collateral should allow. If exploited on a large scale, this could potentially lead to the depletion of all funds within the protocol.
The protocol employs two distinct types of vaults, each governed by different License Contracts:
that allow you to add them to a position
vaultLicenser
.function add( uint id, address vault ) external isDNftOwner(id) { if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults(); if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed();
keroseneManager
.function addKerosene( uint id, address vault ) external isDNftOwner(id) { if (vaultsKerosene[id].length() >= MAX_VAULTS_KEROSENE) revert TooManyVaults(); if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed();
Both licensing entities manage which vaults are authorized within the system. This setup is evident in the deployment script DeployV2, which the vaults licensed by each:
kerosineManager.add(address(ethVault)); kerosineManager.add(address(wstEth));
vaultLicenser.add(address(ethVault)); vaultLicenser.add(address(wstEth)); vaultLicenser.add(address(unboundedKerosineVault));
Notably, both ethVault
and wstEth
vaults are licensed by both the vaultLicenser
and the kerosineManager
. This dual licensing allows these vaults to be added to both vault arrays: Vaults and vaultsKerosene.
The core of the vulnerability arises from how the protocol calculates collateral ratios. The VaultManagerV2:collatRatio
function, which determines the collateral ratio, relies on aggregating the USD values from both standard and kerosene vaults:
function collatRatio( uint id ) public view returns (uint) { --- return getTotalUsdValue(id).divWadDown(_dyad); }
the function getTotalUsdValue
:
function getTotalUsdValue( uint id ) public view returns (uint) { return getNonKeroseneValue(id) + getKeroseneValue(id); }
The functions getNonKeroseneValue
and getKeroseneValue
iterate through their respective vault arrays to compute the total USD value:
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; }
If the same vaults (ethVault and wstEth) are added to both the Vaults and vaultsKerosene arrays, the collateral deposited within these vaults gets counted twice in the total USD value computation. This double-counting leads to an inflated collateral value, allowing users to borrow against an artificially high collateral base.
Vault
and vaultsKerosene
This issue is classified as high severity due to its potential to significantly destabilize the protocol. Users can exploit this vulnerability to mint substantial amounts of uncollateralized DYAD, which they can then sell, effectively draining the protocol's funds. The exploit does not require any external conditions to be met, making it an immediate and direct threat. This vulnerability not only enables theft on a potentially massive scale but also threatens the overall integrity and solvency of the protocol, thereby justifying its classification as a high-severity issue.
Manual Review
To prevent the duplication of vaults in both the Vaults and vaultsKerosene arrays, the protocol should maintain a single set of addresses that tracks all registered vaults. This set should be checked whenever a vault is added to either array.
Invalid Validation
#0 - c4-pre-sort
2024-04-28T18:52:15Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:59Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:28Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-28T15:28:34Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T07:07:03Z
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
Kerosene is a token that represents the overcollateralization within the protocol relative to the amount of DYAD minted. Using Kerosene as collateral is risky for the protocol’s solvency because its value is intrinsically tied to the same assets it secures. This creates a feedback loop where any depreciation in the underlying collateral can disproportionately affect Kerosene's value, amplifying risks and potentially destabilizing the protocol, this is why:
The protocol requires that at least 100% of the 150% collateral ratio backing DYAD be comprised of exogenous assets, excluding the kerosene token, to maintain stability and avoid feedback loops affecting DYAD's price. However, due to an oversight, users can classify kerosene tokens as exogenous collateral by adding them to the vaults
array, thus violating the protocol's key collateral invariant and threatening the peg of DYAD.
The protocol enforces the 100% exogenous collateral ratio rule through specific checks in the DYAD minting and withdrawal functions, which ensure that the non-kerosene collateral value meets or exceeds the amount of DYAD minted. These checks are intended to verify that adequate exogenous collateral is present:
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(); --- }
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(); }
as we can see the check in line 165 and 150 check that the getNonKeroseneValue(id)
is more than the dyad minted signifying more than 100% collateral ratio .
The core of the problem lies in the inclusion of the unboundedKeroseneVault
in the vaults array, licensed by vaultLicenser
:
DeployV2
vaultLicenser.add(address(ethVault)); vaultLicenser.add(address(wstEth)); vaultLicenser.add(address(unboundedKerosineVault)); // vaultLicenser.add(address(boundedKerosineVault));
This inclusion improperly classifies kerosene tokens as valid exogenous collateral, leading to incorrect collateral value calculations:
This flaw poses a high-severity risk as it undermines the foundational collateral requirements of the protocol, potentially leading to the issuance of DYAD that is not adequately backed by stable, exogenous assets. This could destabilize the entire economic model of the protocol, affecting all users and stakeholders.
Manual Review
Ensure that the vaults array strictly excludes any vaults holding or dealing with kerosene tokens. This can be enforced by refining the licensing checks within the vaultLicenser to differentiate between acceptable exogenous asset vaults and those intended for kerosene tokens.
Context
#0 - c4-pre-sort
2024-04-28T18:53:53Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:59Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:21Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-29T11:20:18Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T11:43:08Z
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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L134-L143
Due to lacking access controls on the deposit function in VaultManagerV2
, an attacker can deposit
into any user's position, which can interfere with that user's ability to withdraw their deposited collateral.
The VaultManagerV2:deposit
function allows deposits into any NFT position within any vault. Although the documentation states that only the NFT owner should be able to make deposits, the actual code uses the isValidDNft
modifier rather than isDNftOwner
. This oversight allows anyone to make a deposit into any valid NFT.
An attacker can exploit this by making a deposit (even of zero amount) into a vault during the same block in which the legitimate user intends to withdraw. The protocol contains a safeguard that prevents deposits and withdrawals from the same position within the same block, which the attacker can abuse to trigger transaction reverts for legitimate withdrawals.
This vulnerability can be used to repeatedly disrupt the withdrawal process, essentially locking a user’s collateral in the vault without the possibility of retrieval as long as the attacker continues to exploit this flaw. Given the importance of the withdrawal function, which enables users to reclaim their collateral, this issue is categorized as medium severity.
Consider a scenario where:
Alice initiates a withdrawal from the VaultManagerV2.
An attacker observes Alice’s transaction in the mempool and executes a deposit function call for the same NFT ID=1 with an amount of 0, with any vault address he wants.
This sets idToBlockOfLastDeposit[id = 1] to the current block number.
When Alice’s transaction is processed, it is reverted due to the Deposited in same block check, preventing her from withdrawing her collateral.
Manual Review
To prevent this type of attack, implement access control for the deposit function using the isDNftOwner
modifier.
DoS
#0 - c4-pre-sort
2024-04-27T11:53:53Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:28:40Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:38:18Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:39:24Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T20:39:27Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-05-05T21:41:59Z
koolexcrypto marked the issue as nullified
#6 - c4-judge
2024-05-05T21:42:05Z
koolexcrypto marked the issue as not nullified
#7 - c4-judge
2024-05-08T15:27:58Z
koolexcrypto marked the issue as duplicate of #1001
#8 - c4-judge
2024-05-11T19:49:55Z
koolexcrypto marked the issue as satisfactory
#9 - c4-judge
2024-05-13T18:34:30Z
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
In the VaultManagerV2
, liquidators are dissuaded from performing their role due to the unfavorable economics of liquidation transactions. Specifically, the liquidation rewards do not sufficiently compensate for the amount of DYAD burned, particularly when collateral is distributed between vaults in Vaults
and vaultsKerosene
array. This results in liquidators receiving less than the debt they repaid, deterring liquidation actions and increasing the risk of bad debt accumulation within the protocol. creating a critical vulnerability.
the function VaultManagerV2:liquidate
allows anybody to liquidate a position.
he needs to give to the protocol (to burn) the same dyadAmount minted for that position.
dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id));
and the amount that he gets back is calculated like so:
uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr);
line 217 basically caps the collatRatio to not be less than 1e18 so that it doesn't revert in insolvent cases.
line 218 calculates the reward of the liquidator which is LIQUIDATION_REWARD = 20% of overcollateralisation, which is done to incentivise liquidators to liquidate as soon as the collat drops from 150%
line 219 calculates the percentage amount that the liquidator will take from the liquidatee, by adding the reward to 1e18 and dividing it by how much the liquidatee has of collateral.
in this line we calculate how much the liquidator gets:
for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare);
This process loops through all vaults in the vaults
tied to the position and extracts the liquidation share of collateral.
But it forgets to extract collateral from the vaultsKerosene
.
Leading to a critical issue arises if a user deposits the minimum required collateral into vaults (100% of the position's value), resulting in the liquidator receiving a fraction equivalent to the calculated percentage, which may be less than the DYAD amount burned.
Calculations:
Assumptions:
vaults
arrays, and 50 USD in a WETH vault in vaultsKerosene
.Process:
Vaults
array and gets 73% of them. there is only wstethVault in it.wstethVault
as the collateral share, resulting in a net loss of 27 USD, as he burned DYAD worth 100 USD.Outcome:
Additional Considerations:
vaults
array, which could be less valuable depending on market conditions affecting those specific assets.vaultsKerosene
vaults themselves lose value, then liquidation becomes unfeasible for liquidators even at 100% collateralization since he would not break even.Note that the user can have collateral distributions like this , and there is nothing protecting the protocol from such an attack. so it has a really high likelihood of hapenning (almost always) and the severity is there so it deserves a critical.
This vulnerability critically undermines the motivation for liquidators to act, as the risk of incurring losses makes liquidation unattractive. This lack of action can lead to positions that are undercollateralized not being addressed, resulting in the accumulation of bad debt within the protocol. Additionally, the ability for users to manipulate their collateral distribution to avoid liquidation not only threatens the integrity of the protocol but also creates a scenario where users can profit from an imbalanced system. The accrual of bad debt is particularly severe because it directly threatens the stability of DYAD's peg to the dollar, undermining the fundamental purpose of the protocol. If the protocol accumulates significant bad debt, it could lead to a situation where the collateral is worth less than the debt it supports, further destabilizing the protocol's economy and eroding trust among its users.
Manual Review
To address this issue, the liquidation process should include Vaults
and vaultsKerosene
vaults in its calculations. By ensuring that liquidators receive a fair percentage from the total collateral available, regardless of the vault type.
Math
#0 - c4-pre-sort
2024-04-28T18:56:49Z
JustDravee marked the issue as duplicate of #456
#1 - c4-pre-sort
2024-04-29T09:31:22Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-12T09:02:45Z
koolexcrypto marked the issue as not a duplicate
#3 - c4-judge
2024-05-12T09:02:52Z
koolexcrypto marked the issue as duplicate of #128
#4 - c4-judge
2024-05-12T09:19:16Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: carrotsmuggler
Also found by: Al-Qa-qa, Emmanuel, TheFabled, TheSavageTeddy, ZanyBonzy, adam-idarrha, alix40, lian886
746.4915 USDC - $746.49
The protocol implements measures to prevent the use of flash loans for manipulating the exchange rate of the kerosene token. It does so by prohibiting the deposit and withdrawal of collateral in the same block. However, this protection can be circumvented using the liquidation function, allowing users to manipulate the exchange rate through strategic use of flash loans.
The protocol's primary defense against flash loan attacks involves the idToBlockOfLastDeposit[id]
mapping, which tracks the block number of the last deposit to a position:
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { idToBlockOfLastDeposit[id] = block.number;
Withdrawals in the same block as the deposit are explicitly blocked to prevent the use of flash loans:
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();
However, this measure only applies to the specific position ID. By transferring collateral between positions, users can evade this restriction. This is achievable through the liquidation function, which can move collateral from an undercollateralized position to another, thereby bypassing the block restriction.
note that there is no fees paid to the protocol for minting dyad or liquidating so there is no loss for the attacker.
This vulnerability allows users to bypass critical flashloan protections, enabling them to manipulate the kerosene token’s exchange rate. Such actions can destabilize the token’s value and, by extension, the entire protocol's economic model, leading to significant financial instability and potential losses for other participants.
also this is a clear protection that the protocol said they deployed V2 for, and it's bypassed. so it deserves a high severity.
Manual Review
update the idToBlockOfLastDeposit
when there is movement of collateral between tokens, in liquidate
:
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); + idToBlockOfLastDeposit[to] = block.number } emit Liquidate(id, msg.sender, to); }
Uniswap
#0 - c4-pre-sort
2024-04-27T18:06:10Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:28:41Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:18:31Z
koolexcrypto marked the issue as not a duplicate
#3 - c4-judge
2024-05-06T11:33:46Z
koolexcrypto marked the issue as duplicate of #68
#4 - c4-judge
2024-05-11T12:13:49Z
koolexcrypto marked the issue as satisfactory
#5 - c4-judge
2024-05-13T18:35:03Z
koolexcrypto changed the severity to 2 (Med Risk)
#6 - c4-judge
2024-05-28T09:57:10Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L94-L116
Due to inadequate access controls on the deposit function in VaultManagerV2
, an attacker can deposit into any user's position. This vulnerability can be exploited to make a user's transaction to remove a vault from their position revert.
The VaultManagerV2:deposit
function allows anyone to deposit into any NFT position across all vaults. By depositing a minimal amount, such as 1 wei, into the vault that a user is trying to remove from their position, an attacker can trigger a revert of the user’s removal transaction. This occurs due to a check in the removeKerosene and remove functions:
if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets();
An attacker can monitor the mempool for removal transactions and disrupt them by depositing a small amount, effectively blocking the removal process by ensuring the vault shows a non-zero asset balance.
This vulnerability enables an attacker to deny the removal of vaults from positions with minimal cost (1 wei plus transaction fees). Affected users must withdraw the tiny deposit before successfully removing the vault, incurring additional transaction costs.
Consider a scenario where:
Alice initiates a remove or removeKerosene from the VaultManagerV2.
An attacker observes Alice’s transaction in the mempool and executes a deposit function call for the same NFT ID=1 with an amount of 1 wei, with the vault she wants to remove.
This sets calls WETHVault:deposit
which increments id2asset[id = 1] by 1.
When Alice’s transaction is processed, it is reverted due to id2asset[id = 1] for the vault she is trying to remove is > 0.
Alice must now withdraw the 1 wei and attempt the removal again, wasting gas and time in the process.
Manual Review
To prevent this type of attack, implement access control for the deposit function using the isDNftOwner
modifier.
DoS
#0 - c4-pre-sort
2024-04-27T11:37:22Z
JustDravee marked the issue as primary issue
#1 - c4-pre-sort
2024-04-27T11:37:34Z
JustDravee marked the issue as high quality report
#2 - JustDravee
2024-04-27T11:38:13Z
Not a dup of 🤖_09_group
#3 - c4-pre-sort
2024-04-27T11:45:10Z
JustDravee marked the issue as duplicate of #489
#4 - c4-judge
2024-05-05T20:38:18Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#5 - c4-judge
2024-05-05T20:39:23Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#6 - c4-judge
2024-05-05T20:39:26Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#7 - c4-judge
2024-05-05T21:40:48Z
koolexcrypto marked the issue as nullified
#8 - c4-judge
2024-05-05T21:40:51Z
koolexcrypto marked the issue as not nullified
#9 - c4-judge
2024-05-05T21:40:57Z
koolexcrypto marked the issue as not a duplicate
#10 - c4-judge
2024-05-06T08:54:31Z
koolexcrypto marked the issue as duplicate of #118
#11 - c4-judge
2024-05-11T12:24:04Z
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
An attacker can use the WETH/Kerosene pool on Uniswap to obtain large quantities of Kerosene through a flashswap. By depositing this Kerosene into the keroseneVault and minting substantial amounts of DYAD, the attacker can artificially lower the asset price of Kerosene. This price manipulation can result in the liquidation of positions collateralized fully or partially with Kerosene, allowing the attacker to profit from liquidation rewards. and users erroneously liquidated.
The protocol calculates the price of Kerosene based on the total collateralization within the system, as shown in the UnboundedKerosineVault
:
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; }
The Kerosene token price is sensitive to the system's overall collateralization. By inflating the supply of DYAD without increasing the total value locked (TVL), an attacker can lower the Kerosene price, making positions with Kerosene collateral vulnerable to liquidation. this can be done by providing collateral as kerosene and minting dyad with it.
This strategy can cause widespread liquidations, eroding trust in the protocol and destabilizing its economic model. It also presents a significant financial risk to all DYAD holders as their assets could be unfairly liquidated.
Manual Review
implement protections against flash loan bypass by updating the idToBlockOfLastDeposit
when there is movement of collateral between tokens, in liquidate
:
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); + idToBlockOfLastDeposit[to] = block.number } emit Liquidate(id, msg.sender, to); }
Uniswap
#0 - c4-pre-sort
2024-04-28T05:57:18Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:18:40Z
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:00Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-08T12:22:30Z
koolexcrypto marked the issue as satisfactory
#5 - c4-judge
2024-05-08T12:22:35Z
koolexcrypto marked the issue as not a duplicate
#6 - c4-judge
2024-05-08T13:15:27Z
koolexcrypto marked the issue as duplicate of #67