DYAD - adam-idarrha's results

The first capital efficient overcollateralized stablecoin.

General Information

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

DYAD

Findings Distribution

Researcher Performance

Rank: 6/183

Findings: 6

Award: $766.31

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Summary

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.

Vulnerability Details

The protocol employs two distinct types of vaults, each governed by different License Contracts:

that allow you to add them to a position

  • Standard Vaults: Licensed by vaultLicenser.
function add(
      uint    id,
      address vault
  ) 
    external
      isDNftOwner(id)
  {
    if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults();
    if (!vaultLicenser.isLicensed(vault))  revert VaultNotLicensed();
  • Kerosene Vaults: Licensed by 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();
Licensing Setup

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:

  • Kerosene Manager licenses:
kerosineManager.add(address(ethVault));
kerosineManager.add(address(wstEth));
  • Vault Licenser licenses:
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.

this creates a problem for collateral calculation, let's see why:
  • Collateral Calculation Issue

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;
  }
Problem

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.

Proof Of Concept

Assumptions:
  • Bob possesses $150 in WETH.
  • He owns a position within the protocol identified as ID = 1, which currently holds no collateral or DYAD.
Steps and Outcomes:
  1. Bob deposits that into ethVault for position id = 1
  2. then he adds for that position the ethVault that he deposited into in both Vault and vaultsKerosene
  3. When the protocol calculates the collateral ratio for Bob’s position:
    • The system erroneously counts the 150 from ethVault twice – once for each registration.
    • As a result, the total collateral value considered by the protocol amounts to 300 USD (usdValue(Vaults) + usdValue(vaultsKerosene)).
  4. With the inflated collateral value, Bob is now able to borrow 200 USD, based on the protocol's required collateralization ratio of 150%.
    • Normally, with 150 USD as collateral, Bob should only be able to borrow up to 100 USD.
  • This incorrect setup leads to a position that is not just undercollateralized but also results in a direct financial loss of 50 USD when considering the actual value of the collateral.
  • By increasing the amount deposited and repeatedly exploiting this flaw to inflate perceived collateral values, Bob can substantially augment the severity of the attack.
  • If exploited on a larger scale or by multiple users, this vulnerability could lead to a significant depletion of the protocol's funds, as users withdraw far more than their rightful borrowing capacity.

Impact

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.

Tools Used

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.

Assessed type

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

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L119-L169

Vulnerability details

Summary

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.

Vulnerability Detail

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:

  • we can see in the script 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:

Proof Of Concept

  1. A user registers the unboundedKerosineVault within the vaults array.
  2. They deposit kerosene tokens into this vault.
  3. Minting DYAD passes the collateral check as getNonKeroseneValue erroneously includes the value of kerosene tokens.
  4. This results in a position backed solely by kerosene, violating the protocol's collateral requirements and increasing the risk of insolvency.

Impact

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.

Tools Used

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.

Assessed type

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

Lines of code

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

Vulnerability details

Summary

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.

Vulnerability Detail

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.

Impact

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.

Proof Of Concept

Consider a scenario where:

  • Alice has deposited 100 WETH in a WETHVault for NFT ID=1.
  • She decides to withdraw her collateral.
  1. Alice initiates a withdrawal from the VaultManagerV2.

  2. 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.

  3. This sets idToBlockOfLastDeposit[id = 1] to the current block number.

  4. When Alice’s transaction is processed, it is reverted due to the Deposited in same block check, preventing her from withdrawing her collateral.

Tools Used

Manual Review

To prevent this type of attack, implement access control for the deposit function using the isDNftOwner modifier.

Assessed type

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)

Awards

7.3026 USDC - $7.30

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
edited-by-warden
:robot:_75_group
duplicate-128

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205-L228

Vulnerability details

Summary

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.

Vulnerability Details

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:

  • Alice has minted 100 DYAD with position ID = 1.
  • Alice has 100 USD in wstethVault added to vaults arrays, and 50 USD in a WETH vault in vaultsKerosene.
  • The WETH vault's value drops, reducing Alice's total collateral to 149 USD, making her position eligible for liquidation (the drop is in weth from 50 to 49).

Process:

  1. Liquidator Bob decides to liquidate Alice's position, burning 100 DYAD.
  2. Calculations for Bob’s reward are as follows:
    • cappedCr: 1.49e18 (149% collateral ratio)
    • liquidationEquityShare: 9.8% (0.098e18)
    • liquidationAssetShare: 73% (0.73e18)
  3. bob loops over all vaults in the Vaults array and gets 73% of them. there is only wstethVault in it.
  4. Bob receives 73 USD from the wstethVault as the collateral share, resulting in a net loss of 27 USD, as he burned DYAD worth 100 USD.

