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: 53/183
Findings: 3
Award: $236.12
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xAlix2
Also found by: 0x175, 0x486776, 0xnev, 3docSec, 3th, Aamir, Abdessamed, AlexCzm, Angry_Mustache_Man, Circolors, DedOhWale, Emmanuel, Giorgio, Honour, Jorgect, KupiaSec, Maroutis, Myrault, SBSecurity, Stefanov, T1MOH, VAD37, Vasquez, adam-idarrha, alix40, ducanh2706, falconhoof, iamandreiski, ke1caM, kennedy1030, koo, lian886, ljj, miaowu, pontifex, sashik_eth, shikhar229169, vahdrak1
7.3026 USDC - $7.30
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205
Liquidators are not fully rewarded
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); }
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); }
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
🌟 Selected for report: SBSecurity
Also found by: AlexCzm, Emmanuel, Stefanov, carlitox477, carrotsmuggler, d3e4, grearlake, peanuts
223.9474 USDC - $223.95
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205
Liquidator's reward is not fully paid
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); }
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); + }
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
🌟 Selected for report: dimulski
Also found by: 0xleadwizard, 0xlemon, Aamir, Al-Qa-qa, AvantGard, Bauchibred, Cryptor, DarkTower, Egis_Security, Giorgio, Maroutis, MrPotatoMagic, OMEN, Ocean_Sky, Ryonen, SBSecurity, Sabit, SpicyMeatball, Stefanov, T1MOH, Tigerfrake, WildSniper, atoko, bhilare_, darksnow, fandonov, grearlake, iamandreiski, igdbase, pontifex, web3km, xiao
4.8719 USDC - $4.87
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205
Users won't liquidate small positions because they will lose collateral instead of being rewarded. Protocol will accumulate bad debt
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); }
Manual review
Gather small percentages of collateral from the other liquidations to reward liquidators of positions that are insolvent
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