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: 33/183
Findings: 7
Award: $332.07
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Maroutis
Also found by: 0x486776, 0xShitgem, 0xabhay, 0xleadwizard, 0xlemon, 0xnilay, 0xtankr, 3docSec, AM, Aamir, Abdessamed, Al-Qa-qa, AlexCzm, Circolors, CodeWasp, Daniel526, Egis_Security, Emmanuel, Giorgio, Honour, Hueber, Infect3d, Krace, KupiaSec, LeoGold, Limbooo, PoeAudits, SBSecurity, SpicyMeatball, T1MOH, The-Seraphs, TheSavageTeddy, TheSchnilch, Topmark, VAD37, ZanyBonzy, adam-idarrha, bhilare_, btk, carlitox477, cinderblock, dimulski, falconhoof, grearlake, gumgumzum, iamandreiski, itsabinashb, josephdara, ke1caM, kennedy1030, ljj, n0kto, n4nika, neocrao, oakcobalt, petro_1912, pontifex, poslednaya, shaflow2, shikhar229169, web3km, ych18, zhaojohnson, zigtur
0.2831 USDC - $0.28
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Deploy.V2.s.sol#L95
In the DeployV2.run() function, the unboundedKerosineVault
is added into the vaultLicenser
, so the malicious DNftOwner
can add the unboundedKerosineVault
into vaults[id]
.
If the unboundedKerosineVault
is added into the vaults[id]
, the getNonKeroseneValue()
function is miscalculated.
The malicious DNftOwner
can mint more dyad
tokens and withdraw vault tokens than actual amount.
Also, the liquidation cannot be done fairly.
Consequently, dyad
token cannot stay stablity.
DNftOwner
can mint more dyad
tokens and withdraw vault tokens than actual amount.dyad
token cannot stay stablility.In the DeployV2.run() function, unboundedKerosineVault
is added into vaultLicenser
from L95.
https://github.com/code-423n4/2024-04-dyad/blob/main/script/deploy/Deploy.V2.s.sol#L95
File: script\deploy\Deploy.V2.s.sol 95: vaultLicenser.add(address(unboundedKerosineVault));
So the malicious DNftOwner
can add unboundedKerosineVault
into vaults[id]
from L76.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L75~L76
File: src\core\VaultManagerV2.sol 75: if (!vaultLicenser.isLicensed(vault)) revert VaultNotLicensed(); 76: if (!vaults[id].add(vault)) revert VaultAlreadyAdded();
If malicious DNftOwner
adds unboundedKerosineVault
into vaults[id]
, the getNonKeroseneValue()
function calculates USD value of real non-kerosene vaults and unboundedKerosineVault
.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L250~L267
File: src\core\VaultManagerV2.sol 250: function getNonKeroseneValue( 251: uint id 252: ) 253: public 254: view 255: returns (uint) { 256: uint totalUsdValue; 257: uint numberOfVaults = vaults[id].length(); 258: for (uint i = 0; i < numberOfVaults; i++) { 259: Vault vault = Vault(vaults[id].at(i)); 260: uint usdValue; 261: if (vaultLicenser.isLicensed(address(vault))) { 262: usdValue = vault.getUsdValue(id); 263: } 264: totalUsdValue += usdValue; 265: } 266: return totalUsdValue; 267: }
Thus, the return value of the getNonKeroseneValue()
function is greater than real non-kerosene value.
This makes the collatRatio
greater than actual value.
As a result, the malicious DNftOwner
can mint more dyad
tokens and withdraw vault tokens than actual amount.
Also, the liquidation cannot be done fairly.
Consequently, the dyad token cannot stay stable.
Manual Review
In the DeployV2.run()
function, kerosine vaults must not be added into vaultLicenser
.
Other
#0 - c4-pre-sort
2024-04-29T05:23:17Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:10Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:24Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-29T11:19:58Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T14:03:55Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: MrPotatoMagic
Also found by: 0x175, 0x486776, 0x77, 0xAkira, 0xAsen, 0xDemon, 0xabhay, 0xblack_bird, 0xlemon, 0xloscar01, 0xtankr, 3docSec, 4rdiii, Abdessamed, AlexCzm, Angry_Mustache_Man, BiasedMerc, Circolors, Cryptor, DMoore, DPS, DedOhWale, Dinesh11G, Dots, GalloDaSballo, Giorgio, Honour, Imp, Jorgect, Krace, KupiaSec, Mrxstrange, NentoR, Pechenite, PoeAudits, Ryonen, SBSecurity, Sabit, T1MOH, TheFabled, TheSavageTeddy, Tychai0s, VAD37, Vasquez, WildSniper, ZanyBonzy, adam-idarrha, alix40, asui, blutorque, btk, c0pp3rscr3w3r, caglankaan, carrotsmuggler, d_tony7470, dimulski, dinkras, djxploit, falconhoof, forgebyola, grearlake, imare, itsabinashb, josephdara, kartik_giri_47538, ke1caM, kennedy1030, koo, lionking927, ljj, niser93, pep7siup, poslednaya, ptsanev, sashik_eth, shaflow2, steadyman, turvy_fuzz, ubl4nk, valentin_s2304, web3km, xyz, y4y, zhaojohnson, zigtur
0.0234 USDC - $0.02
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
Attackers can delay any withdrawing and vault removing by depositing 1 wei.
At L143 of VaultManagerV2.withdraw()
, there is a block.number
check.
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { 143 if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); uint value = amount * _vault.assetPrice() * 1e18 / 10**_vault.oracle().decimals() / 10**_vault.asset().decimals(); if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
When someone tries withdrawing from his id
, the attacker can set idToBlockOfLastDeposit[id]
to the current block number by front-depositing 1 wei to that id
, results in revert the withdrawal.
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { 127 idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
This DOS attack can be conducted to redeemDyad(), remove()
and removeKerosene()
.
Manual review
Deposit should be allowed to only the owner of DNFT token.
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) + isDNftOwner(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
DoS
#0 - c4-pre-sort
2024-04-27T11:31:05Z
JustDravee marked the issue as duplicate of #1103
#1 - c4-pre-sort
2024-04-27T11:45:48Z
JustDravee marked the issue as duplicate of #489
#2 - c4-pre-sort
2024-04-29T09:29:33Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-05T20:38:11Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T20:46:29Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T20:46:46Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:29:16Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:51:11Z
koolexcrypto marked the issue as satisfactory
#8 - c4-judge
2024-05-13T18:34:30Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: 0xAlix2
Also found by: 0xfox, 0xlemon, 0xnev, 3docSec, Aamir, Abdessamed, Dudex_2004, Egis_Security, Evo, FastChecker, Honour, Jorgect, KupiaSec, Limbooo, MrPotatoMagic, SpicyMeatball, TheSchnilch, alix40, bhilare_, favelanky, forgebyola, ke1caM, kennedy1030, koo, oakcobalt, petro_1912, shikhar229169
32.4128 USDC - $32.41
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
Users can't withdraw kerosene and withdrawing from unadded vaults could be unfairly reverted.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); uint value = amount * _vault.assetPrice() * 1e18 148 / 10**_vault.oracle().decimals() / 10**_vault.asset().decimals(); 150 if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
If the vault
is a kerosene vault, VaultManagerV2.withdraw()
will be reverted at L148 because there is no oracle
in kerosene vaults.
And, if the vault
was not added to id
, value
is not included to getNonKeroseneValue(id)
, so getNonKeroseneValue(id)
should not be subtracted by value
at L150. So L150 could result in a reversal of the transaction.
Manual review
The collateral check should be improved.
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); - uint value = amount * _vault.assetPrice() - * 1e18 - / 10**_vault.oracle().decimals() - / 10**_vault.asset().decimals(); + uint value; + if (vaults[id].contains(vault) && vaultLicenser.isLicensed(address(vault))) + { + value = amount * _vault.assetPrice() + * 1e18 + / 10**_vault.oracle().decimals() + / 10**_vault.asset().decimals(); + } if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
Other
#0 - c4-pre-sort
2024-04-26T21:40:08Z
JustDravee marked the issue as duplicate of #397
#1 - JustDravee
2024-04-26T21:40:09Z
Double dup on 397 and 1048. Should these be grouped together? Duping with 397 for now
#2 - c4-pre-sort
2024-04-29T08:48:16Z
JustDravee marked the issue as sufficient quality report
#3 - koolexcrypto
2024-05-08T09:26:49Z
Will leave it as dup for 397
Advice for the warden, please report this as two issues next time. Generally speaking, Two different root causes => two separate issue
#4 - c4-judge
2024-05-11T19:22:53Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: shikhar229169
Also found by: 0x486776, 0xSecuri, 0xfox, 3th, Circolors, Honour, KupiaSec, Maroutis, Sancybars, Stormreckson, Strausses, ke1caM, kennedy1030
283.3687 USDC - $283.37
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205-L228
Anyone can prevent liquidation.
There is only collateral ratio check at L214 in VaultManagerV2.liquidate()
.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { uint cr = collatRatio(id); 214 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); }
But, anyone who is facing liquidation can deposit a large amount of collateral which results in the increasing of the kerosene price. Consequently, he can increase collateral ratio increase enough to avoid liquidation. Then, even though there is a less collateral than dyad in id
, that id
avoids liquidation.
Manual review
The liquidation check should be improved.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { uint cr = collatRatio(id); - if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); + if (cr >= MIN_COLLATERIZATION_RATIO && getNonKeroseneValue(id) >= dyad.mintedDyad(address(this), id)) 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); }
Other
#0 - c4-pre-sort
2024-04-28T10:22:23Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:03:31Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-08T09:40:22Z
koolexcrypto marked the issue as not a duplicate
#3 - c4-judge
2024-05-08T09:40:34Z
koolexcrypto marked the issue as duplicate of #338
#4 - c4-judge
2024-05-11T12:20:17Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: 0xAlix2
Also found by: 0x486776, 0xabhay, 0xlucky, 0xtankr, Abdessamed, Circolors, CodeWasp, DarkTower, Egis_Security, Giorgio, Infect3d, Krace, KupiaSec, Limbooo, Maroutis, NentoR, Ryonen, SpicyMeatball, T1MOH, TheFabled, TheSavageTeddy, TheSchnilch, VAD37, XDZIBECX, btk, carrotsmuggler, cu5t0mpeo, dimulski, gumgumzum, iamandreiski, imare, itsabinashb, ke1caM, kennedy1030, lian886, n4nika, oakcobalt, sashik_eth, shaflow2, steadyman, web3km, windhustler, zhaojohnson
3.8221 USDC - $3.82
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L50
The asset price of unbounded kerosene vaults is calculated using the degree of DYAD’s overcollateralization, tvl - dyad.totalSupply()
.
If undercollateralization occurs, the subtract operation is reverted.
This makes the calling of assetPrice()
function reverted, so liquidation cannot be done.
In case of undercollateralization like sudden price drop of collateral assets, liquidation cannot be done.
In the Vault.kerosine.unbounded.assetPrice() function, the asset price of unbounded kerosene vaults is calculated using the degree of DYAD’s overcollateralization from L65.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L65
File: src\core\Vault.kerosine.unbounded.sol 58: for (uint i = 0; i < numberOfVaults; i++) { 59: Vault vault = Vault(vaults[i]); 60: tvl += vault.asset().balanceOf(address(vault)) 61: * vault.assetPrice() * 1e18 62: / (10**vault.asset().decimals()) 63: / (10**vault.oracle().decimals()); 64: } 65: uint numerator = tvl - dyad.totalSupply(); 66: uint denominator = kerosineDenominator.denominator();
Here, tvl
is the total USD value of kerosineManager
's vaults.
If the asset price of kerosineManager
's vaults drops, tvl
becomes smaller than dyad.totalSupply()
.
This leads the subtract operation to be reverted from L65.
Finally, in the case of undercollateralization, nobody can not call Vault.kerosine.unbounded.assetPrice()
function.
However anyone must be allowed to liquidate that DNft.
In the VaultManagerV2, calculating collatRatio
needs to call Vault.kerosine.unbounded.assetPrice()
function.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L230
File: src\core\VaultManagerV2.sol 230: function collatRatio( 231: uint id 232: ) 233: public 234: view 235: returns (uint) { 236: uint _dyad = dyad.mintedDyad(address(this), id); 237: if (_dyad == 0) return type(uint).max; 238: return getTotalUsdValue(id).divWadDown(_dyad); 239: } 240: 241: function getTotalUsdValue( 242: uint id 243: ) 244: public 245: view 246: returns (uint) { 247: return getNonKeroseneValue(id) + getKeroseneValue(id); 248: } [...] 269: function getKeroseneValue( 270: uint id 271: ) 272: public 273: view 274: returns (uint) { 275: uint totalUsdValue; 276: uint numberOfVaults = vaultsKerosene[id].length(); 277: for (uint i = 0; i < numberOfVaults; i++) { 278: Vault vault = Vault(vaultsKerosene[id].at(i)); 279: uint usdValue; 280: if (keroseneManager.isLicensed(address(vault))) { 281: usdValue = vault.getUsdValue(id); 282: } 283: totalUsdValue += usdValue; 284: } 285: return totalUsdValue; 286: }
Calculating the collatRatio()
function from L213 is always reverted.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205
File: src\core\VaultManagerV2.sol 205: function liquidate( 206: uint id, 207: uint to 208: ) 209: external 210: isValidDNft(id) 211: isValidDNft(to) 212: { 213: uint cr = collatRatio(id); [...] 228: }
Consequently, liquidation cannot be done.
Manual Review
File: src\core\Vault.kerosine.unbounded.sol function assetPrice() public view override returns (uint) { uint tvl; address[] memory vaults = kerosineManager.getVaults(); uint numberOfVaults = vaults.length; for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[i]); tvl += vault.asset().balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10**vault.asset().decimals()) / (10**vault.oracle().decimals()); } + if (tvl < dyad.totalSupply()) + return 0; uint numerator = tvl - dyad.totalSupply(); uint denominator = kerosineDenominator.denominator(); return numerator * 1e8 / denominator; }
Other
#0 - c4-pre-sort
2024-04-28T19:29:35Z
JustDravee marked the issue as duplicate of #224
#1 - c4-pre-sort
2024-04-29T09:04:37Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-08T08:31:59Z
koolexcrypto marked the issue as duplicate of #308
#3 - c4-judge
2024-05-11T20:09:36Z
koolexcrypto marked the issue as satisfactory
🌟 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
In 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 colateral.(check here) However, since Kerosene
used as collateral in the target Note
are not moved to the liquidator, it discourages liquidation and the liqudator migth receive less incentives even when the target Note
's collateral ratio is bigger than 1
.
When a liqudator liquidates the target Note
, he burns a quantity of DYAD
equal to the target Note’s DYAD
minted balance and in return receives an equal value plus a 20% bonus of the target Note's backing collateral.
But in the VaultManagerV2.liquidate() function, only the Non-Kerosine
part of collateral is directed at L221.
File: 2024-04-dyad\src\core\VaultManagerV2.sol 205: function liquidate( 206: uint id, 207: uint to 208: ) 209: external 210: isValidDNft(id) 211: isValidDNft(to) 212: { 213: uint cr = collatRatio(id); 214: if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); 215: dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id)); 216: 217: uint cappedCr = cr < 1e18 ? 1e18 : cr; 218: uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); 219: uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr); 220: 221: uint numberOfVaults = vaults[id].length(); // @audit-info Only non-Kerosene collaterals are considered. 222: for (uint i = 0; i < numberOfVaults; i++) { 223: Vault vault = Vault(vaults[id].at(i)); 224: uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); 225: vault.move(id, to, collateral); 226: } 227: emit Liquidate(id, msg.sender, to); 228: }
As Kerosene
is used as the collaterals, in the liquidation process, the asset of KeroseneVaults
should be moved to the liquidator.
Assume Alice is going to liquidate Bob's Note
.
Let Bob's Note
is as follows.
Vault | id2Asset(1e18) | Price(1e18) | UsdValue(1e18) | Sum(1e18) |
---|---|---|---|---|
wETH | 10 | 3111.12 | 31111.2 | |
wstETH | 10 | 3116.94 | 31169.4 | 62280.6 |
Kerosine.unbound | 500 | 10 | 5000 | |
Kerosin.bound | 0 | 20 | 0 | 5000 |
67280.6 | ||||
mintedDYAD | 60000 | 1 | 60000 | |
In this case, his collateral ratio is larger than 100 % but smaller than 150%.
At L213-L219, the variables cr
, cappedCr
, liquidationEquityShare
, liquidationAssetShare
are calculated as below.
cr
= 62280.6 / 6000 = 1.1213 * 1e18
cappedCr
= max(1, cr) = 1.1213 * 1e18
liquidationEquityShare
= (cappedCr - 1e18).mulWadDown(0.2e18) = 0.0242 * 1e18
liquidationAssetShare
= (liquidationEquityShare + 1e18).divWadDown(cappedCr) = 0.9134 * 1e18
Then, the UsdValue
of insentive of liquidator is calculated like below.
Vault | Moved Asset(1e18) | Price(1e18) | UsdValue(1e18) | Sum(1e18) |
---|---|---|---|---|
wETH | 9.1343 | 3111.12 | 28417.90413 | |
wstETH | 9.1343 | 3116.94 | 28471.06576 | |
Kerosine.unbound | 0 | 10 | 0 | |
Kerosin.bound | 0 | 20 | 0 | |
56888.96989 | ||||
To sum up, the liquidator burned DYAD
of $60000
but received only $56888.97
as incentive. Although Alice liquidated Bob's Note
, she received less incentive than her waste. This is not fair for liquidators who contributes to enhance the security of the protocol.
The reason behind this is that the protocol allowed to move Non-Kerosene
assets only in liquidation process although Kerosene
was used to calculate Bob's collateral ratio.
If the protocol allows to move Kerosene
, it will be fair. It is revealed below.
Vault | Moved Asset(1e18) | Price(1e18) | UsdValue(1e18) | Sum(1e18) |
---|---|---|---|---|
wETH | 9.1343 | 3111.12 | 28417.90413 | |
wstETH | 9.1343 | 3116.94 | 28471.06576 | |
Kerosine.unbound | 456.715 | 10 | 4567.15 | |
Kerosin.bound | 0 | 20 | 0 | |
61456.12 | ||||
But still, if collateral ratio drops below 1
, the liqudator always receive less incentives than the amount she wasted to liquidate.
Manual Review
In VaultManagerV2.liquidate(), assets of vaultsKerosene
should be moved to the liquidator.
File: 2024-04-dyad\src\core\VaultManagerV2.sol 205: function liquidate( 206: uint id, 207: uint to 208: ) 209: external 210: isValidDNft(id) 211: isValidDNft(to) 212: { 213: uint cr = collatRatio(id); 214: if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh(); 215: dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id)); 216: 217: uint cappedCr = cr < 1e18 ? 1e18 : cr; 218: uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); 219: uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr); 220: 221: uint numberOfVaults = vaults[id].length(); 222: for (uint i = 0; i < numberOfVaults; i++) { 223: Vault vault = Vault(vaults[id].at(i)); 224: uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); 225: vault.move(id, to, collateral); 226: } + uint numberOfVaults = vaultsKerosene[id].length(); + for (uint i = 0; i < numberOfVaults; i++) { + Vault vault = Vault(vaultsKerosene[id].at(i)); + uint collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare); + vault.move(id, to, collateral); + } 227: emit Liquidate(id, msg.sender, to); 228: }
Other
#0 - c4-pre-sort
2024-04-28T10:22:10Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:03:32Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:39:44Z
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
A malicious attacker can manipulate the price of Kerosene
by depositing or transfering large amount of Non-Kerosene
tokens to the vaults
.
Non-Kerosene
tokens to the vaults
increases the tvl
of the protocol and also the price of Kerosene
. It makes it difficult to liquidate the Note
with KerosenVaults
due to the rise of collateral ratio.Kerosene
decreases and he can place the Notes
with KeroseneVaults
in the liquidation state. The attacker liquidates the Notes
and can earn yields from the liquidation.The price of Kerosene
is calculated from the Vault.kerosine.unbounded.assetPrice() function,
File: 2024-04-dyad\src\core\Vault.kerosine.unbounded.sol 50: function assetPrice() 51: public 52: view 53: override 54: returns (uint) { 55: uint tvl; 56: address[] memory vaults = kerosineManager.getVaults(); 57: uint numberOfVaults = vaults.length; 58: for (uint i = 0; i < numberOfVaults; i++) { 59: Vault vault = Vault(vaults[i]); 60: tvl += vault.asset().balanceOf(address(vault)) //@audit-info avoid to use balanceOf(address(vault)) 61: * vault.assetPrice() * 1e18 62: / (10**vault.asset().decimals()) 63: / (10**vault.oracle().decimals()); 64: } 65: uint numerator = tvl - dyad.totalSupply(); 66: uint denominator = kerosineDenominator.denominator(); 67: return numerator * 1e8 / denominator; 68: }
First, it calculates tvl
of the protocol. Here, it uses the balance of vault
at L60.
An attacker can manipulate the balance of vault
by transfering large amount of Non-Kerosine
tokens to it directly via ERC20.Safetransfer
or depositing via VaultManagerV2.deposit()
File: 2024-04-dyad\src\core\VaultManagerV2.sol 119: function deposit( 120: uint id, 121: address vault, 122: uint amount 123: ) 124: external 125: isValidDNft(id) 126: { 127: idToBlockOfLastDeposit[id] = block.number; 128: Vault _vault = Vault(vault); 129: _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); 130: _vault.deposit(id, amount); 131: }
It makes it difficult to liquidate the Note
with KeroseneVaults
. When tvl
decreases due to price fluctuation, he withdraws deposited assets and place Note
with KeroseneVaults
in the liqudation state. Then he can earns yields by liquidating all available Notes
Alice is going to manipulate the price of Kerosene
and wants to earn yields.
Note
by calling DNft.mintNft().Dyad
from the Note
, as a result, her Note
's collateral ratio is uint.max
and she can withdraw her Note
's collateral assets at anytime as much as she want.ether
of WETH
to ethVault
via the minted Note
. This increases the balance of WETH
of ethVault
, tvl
of the protocol. Consequencely, the price of Kerosene
increases.Notes
with KeroseneVaults
balance as their collateral ratios rise.WETH
decreases, she decided to withdraw all deposited WETH
. It results the decrease of the price of Kerosene
, the decrease of the collateral ratio of the Notes
with KeroseneVaults
. Then, the Notes
with KeroseneVaults
are placed in the liquidation state.Notes
and earn yields from liquidation.Manual Review
We recommend the to avoid using KeroseneVaults
in the Notes
.
And we also recommed to use internal accounting for collaterals.
Other
#0 - c4-pre-sort
2024-04-28T05:50:09Z
JustDravee marked the issue as duplicate of #67
#1 - c4-pre-sort
2024-04-29T09:06:12Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T09:59:12Z
koolexcrypto changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-05-08T11:50:03Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-08T12:02:35Z
koolexcrypto marked the issue as satisfactory