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: 67/183
Findings: 5
Award: $68.51
π 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/script/deploy/Deploy.V2.s.sol#L36-#L113
From run()
function, it can be seen that ethVault
and wstEth
are added to both kerosineManager
and vaultLicenser
:
kerosineManager.add(address(ethVault)); // <-- kerosineManager.add(address(wstEth)); // <-- . . . . . . vaultLicenser.add(address(ethVault)); // <-- vaultLicenser.add(address(wstEth)); // <-- vaultLicenser.add(address(unboundedKerosineVault));
In VaultManagerV2()
contract, to calculate valuable of dNFT, getTotalUsdValue()
function is used:
function getTotalUsdValue(uint id) public view returns (uint) { return getNonKeroseneValue(id) + getKeroseneValue(id); // <--- }
Function getNonKeroseneValue()
get value of token that added in vaultLicenser
contract:
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))) { // <--- usdValue = vault.getUsdValue(id); // <--- } totalUsdValue += usdValue; } return totalUsdValue; }
Similar with it, getKeroseneValue()
get value of token that added in keroseneManager
contract:
function getKeroseneValue(uint id) public view returns (uint) { uint totalUsdValue; uint numberOfVaults = vaultsKerosene[id].length(); for (uint i = 0; i < numberOfVaults; i++) { Vault vault = Vault(vaultsKerosene[id].at(i)); uint usdValue; if (keroseneManager.isLicensed(address(vault))) { // <--- usdValue = vault.getUsdValue(id); // <--- } totalUsdValue += usdValue; } return totalUsdValue; }
Since the WETH and WstETH added 2 time, it will be counted twice, which will return incorrect price
Price will be incorrectly returned,
Manual review
Only add WETH
and WstETH
vault once.
Other
#0 - c4-pre-sort
2024-04-29T07:00:05Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:52Z
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:21Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T11:43:30Z
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#L143
In withdraw()
function, there is a checking condition to restrict user from withdraw token in the same block to mitigrating flashloan attack:
if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();
But anyone can deposit to id's dNFT vault, and idToBlockOfLastDeposit
variable is updated in that time:
function deposit(uint id, address vault, uint amount) external isValidDNft(id) // <--- { idToBlockOfLastDeposit[id] = block.number; // <---
It will open attack scenario that attacker can front-running, deposit dust amount to victim's id in the same block that victim withdraw, make withdraw's transaction reverted.
Victim are not able to withdraw in that block
Manual review
Add reentrancy protection to mitigrating flashloan attack instead.
Other
#0 - c4-pre-sort
2024-04-27T11:33:20Z
JustDravee marked the issue as duplicate of #1103
#1 - c4-pre-sort
2024-04-27T11:45:52Z
JustDravee marked the issue as duplicate of #489
#2 - c4-pre-sort
2024-04-29T09:28:30Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-05T20:38:10Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T20:55:30Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T20:55:33Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:29:21Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:44:33Z
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: SBSecurity
Also found by: AlexCzm, Emmanuel, Stefanov, carlitox477, carrotsmuggler, d3e4, grearlake, peanuts
55.9869 USDC - $55.99
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L217-#L219
In liquidate() function, rate is calculated as below:
uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr);
With cr = 1.2e18
=> liquidationEquityShare = (1.2e18 - 1e18) * 0.2e18 / 1e18 = 0.06e18
=> liquidationAssetShare = (0.06e18 + 1e18) * 1e18 / 1.3e18 = 0.815e18
, which is not correct compare to document: 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
. From that example, liquidator should receive (1e18 + 0.2e18 * 20%) / 1.2 = 0.86667e18 = 86,67% of the dNFT's total token, and protocol keep 13,33% of the last one.
Wrong number of token is sent to liquidator, they receive less token than they should
Manual review
Update formula to below:
uint cappedCr = cr <= 1e18 ? 1e18 : cr; uint liquidationAssetShare = cr <= 1e18 ? 1e18 : (1e18 + (cr - 1e18).mulWadDown(LIQUIDATION_REWARD)).divWadDown(cappedCr) uint liquidationEquityShare = cr <= 1e18 ? 0 : 1e18 - liquidationAssetShare
Other
#0 - c4-pre-sort
2024-04-29T06:59:45Z
JustDravee marked the issue as insufficient quality report
#1 - c4-judge
2024-05-11T09:01:18Z
koolexcrypto marked the issue as duplicate of #75
#2 - c4-judge
2024-05-11T09:06:19Z
koolexcrypto marked the issue as partial-25
#3 - c4-judge
2024-05-11T12:11:51Z
koolexcrypto marked the issue as satisfactory
#4 - c4-judge
2024-05-11T12:12:01Z
koolexcrypto marked the issue as partial-25
#5 - c4-judge
2024-05-13T18:41:45Z
koolexcrypto changed the severity to 2 (Med Risk)
#6 - c4-judge
2024-05-28T19:22:10Z
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-#L228
Function liquidate()
is used to liquidate dNFT that collateral ratio is under 150%:
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); }
When user liquidate vault, they will receive 20% bonus of the target Noteβs backing colateral. But in the contract, there is no mechanism to restrict minimum amount to deposit/minting. Which will open the attack vector that user will mint very small amount of dyad token, and when that dNFT is underwater, there is no incentive for user to liquidate them because the cost to liquidating is higher than number of gas required to call liquidate()
function, which will lead to bad debt for protocol
dNFT that under-collateralized is no liquidated, which will lead to bad debt for protocol, and dyad token lost some backup token
Manual review
Add minimum number of token should be used to be able to mint dyad token.
Other
#0 - c4-pre-sort
2024-04-27T13:29:59Z
JustDravee marked the issue as duplicate of #1258
#1 - c4-pre-sort
2024-04-29T09:21:06Z
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:33:07Z
koolexcrypto marked the issue as grade-c
#4 - c4-judge
2024-05-22T14:26:06Z
This previously downgraded issue has been upgraded by koolexcrypto
#5 - c4-judge
2024-05-28T16:52:09Z
koolexcrypto marked the issue as satisfactory
#6 - c4-judge
2024-05-28T20:06:27Z
koolexcrypto marked the issue as duplicate of #175
π Selected for report: TheSavageTeddy
Also found by: 0x175, 0x486776, 0xnev, AamirMK, AlexCzm, ArmedGoose, BiasedMerc, CaeraDenoir, Egis_Security, Jorgect, KYP, MrPotatoMagic, PoeAudits, SBSecurity, SovaSlava, VAD37, adam-idarrha, alix40, carrotsmuggler, d_tony7470, dimulski, grearlake, josephdara, ljj, n0kto, okolicodes, sashik_eth, sil3th, turvy_fuzz
7.3512 USDC - $7.35
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L101 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L113
In both remove()
and removeKerosine()
function, there is a checking condition:
if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets();
But function deposit()
only check if id is valid or not, but not id is belong to caller:
function deposit(uint id, address vault, uint amount) external isValidDNft(id) // <---- { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
It will lead to scenario that attacker will front-running and deposit dust amount for victim's id, make them are not able to remove vault
Attacker can make user failed from removing vault of their DNFT
Manual review
Only allow owner to deposit to that dNFT
function deposit(uint id,address vault,uint amount) external - isValidDNft(id) + isDNftOwner(id)
Other
#0 - c4-pre-sort
2024-04-27T13:35:14Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:28:21Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:38:10Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:52:50Z
koolexcrypto marked the issue as nullified
#4 - c4-judge
2024-05-05T20:52:56Z
koolexcrypto marked the issue as not nullified
#5 - c4-judge
2024-05-05T20:53:03Z
koolexcrypto marked the issue as not a duplicate
#6 - c4-judge
2024-05-06T11:32:19Z
koolexcrypto marked the issue as duplicate of #118
#7 - c4-judge
2024-05-11T12:24:17Z
koolexcrypto marked the issue as satisfactory