Outcome:

  • Financial Impact on liquidators: liquidators recovers only 73% of the collateral against the 100 DYAD he burned, resulting in a $27 USD shortfall.
  • Financial Gain for users: effectively gains $27 USD by retaining part of the undervalued collateral, despite his position being undercollateralized.
  • Incentive Disruption: This scenario demotivates liquidators from acting, potentially leading to the accumulation of bad debt within the protocol.

Additional Considerations:

  • At 149% Collateralization: liquidators incurs a 27% loss relative to the amount he contributed in USD.
  • At 100% Collateralization or Below: liquidators would recover only the collateral in vaults in the vaults array, which could be less valuable depending on market conditions affecting those specific assets.
  • Impact of Kerosene Vault Devaluation: If the 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.

Impact

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.

Tools Used

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.

Assessed 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

Findings Information

🌟 Selected for report: carrotsmuggler

Also found by: Al-Qa-qa, Emmanuel, TheFabled, TheSavageTeddy, ZanyBonzy, adam-idarrha, alix40, lian886

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
upgraded by judge
edited-by-warden
:robot:_68_group
duplicate-68

Awards

746.4915 USDC - $746.49

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L119-L153

Vulnerability details

Summary

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.

Vulnerability Detail

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.

overview of the attack:
  1. Initial Setup: User establishes a position with substantial collateral but no minted DYAD. to be able to control the overcollaterization ratio and thus ba able to lower the price of kerosene.
  2. because he hasn't minted any dyad , the overcollaterisation is high , and thus asset price of kerosine is high
  3. User conducts a flash swap on the Uniswap WETH/kerosene pool and deposits the borrowed amount into position ID = 1.
  4. user mints DYAD with the kerosene token just deposited at a just-sufficient collateralization ratio of 150%.
  5. SelfThe user then intentionally drops the collateralization ratio to 149% by minting additional DYAD with position id = 2, setting up their own position for liquidation.
  6. Collateral Transfer via Liquidation: The user liquidates their position, transferring 73.69% of the collateral to the secondary position which has no block restriction on withdrawals.
  7. Withdrawal and Repetition: The user withdraws the transferred collateral and repeats the process, each cycle diminishing the collateral by 73.69% (for collateral of 149%) until it’s manageable to cover personally.
  8. the repetition each time can be done with lowering the asset price of kerosine by more and more dyad with collateral already provided.

note that there is no fees paid to the protocol for minting dyad or liquidating so there is no loss for the attacker.

Impact

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.

Tools Used

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);
  }

Assessed type

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)

Awards

7.3512 USDC - $7.35

Labels

bug
2 (Med Risk)
high quality report
satisfactory
:robot:_09_group
duplicate-118

External Links

Lines of code

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

Vulnerability details

Summary

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.

Vulnerability Detail

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.

Impact

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.

Proof Of Concept

Consider a scenario where:

  • Alice has WETHVault in the vaults of position ID=1.
  • She decides to remove this vault.
  1. Alice initiates a remove or removeKerosene from the VaultManagerV2.

  2. 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.

  3. This sets calls WETHVault:deposit which increments id2asset[id = 1] by 1.

  4. 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.

Tools Used

Manual Review

To prevent this type of attack, implement access control for the deposit function using the isDNftOwner modifier.

Assessed type

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

Awards

4.8719 USDC - $4.87

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
:robot:_67_group
duplicate-67

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L50-L69

Vulnerability details

Summary

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.

Vulnerability Detail

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.

Proof Of Concept

Assumptions
  • The WETH/KEROSENE pool price reflects the UnboundedKerosineVault asset price due to arbitrage activities.
Attack Path
  1. the attacker takes a flashswap of kerosine tokens from the uniswap pool
  2. deposits it into a position as collateral
  3. mints dyad with this collateral
  4. this will lower the asset price of kerosene tokens
  5. anybody using kerosene as part or full of their position may get liquidated leading to mass liquidations
  6. the attacker runs away with the reward uses the bypass of flash loan protection to pay back the flashswap.
  7. the users get mass liquidated erroneously

Impact

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.

Tools Used

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);
  }

Assessed type

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter