DYAD - Stefanov'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: 53/183

Findings: 3

Award: $236.12

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

7.3026 USDC - $7.30

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
:robot:_97_group
duplicate-128

External Links

Lines of code

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

Vulnerability details

Impact

Liquidators are not fully rewarded

Proof of Concept

https://dyadstable.notion.site/DYAD-design-outline-v6-3fa96f99425e458abbe574f67b795145

As it is said in the documentation "If a Note’s collateral value in USD drops below 150% of its DYAD minted balance, it faces liquidation. The liquidator burns a quantity of DYAD equal to the target Note’s DYAD minted balance, and in return receives an equivalent value plus a 20% bonus of the target Note’s backing colatera". We also know that Note's collateral value is the totality of kerosene + non kerosene tokens. However, VaultManagerV2:liquidate takes only the non kerosene vaults, which will not compensate fully the liquidators

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L221

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

Tools Used

Manual review

Liquidator should take portion from voultsKerosene too.

 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();
+     uint numberOfKeroseneVaults = vaultsKerosene[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);
      }
+     for (uint i = 0; i < numberOfKeroseneVaults ; i++) {
+         Vault vault      = Vault(vaultsKerosene[id].at(i));
+         uint  collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare);
+         vault.move(id, to, collateral);
+     }
      emit Liquidate(id, msg.sender, to);
  }

Assessed type

Token-Transfer

#0 - c4-pre-sort

2024-04-28T10:21:11Z

JustDravee marked the issue as duplicate of #128

#1 - c4-pre-sort

2024-04-29T09:08:12Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-11T19:39:46Z

koolexcrypto marked the issue as satisfactory

Findings Information

🌟 Selected for report: SBSecurity

Also found by: AlexCzm, Emmanuel, Stefanov, carlitox477, carrotsmuggler, d3e4, grearlake, peanuts

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
edited-by-warden
:robot:_75_group
duplicate-982

Awards

223.9474 USDC - $223.95

External Links

Lines of code

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

Vulnerability details

Impact

Liquidator's reward is not fully paid

Proof of Concept

https://dyadstable.notion.site/DYAD-design-outline-v6-3fa96f99425e458abbe574f67b795145

As it is said in the documentation "If a Note’s collateral value in USD drops below 150% of its DYAD minted balance, it faces liquidation. The liquidator burns a quantity of DYAD equal to the target Note’s DYAD minted balance, and in return receives an equivalent value plus a 20% bonus of the target Note’s backing collateral". The problem is that if a Note’s collateral value in USD drops to 110%, the 20% reward out of this 10% is 2% or close to noting. Liquidators won't liquidate such small position because they don't have interest in such small rewards. On the other hand the smaller positions are more problematic so the rewards for them should better rewarded.

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

Tools Used

Manual review

Give them 20% reward from the Dyad token balance that has been burned.

    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);
+     if(cappedCr > 1e18) {
+     uint liquidationAssetShare  = (LIQUIDATION_REWARD+ 1e18).divWadDown(cappedCr);
+     } else {
+       uint liquidationAssetShare  = (1e18).divWadDown(cappedCr);
+     }

Assessed type

Math

#0 - c4-pre-sort

2024-04-29T08:28:04Z

JustDravee marked the issue as duplicate of #75

#1 - c4-pre-sort

2024-04-29T11:59:01Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-11T12:12:19Z

koolexcrypto marked the issue as satisfactory

#3 - c4-judge

2024-05-13T18:40:55Z

koolexcrypto changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-05-28T19:22:07Z

koolexcrypto marked the issue as duplicate of #982

Awards

4.8719 USDC - $4.87

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
edited-by-warden
:robot:_75_group
duplicate-175

External Links

Lines of code

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

Vulnerability details

Impact

Users won't liquidate small positions because they will lose collateral instead of being rewarded. Protocol will accumulate bad debt

Proof of Concept

https://dyadstable.notion.site/DYAD-design-outline-v6-3fa96f99425e458abbe574f67b795145

Documentation says "If a Note’s collateral value in USD drops below 150% of its DYAD minted balance, it faces liquidation. The liquidator burns a quantity of DYAD equal to the target Note’s DYAD minted balance, and in return receives an equivalent value plus a 20% bonus of the target Note’s backing collateral". In case where a Note’s collateral value in USD drops below 100% this is not true.

If cr = 0.9e18 (90% collateral value) the liquidator is still liquidating all 100% minted DYAD balance. In this case liquidationEquityShare or the reward is equal to 0. The liquidator, in this case, is going to make only 90% of the collateral he liquidated. They are not only doing it without being rewarded, but also they are losing collateral.

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

Tools Used

Manual review

Gather small percentages of collateral from the other liquidations to reward liquidators of positions that are insolvent

Assessed type

Math

#0 - c4-pre-sort

2024-04-29T08:27:32Z

JustDravee marked the issue as duplicate of #1258

#1 - c4-pre-sort

2024-04-29T09:16:39Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-03T14:07:47Z

koolexcrypto changed the severity to QA (Quality Assurance)

#3 - c4-judge

2024-05-12T09:32:41Z

koolexcrypto marked the issue as grade-c

#4 - c4-judge

2024-05-22T14:26:07Z

This previously downgraded issue has been upgraded by koolexcrypto

#5 - c4-judge

2024-05-28T16:51:41Z

koolexcrypto marked the issue as satisfactory

#6 - c4-judge

2024-05-28T20:06:04Z

koolexcrypto marked the issue as duplicate of #175

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