DYAD - peanuts'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: 26/183

Findings: 4

Award: $446.95

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

Awards

200.8376 USDC - $200.84

Labels

3 (High Risk)
satisfactory
upgraded by judge
duplicate-1097

External Links

Judge has assessed an item in Issue #1211 as 2 risk. The relevant finding follows:

[L-01] No partial repayment option will be disadvantageous for the protocol. Whales can potentially not get liquidated at all. When a user’s collateral ratio is below 150%, the user can be liquidated. The liquidator will burn all the dyad minted in exchange for collateral funds.

/// @inheritdoc IVaultManager 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));

The issue is that the liquidator MUST burn all the dyad that the user had minted. If the user is a whale, and has minted 1 million dyad worth ~1M, only other whales can liquidate him because most people don’t have the capital to do so.

The other whales is not incentivize to liquidate also since if whales are starting to fall below the collateral ratio, it means that there is a flash crash happening to the price of the collateral, which will drive the price of DYAD down.

For whales to liquidate other whales, they have to bear the burden of having ~1M DYAD that they need to burn in order to get back their original collateral. Having this debt in a time of flash crashes is not a good idea.

Consider having partial liquidation so that users with smaller balances can still liquidate without needed to burn so much DYAD.

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

#0 - c4-judge

2024-05-10T11:57:36Z

koolexcrypto changed the severity to 3 (High Risk)

#1 - koolexcrypto

2024-05-10T11:59:29Z

I can't understand why you reported it as low. You seem to understand the impact and your explanation of it is clear

#2 - c4-judge

2024-05-10T12:00:17Z

koolexcrypto marked the issue as duplicate of #1097

#3 - c4-judge

2024-05-11T12:21:40Z

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)
satisfactory
sufficient quality report
: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/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205-L228

Vulnerability details

Impact

Liquidation rewards are not fair. Liquidators are more incentivized to liquidate a position that just dipped below MIN_COLLATERAL_RATIO than a position that is way below the minimum.

Proof of Concept

When liquidating an account, the function checks the cappedCr. The minimum liquidation ratio is <150%. If the current collateral ratio is below 100%, then cappedCr is 100%. Otherwise, cappedCr is whatever the current collateral ratio is.

The function will then check the liquidationEquityShare and liquidationAssetShare and move() the collateral to the liquidator.

/// @inheritdoc IVaultManager function liquidate( ... uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr);

The liquidation reward is set at 0.2e18 which is 20%. This means that the liquidator should get a 20% reward when liquidation a position. This is not the case.

Let's say a user has 2000 USD worth of ETH (1 ETH) and mints 1000 DYAD. The current collateral ratio is 200%. The price of eth drops until the user has 1490 USD worth of ETH. The current collateral ratio is 149%. The user is now liquitable.

uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr); cappedCr = 1.49e18 liquidationEquityShare = (1.49e18 - 1e18) * 0.2e18 / 1e18 = 0.098e18 liquidationAssetShare = (0.098e18 + 1e18) * 1e18 / 1.49e18 = 0.7369e18 uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); uint collateral = 1e18 * 0.7369e18 / 1e18 = 0.7369e18

The liquidator burns 1000 DYAD and gets 1097 USD worth of ETH (0.739 ETH). This means he gets ~97 USD worth of rewards.

Let's say the price of eth drops until the user has 1200 USD worth of ETH instead. The current collateral ratio is 120%. The user is now liquitable.

cappedCr = 1.2e18 liquidationEquityShare = (1.2e18 - 1e18) * 0.2e18 / 1e18 = 0.04e18 liquidationAssetShare = (0.04e18 + 1e18) * 1e18 / 1.2e18 = 0.8667e18 uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); uint collateral = 1e18 * 0.8667e18 / 1e18 = 0.8667e18

The liquidator burns 1000 DYAD and gets 1040 USD worth of ETH (0.8667 ETH). This means he gets ~40 USD worth of rewards.

In both cases, if CR is 149% or CR is 120%, the user does not get a 20% liquidation reward, but rather a factor of it (9.7% and 4% respectively assuming DYAD is at 1 USD). Also, the liquidator gets more rewards if the collateral ratio is just below the threshold rather than way below the threshold, which should not be the case (liquidator should be incentivized to liquidator if the CR is lower rather than higher).

Tools Used

Manual Review

Recommend simplifying the liquidation rewards. User burns DYAD to get 10% more in extra collateral, instead of finding the equity share. Also, the liquidation reward is too much, maybe 5-10% will suffice.

Assessed type

Context

#0 - c4-pre-sort

2024-04-29T07:13:25Z

JustDravee marked the issue as duplicate of #906

#1 - c4-pre-sort

2024-04-29T08:46:22Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-05T10:10:58Z

koolexcrypto marked the issue as not a duplicate

#3 - c4-judge

2024-05-05T10:13:56Z

koolexcrypto marked the issue as unsatisfactory: Invalid

#4 - cryptostaker2

2024-05-16T19:20:20Z

Hi, this issue should not be a dupe of #906.

This issue talks about how liquidators do not get 20% collateral, which is related to #75.

More importantly, it also talks about how liquidators get more incentives when the collateralRatio is nearer to 150% and how liquidators get less incentives when the collateralRatio is near 100% (which should be the opposite).

In both cases, if CR is 149% or CR is 120%, the user does not get a 20% liquidation reward, but rather a factor of it (9.7% and 4% respectively assuming DYAD is at 1 USD).

Also, the liquidator gets more rewards if the collateral ratio is just below the threshold rather than way below the threshold, which should not be the case (liquidator should be incentivized to liquidator if the CR is lower rather than higher).

Thanks!

#5 - koolexcrypto

2024-05-23T11:17:55Z

Hi @cryptostaker2

