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: 101/183
Findings: 4
Award: $11.32
🌟 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
The function getNonKeroseneValue()
should only calculate the USD value of exogenous collateral but an incorrect check means instead it calculates the USD value of all collateral, exogenous and endogenous
getNonKeroseneValue()
should calculate the USD value of assets stored in non-kerosene vaults, i.e. exogenous collateral vaults, for a given NFT id
.
However, the line marked below in getKeroseneValue()
incorrectly checks if the vault has been licensed in vaultLicenser
and if true adds it's usd value to the calculation but vaults licensed in vaultLicenser
are both exogenous and endogenous.
This means the USD value of both endogenous and exogenous collateral associated to an NFT id
will be calculated where only the value of the exdogenous collateral is what is required.
function getNonKeroseneValue( uint id ) public view returns (uint) { // SOME CODE >>> if (vaultLicenser.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); console.log("usdValue Non Kero: ", usdValue); } // SOME CODE }
We can see in the deploy script Deploy.V2.s.sol
, both exogenous and endogenous vaults will be licensed in vaultLicenser
while only exogenous collateral vaults are licensed in kerosineManager
.
// SOME CODE // @audit : exogenous collateral vaults contributing to TVL kerosineManager.add(address(ethVault)); kerosineManager.add(address(wstEth)); // SOME CODE // @audit : all collateral vaults where users can deposit vaultLicenser.add(address(ethVault)); vaultLicenser.add(address(wstEth)); vaultLicenser.add(address(unboundedKerosineVault)); // vaultLicenser.add(address(boundedKerosineVault)); // SOME CODE
User's non kerosene vault's USD value will be inflated by including kerosene vaults and user's overall collateral ratio will be inflated by double counting kerosene vaults. User's with kerosene deposited in a kerosene vault will be able to mint Dyad for less exogenous collateral than should be possible and therefore Dyad will be backed by less collateral than it should which can lead to it's depegging.
Manual Review Foundry Testing
Update the check to only loop through the exogenous vaults licensed in keroseneManager
:
function getNonKeroseneValue( uint id ) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaults[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaults[id].at(i)); uint usdValue; - if (vaultLicenser.isLicensed(address(vault))) { + if (keroseneManager.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } totalUsdValue += usdValue; } return totalUsdValue; }
Error
#0 - c4-pre-sort
2024-04-29T08:16:48Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:55Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:46:20Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-29T11:20:25Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T11:41:41Z
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
A user can be prevented from calling withdraw()
and redeemDyad()
by malicious user making a deposit of any amount on their behalf
When a deposit is made on behalf of an NFT id in deposit()
, the current block.number
is stored against that id in idToBlockOfLastDeposit[id]
.
This stored value is then used in a check in withdraw()
, see below, to prevent the depositing and withdrawing of collateral within the same block.
A malicious actor can use this check to block withdrawls of any collateral associated to an NFT id by front-running the transaction in the same block with a deposit of any amount on behalf of that id.
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { >>> idToBlockOfLastDeposit[id] = block.number; // SOME CODE } function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { >>> if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); // SOME CODE }
withdraw()
on his NFT id 134
134
in the same blockDepositedInSameBlock()
errorMalicious user can DOS users indefinitely from calling withdraw()
or redeemDyad()
by consistently front running the transaction in the same block with a deposit on behalf of the victim's NFT id.
This affects the availability of core protocol functionality; costing the victim gas as well as locking their funds up.
Manual Review Foundry Testing
Change the deposit()
modifier from isValidDNft(id)
to isDNftOwner(id)
so that deposits can not be made on another user's behalf:
function deposit( uint id, address vault, uint amount ) external - isValidDNft(id) + isDNftOwner(id) { // SOME CODE }
DoS
#0 - c4-pre-sort
2024-04-27T11:24:15Z
JustDravee marked the issue as duplicate of #1103
#1 - c4-pre-sort
2024-04-27T11:51:50Z
JustDravee marked the issue as duplicate of #489
#2 - c4-pre-sort
2024-04-29T09:28:34Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-05T20:38:07Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:08:55Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:09:01Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:30:04Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:45:08Z
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: 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
Liquidators have no incentive to liquidate users who hold a proportion of their collateral in Kerosene which increases likelihood of bad debt accumulating in the protocol
The Collateral Ratio is used to measure whether the health of a user's position and whether they can be liquidated or not.
It is based on the USD value of a user's holdings of both Exogenous (e.g wEth, wsEth) & Endogenous (e.g. kerosene) collateral and if a user has a ratio less than 1.5e18
they can be liquidated.
The amount of collateral a liquidator receives for liquidating is also derived from this measure.
However users are only paid out from the liquidatee's Exogenous collateral, i.e. vaults stored against a user's NFT id in VaultManagerV2::vaults
, as indicated in the function below, and not on their Kerosene
collareral.
This means that the larger the proportion of kerosene making up a potential liquidatee's collateral, the smaller the payout becomes for the liqudiator; making liquidations of users with kerosene less likely.
function liquidate( uint id, uint to ) external isValidDNft(id) isValidDNft(to) { // @audit : cr is based on USD value of Exo and Endo collateral >>> 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++) { // @audit : only exogenous vaults pay out >>> 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); }
$50_000
worth of Kerosene100 wEth
deposited which at $1000
is worth $100_000
100_000 Dyad
for a Collateral Ratio of 150%
100% is Exogenous
collateral & 50% is Endogenous
Collateral Ratio is 140%
and she can be liquidatedCollateral Ratio of 140%
liquidator only stands to gain 77% of her Exogenous collateral
for burning their 100_000 Dyad
which would equal 77 wETH
100_000 Dyad
for 100 wEth or the wstEth equivalent; depending on what they hold.150 wEth
, they would be paid out 150 * .77 = 115.5
;Liquidation function calculations for cr = 1.4e18
used above:
cr = 1.4e18
=> cappedCr = 1.4e18
=> liquidationEquityShare = (1.4e18 - 1e18) / 0.2e18 / 1e18 => 0.08e18
=> liquidationAssetShare = (0.08e18 + 1e18) / 1.4e18 => 0.77e18
=> collateral = 100e18 * 0.77e18 / 1e18 => 77e18 wEth
Liquidation function calculations for cr = 1.1e18
used above:
cr = 1.1e18
=> cappedCr = 1.1e18
=> liquidationEquityShare = (1.1e18 - 1e18) / 0.2e18 / 1e18 => 0.02e18
=> liquidationAssetShare = (0.02e18 + 1e18) / 1.1e18 => 0.93e18
=> collateral = 100e18 * 0.82e18 / 1e18 => 93e18 wEth
Increase of bad debt in the system and depeg of Dyad.
Manual Review Foundry Testing
Given that users are not entitled to Endogenous collateral; payout amounts should take into account the proportions of Exogenous to Endogenous collateral that the liquidatee is holding and adjust payout accordingly.
Error
#0 - c4-pre-sort
2024-04-28T10:18:36Z
JustDravee marked the issue as duplicate of #128
#1 - c4-pre-sort
2024-04-29T09:07:41Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T19:41:39Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:36:08Z
koolexcrypto changed the severity to 3 (High Risk)
🌟 Selected for report: carrotsmuggler
Also found by: 0xAlix2, 0xSecuri, 0xleadwizard, 0xlemon, 0xtankr, 3th, Aamir, Abdessamed, Bauchibred, Circolors, Egis_Security, Evo, Hueber, Mahmud, SBSecurity, TheSavageTeddy, TheSchnilch, Tychai0s, alix40, bbl4de, btk, d3e4, ducanh2706, falconhoof, itsabinashb, ke1caM, lian886, n4nika, oakcobalt, pontifex, sashik_eth, steadyman, tchkvsky, zhuying
3.7207 USDC - $3.72
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L80-L91 https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L269-L286
Checks in addKerosene()
and getKeroseneValue()
, which should verify whether a vault has been licensed or not, will always revert
Checks in two different functions verify if a kerosene vault has been licensed by querying keroseneManager::isLicensed()
.
However keroseneManager
is the wrong contract to check because the vaults licensed in keroseneManager
must be exogenous collateral vaults only as they are used in the TVL calculations in UnboundedKerosineVault::assetPrice()
.
Therefore, keroseneManager::isLicensed()
will always return false where the input is a Kerosene vault.
This is reflected in the deploy script Deploy.V2.s.sol
, where only exogenous collateral vaults are licensed in kerosineManager
while all vaults users can interact with, endogenous and exogenous, are licensed in vaultLicenser
.
// SOME CODE // @audit : exogenous collateral vaults contributing to TVL kerosineManager.add(address(ethVault)); kerosineManager.add(address(wstEth)); // SOME CODE // @audit : all collateral vaults where users can deposit vaultLicenser.add(address(ethVault)); vaultLicenser.add(address(wstEth)); vaultLicenser.add(address(unboundedKerosineVault)); // vaultLicenser.add(address(boundedKerosineVault)); // SOME CODE
addKerosene()
The line marked below in addKerosene()
incorrectly checks if the provided vault has been licensed in keroseneManager
. This prevents the addition of kerosene vaults by users to the vaultsKerosene
mapping against their NFT id.
function addKerosene( uint id, address vault ) external isDNftOwner(id) { // SOME CODE >>> if (!keroseneManager.isLicensed(vault)) revert VaultNotLicensed(); // SOME CODE }
getKeroseneValue()
The line marked below in getKeroseneValue()
incorrectly checks if the provided kerosene vault has been licensed in keroseneManager
. This means only the usd value of the exogenous collateral associated to an NFT id will be calculated where the value of the endogenous collateral is what is actually required.
function getKeroseneValue( uint id ) public view returns (uint) { // SOME CODE >>> if (keroseneManager.isLicensed(address(vault))) { usdValue = vault.getUsdValue(id); } // SOME CODE }
User activity with Kerosene vaults is completely blocked because users can't add kerosene vaults to deposit into. Users rewards in the form of Kerosene will not be usable in the protocol; essentially becoming worthless. Only exogenous collateral can be accepted and priced by the protocol, breaking core protocol functionality which should allow users to deposit Kerosene as endogenous collateral.
Manual Review Foundry Testing
Because the vaults licenced in keroseneManager
should only be exogenous and the vaults licenced in vaultLicenser
should contain both exogenous + endogenous; the easiest thing to do would be to create a new data structure to store only endogenous vaults.
DoS
#0 - c4-pre-sort
2024-04-29T05:15:50Z
JustDravee marked the issue as duplicate of #70
#1 - c4-pre-sort
2024-04-29T12:03:21Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:01:15Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:36:27Z
koolexcrypto changed the severity to 2 (Med Risk)