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: 26/183
Findings: 4
Award: $446.95
π Selected for report: 0
π Solo Findings: 0
π Selected for report: MrPotatoMagic
Also found by: 0xtankr, ArmedGoose, Egis_Security, Giorgio, KYP, Maroutis, NentoR, OMEN, Sabit, Shubham, SpicyMeatball, T1MOH, d3e4, dimulski, peanuts
200.8376 USDC - $200.84
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.
#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
π Selected for report: SBSecurity
Also found by: AlexCzm, Emmanuel, Stefanov, carlitox477, carrotsmuggler, d3e4, grearlake, peanuts
223.9474 USDC - $223.95
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.
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).
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.
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
π Selected for report: Infect3d
Also found by: 0x486776, 0xAlix2, 0xleadwizard, 0xnilay, Abdessamed, ArmedGoose, Bauchibred, Bigsam, GalloDaSballo, HChang26, Myrault, OMEN, SBSecurity, T1MOH, ZanyBonzy, alix40, atoko, iamandreiski, jesjupyter, ke1caM, miaowu, peanuts, vahdrak1
17.2908 USDC - $17.29
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.
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.
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.
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
π 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
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.
#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