This wasn't a dup of #906 . However, should be duped with #75 since you stated

This means that the liquidator should get a 20% reward when liquidation a position. This is not the case.

#6 - c4-judge

2024-05-23T11:18:03Z

koolexcrypto removed the grade

#7 - c4-judge

2024-05-23T11:18:19Z

koolexcrypto marked the issue as duplicate of #75

#8 - c4-judge

2024-05-28T19:22:09Z

koolexcrypto marked the issue as duplicate of #982

#9 - c4-judge

2024-05-29T11:23:44Z

koolexcrypto marked the issue as satisfactory

Awards

17.2908 USDC - $17.29

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_11_group
duplicate-977

External Links

Lines of code

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

Vulnerability details

Impact

CR < 100% will lead to bad debt that no one can clear. Liquidators will not be incentivized to clear the debt because they will lose out instead, and this will lead to the depeg of DYAD if too much bad debt accrues in the protocol.

Proof of Concept

When it is time for liquidation, the liquidator will burn his DYAD in exchange for some extra collateral from the user being liquidated. The MIN_COLLATERIZATION_RATIO is set at 1.5e18 (150%) so if any user goes below this ratio, they are susceptible to liquidation.

The liquidator will just burn his DYAD in exchange for the user's collateral. The user will get to keep his minted DYAD and whatever collateral left remaining.

/// @inheritdoc IVaultManager 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;

For example, if the user mints 1000 DYAD and has 1200 USD worth of collateral, his collateral ratio is 120% which means he is liquitable. A liquidator can burn 1000 DYAD for ~1040 USD worth of collateral according to the calculation. The extra 40 USD is the incentive for the liquidator. The user gets to keep 1000 DYAD and ~160 USD worth of collateral.

In an event of a flash crash, the CR of users can go below 100%. In that case, the liquidator is not incentivized to liquidate anymore. If a user has 1000 DYAD and has 900 USD worth of collateral, (CR at 90%), the liquidator will take all the collateral, but will still be insufficient to cover his 1000 DYAD that he burned.

This means that any user's CR <100% will incur bad debt. Since no one wants to liquidate them and they cannot withdraw their collateral (unless they burn their DYAD), they will simply try to sell their DYAD on external markets. The price of DYAD will depend on how much bad debt there is, and if they cannot sell their DYAD for at least 0.9, then they will have to burn it to get their collateral back. People will no longer be willing to trade DYAD for a dollar, resulting in a depeg scenario.

Tools Used

Manual Review

If collateral ratio is below 1e18, then the liquidator should also get a portion of the DYAD from the user being liquidated. This will help offset the bad debt.

If a user has 1000 DYAD and has 900 USD worth of collateral, (CR at 90%), the liquidator will take all the collateral, and also 100 (+ incentive) DYAD extra so that the bad debt will not just remain in the protocol.

At a point if the crash is so bad, the user's DYAD should also be confiscated, to maintain the stablecoin peg.

Assessed type

Context

#0 - c4-pre-sort

2024-04-28T17:25:12Z

JustDravee marked the issue as duplicate of #456

#1 - c4-pre-sort

2024-04-29T09:31:20Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-12T09:07:15Z

koolexcrypto marked the issue as unsatisfactory: Insufficient proof

#3 - cryptostaker2

2024-05-16T19:25:50Z

Hi, not sure of the decision for unsatisfactory.

This issue talks about lingering bad debt and how liquidators are not incentivize to clear debt with collateralRatio below <100%.

It is quite similar to #1097, but this issue talks about how the bad debt can be prevented by rewarding liquidators with the extra DYAD that the user has instead of talking about partial liquidation.

Thanks for reviewing the issue again!

#4 - koolexcrypto

2024-05-23T11:21:46Z

Hi @cryptostaker2

Thanks for the input.

Similar to #977 , will revisit later to evaluate all in one

#5 - c4-judge

2024-05-28T16:04:11Z

koolexcrypto marked the issue as duplicate of #977

#6 - c4-judge

2024-05-29T07:03:05Z

koolexcrypto marked the issue as satisfactory

Awards

4.8719 USDC - $4.87

Labels

2 (Med Risk)
satisfactory
duplicate-67

External Links

Judge has assessed an item in Issue #1211 as 2 risk. The relevant finding follows:

[L-03] Kerosine price can be manipulated by a coordinated attack, leading to undesirable liquidations The price of kerosene is deterministically calculated.

It takes (TVL - DYAD total supply) / Kerosene supply.

When minting DYAD, users need to have at least a 100% collateral ratio between exogeneous collateral (WSTETH/WETH).

uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount; if (getNonKeroseneValue(id) < newDyadMinted) revert NotEnoughExoCollat(); dyad.mint(id, to, amount);

Users then need to have at least 150% collateral ratio to prevent liquidation.

Kerosene can be used as extra collateral to mint more DYAD.

When minting 1000 DYAD, users can have

1500 USD worth of exogenous collateral (WETH) 1000 USD worth of exogeneous collateral and 500 USD worth of kerosene Users cannot have

1500 USD worth of kerosene With that being said, this is how the attack works.

Let’s say there are a bunch of people that has both exogenous collateral and kerosene as collateral.

The price of kerosene is (TVL - DYAD total supply) / Kerosene supply.

Assume TVL is 10M and DYAD total supply is ~4M. The whales can come together and purposely burn their DYAD to bring down the DYAD total supply, leaving some DYAD to liquidate a particular user that has a lot of kerosene as collateral.

Having a deterministic price may not be a good idea because it brings about many potential issues. Consider get the price through a TWAP on a liquidity pool instead.

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

#0 - c4-judge

2024-05-10T11:57:04Z

koolexcrypto marked the issue as duplicate of #67

#1 - c4-judge

2024-05-11T19:34:53Z

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