DYAD - wangxx2026'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: 135/183

Findings: 1

Award: $4.87

🌟 Selected for report: 0

🚀 Solo Findings: 0

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-L68 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/VaultManagerV2.sol#L230-L239 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L250-L267

Vulnerability details

Impact

A user deposit too much collateral can raise the price of Kerosene, But he can also lower the price of Kerosene by withdrawing collateral or mint DYAD.

A malicious user can manipulate the price of Kerosene to influence the user to meet the liquidation criteria, and the malicious user can profit from the liquidation.

Proof of Concept

1、Casting DYAD process

With VaultManagerV2.mintDyad we can see the restrictions on mintDyad https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L156-L169

function mintDyad( uint id, uint amount, address to ) external isDNftOwner(id) { uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount; // @audit If user is casting DYAD for the first time, newDyadMinted is 0, getNonKeroseneValue if also 0 can be checked by if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); dyad.mint(id, to, amount); // @audit The collateralization rate calculation includes getNonKeroseneValue and getKeroseneValue two parts, if getNonKeroseneValue is 0, completely getKeroseneValue is non-zero, as long as the collateralization rate is greater than 1.5 can be casting normally. if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); emit MintDyad(id, amount, to); }

The main limitation is:

  1. getNonKeroseneValue(id) < newDyadMinted With the getNonKeroseneValue function, we can see that the collateral value is calculated excluding the KeroseneValue value. If it is the first time the user calls mintDyad, newDyadMinted=0, getNonKeroseneValue returns 0 which is passable. https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L250-L267
  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;
  }
  1. collatRatio(id) < MIN_COLLATERIZATION_RATIO Through the collatRatio method we can see that the calculation of the pledge rate includes getNonKeroseneValue and getKeroseneValue 2 parts, the sum of the 2 collateralization rate is greater than or equal to 1.5 can be. getNonKeroseneValue is equal to 0, also can be passed. https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L230-L239
  function collatRatio(
    uint id
  )
    public 
    view
    returns (uint) {
      uint _dyad = dyad.mintedDyad(address(this), id);
      if (_dyad == 0) return type(uint).max;
      return getTotalUsdValue(id).divWadDown(_dyad);
  }

The collatRatio calls the getTotalUsdValue method. https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L241-L248

  function getTotalUsdValue(
    uint id
  ) 
    public 
    view
    returns (uint) {
      return getNonKeroseneValue(id) + getKeroseneValue(id);
  }

Through the getKeroseneValue method, we can see the calculation process https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L269-L286

  function getKeroseneValue(
    uint id
  ) 
    public 
    view
    returns (uint) {
      uint totalUsdValue;
      uint numberOfVaults = vaultsKerosene[id].length(); 
      for (uint i = 0; i < numberOfVaults; i++) {
        Vault vault = Vault(vaultsKerosene[id].at(i));
        uint usdValue;
        if (keroseneManager.isLicensed(address(vault))) {
          usdValue = vault.getUsdValue(id);  // @audit Calculating Collateral value      
        }
        totalUsdValue += usdValue;
      }
      return totalUsdValue;
  }

With vault.getUsdValue we can see that the value depends on the price and quantity https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.sol#L79-L89)

  function getUsdValue(
    uint id
  )
    external
    view 
    returns (uint) {
      return id2asset[id] * assetPrice() 
              * 1e18 
              / 10**oracle.decimals() 
              / 10**asset.decimals();
  }

Through the UnboundedKerosineVault.assetPrice we can see how the Kerosene price is calculated.

  function assetPrice() 
    public 
    view 
    override
    returns (uint) {
...
      for (uint i = 0; i < numberOfVaults; i++) {
        Vault vault = Vault(vaults[i]);
        tvl += vault.asset().balanceOf(address(vault))  // @audit Get locked value
                * vault.assetPrice() * 1e18
                / (10**vault.asset().decimals()) 
                / (10**vault.oracle().decimals());
      }
      uint numerator   = tvl - dyad.totalSupply();
      uint denominator = kerosineDenominator.denominator();
      return numerator * 1e8 / denominator; // @audit Calculate Kerosine price
  }

We can see that a malicious user can reduce the value of getKeroseneValue by reducing the pledge to reduce the tvl. This is when users who mainly rely on KeroseneValue pledges are affected. This is when malicious users can profit by liquidating these users

2、Liquidation process

Through the Liquidation method, we can see that the pledge rate is lower than 1.5 will be liquidated, the liquidator can get rewards. https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205-L228

  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);
      }
      emit Liquidate(id, msg.sender, to);
  }

collatRatio Through the above analysis, we can know that users with more KeroseneValue collateralization are vulnerable to price manipulation

Tools Used

Manual Review

When calling withdraw to extract collateral and calling mintDyad, we should not only consider whether the collateralization rate is greater than or equal to 1.5, but also consider the impact on KeroseneValue, for example, one day to limit the range of fluctuation of KeroseneValue, so as to protect the system.

Assessed type

Oracle

#0 - c4-pre-sort

2024-04-28T05:08:55Z

JustDravee marked the issue as duplicate of #67

#1 - c4-pre-sort

2024-04-29T09:17:10Z

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:04Z

koolexcrypto marked the issue as unsatisfactory: Invalid

#4 - c4-judge

2024-05-08T12:02:04Z

koolexcrypto marked the issue as satisfactory

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