xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #1011 [1714930072204]
Audit: 2024-04-dyad-findings
Severity: HIGH

Judge has assessed an item in Issue #1011 as 2 risk. The relevant finding follows:

 [L-04] VaultManagerV2::withdraw can be blocked at no cost
Issue Description:

Since everyone with minted dNft can call VaultManagerV2::deposit, front run protection which was added to the V2 of the VaultManager can be utilized by a griefer to block any call to withdraw function with 0 tokens transfer (if asset supports it, otherwise 1 wei is enough). When someone calls deposit function, idToBlockOfLastDeposit[id] is set to the current block.number . After that idToBlockOfLastDeposit[id] is used in the withdraw function. Now anyone with a minted dNft can call deposit with 0 wei on behalf of a user, frontrunning his withdraw execution. The result will be a DoS for the withdrawer of the duration (or even more if the liquidator is financially incentivised to do it) of a block.

VaultManagerV2.sol#L127

  function deposit(
    uint    id,
    address vault,
    uint    amount
  ) 
    external 
      isValidDNft(id)
  {
    idToBlockOfLastDeposit[id] = block.number; //ISSUE set to current
    Vault _vault = Vault(vault);
    _vault.asset().safeTransferFrom(msg.sender, address(vault), amount);
    _vault.deposit(id, amount);
  }
VaultManagerV2.sol#L143

function withdraw(
    uint    id,
    address vault,
    uint    amount,
    address to
  ) 
    public
      isDNftOwner(id)
  {
    if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();//ISSUE revert on current
    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(); 
  }
Recommendation:

Check if the deposit occured at the same block.number as the withdraw is from the withdrawer itself, otherwise revert.
Upgraded Q -> 2 from #1018 [1714929888280]
Audit: 2024-04-dyad-findings
Severity: HIGH

Judge has assessed an item in Issue #1018 as 2 risk. The relevant finding follows:

 [L-1] assetPrice() queries from Kerosene price to determine price will fail when TVL is 0
In the case TVL is 0, the function will revert as it attempts a subtraction from 0 operation which runs into an underflow. https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L65

function assetPrice() 
    public 
    view 
    override
    returns (uint) {
      uint tvl;
      address[] memory vaults = kerosineManager.getVaults();
      uint numberOfVaults = vaults.length;
      for (uint i = 0; i < numberOfVaults; i++) {
        Vault vault = Vault(vaults[i]);
        tvl += vault.asset().balanceOf(address(vault)) 
                * vault.assetPrice() * 1e18
                / (10**vault.asset().decimals()) 
                / (10**vault.oracle().decimals());
      }
@>    uint numerator   = tvl - dyad.totalSupply(); // @audit revert if TVL resolves to 0
      uint denominator = kerosineDenominator.denominator();
      return numerator * 1e8 / denominator;
  }
Return 0 or implement a case where if the TVL is 0, return no price:

function assetPrice() 
    public 
    view 
    override
    returns (uint) {
      uint tvl;
      address[] memory vaults = kerosineManager.getVaults();
      uint numberOfVaults = vaults.length;
      for (uint i = 0; i < numberOfVaults; i++) {
        Vault vault = Vault(vaults[i]);
        tvl += vault.asset().balanceOf(address(vault)) 
                * vault.assetPrice() * 1e18
                / (10**vault.asset().decimals()) 
                / (10**vault.oracle().decimals());
      }
+     if (tvl == 0) { return 0;}
      uint numerator   = tvl - dyad.totalSupply();
      uint denominator = kerosineDenominator.denominator();
      return numerator * 1e8 / denominator;
  }
Upgraded Q -> 2 from #1082 [1714929308125]
Audit: 2024-04-dyad-findings
Severity: HIGH

Judge has assessed an item in Issue #1082 as 2 risk. The relevant finding follows:

 [L-02] Remove Vault can be susceptible to dos
in the function remove

  function remove(
      uint   id,
      address vault
  ) 
    external
      isDNftOwner(id)
  {
    if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets();
    if (!vaults[id].remove(vault))     revert VaultNotAdded();
    emit Removed(id, vault);
  }


if (Vault(vault).id2asset(id) > 0) revert VaultHasAssets(); will cause revertion if a attacker deposits to the vault making the user not be able remove the vault, resulting in dos.
Upgraded Q -> 2 from #1082 [1714929278595]
Audit: 2024-04-dyad-findings
Severity: HIGH

Judge has assessed an item in Issue #1082 as 2 risk. The relevant finding follows:

 [L-01] Deposit() can deposit to any vault might result in dos
in function Deposittakes a vault address & id as argument but doesnt perform any owner checks,possibly allowing anyone to deposit to any id corresponding vault.


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

However,the vulnerability arises when a legitimate user wants to withdraw their funds


function withdraw(
    uint    id,
    address vault,
    uint    amount,
    address to
  ) 
    public
      isDNftOwner(id)
  {
    if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();

In the withdraw function theres a check where it (idToBlockOfLastDeposit[id] == block.number) a malicious user can deposit a small amount to the legit user whos trying to withdraw their funds,reverting the transaction. Consider adding a grace period or msg sender check.
A malicious user can block all withdrawals and manipulate the Kerosine price
Audit: 2024-04-dyad-findings
Severity: HIGH

## Lines of Code 
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L127
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L143

## IMPACT
DOS of all withdrawals in a given block thus manipulating the Kerosine price in certain conditions

## Proof of concept
An attacker can block all withdrawals txs in a given block by frontrunning the users who want to withdraw and call **deposit()**
with 0 wei and the victim's nft Ids. The flash loan prevention mechanism forbids the users to deposit and withdraw at the same block.
This will lead to reverts when the victims try to withdraw.
		
Since the Kerosine price is calculated with the tvl of all exocollateral assets in mind, in case of huge sum withdrawals from users,
the attacker can block their withdrawals -> manipulate the Kerosine price and take advantage of the price.

Redeeming Dyad functionality could also be blocked for the current block with this approach. 

This is all possible because anyone can deposit with someone else's NFT with the combination of the flash loan prevention mechanism of DYAD protocol.

## Tools used
Manual review

## Reccomended Mitigation Steps
Add a isDNftOwner(id) modifier in VaultManagerV2.deposit() so only the NFT owner can deposit

## Assessed Type
DOS
Users cannot withdraw Kerosine via the `withdraw()`
Audit: 2024-04-dyad-findings
Severity: HIGH

## Lines of Code:
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L146-L150
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L15

## Impact
All deposited Kerosine tokens inside Vault.kerosine.unbounded.sol get stuck and cannot be withdrawn by anyone

## Proof of Concept
When a user tries to withdraw his Kerosine tokens from a Kerosine vault he calls the VaultManagerV2.withdraw().
When calculating the value(L 146) the _vault.oracle() field is used for the calculation.
However this field does not exist inside Vault.kerosine.unbounded.sol and an EVM error is thrown.
As a result all withdraw txs of Kerosine tokens are reverted. This leads to frozen funds
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L146-L150

POC: https://gist.github.com/dinkras/8d4508b34c9f33487a2e2027011a6ac6

## Tools Used
Manual review

## Recommended Mitigation Steps
Implement withdraw specific logic for the Kerosin vault in VaultManagerV2.sol

## Assessed Type
DOS
`VaultManagerV2::liquidate()` - The liquidate() function has multiple problems that need to be addressed.
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

HIGH: `VaultManagerV2::liquidate()` - The liquidate() function has multiple problems that need to be addressed.

Please see descriptions under "Recommendations" section.

Function in question:
```solidity
  function liquidate(
    uint id,
    uint to
  ) 
    external
      isValidDNft(id)
      isValidDNft(to) /// @audit-issue this doesnt make sense and should be removed
    {
      uint cr = collatRatio(id);
      if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh();
      dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id));

      uint cappedCr               = cr < 1e18 ? 1e18 : cr;
      uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD);
      uint liquidationAssetShare  = (liquidationEquityShare + 1e18).divWadDown(cappedCr);

      uint numberOfVaults = vaults[id].length();
      for (uint i = 0; i < numberOfVaults; i++) {
          Vault vault      = Vault(vaults[id].at(i));
          uint  collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare);
          vault.move(id, to, collateral);
      }
      emit Liquidate(id, msg.sender, to);
  }
```
Impact:
- Likelihood: High, Impact: High, Severity: High
- Function may revert every time it's called, and IF it doesn't revert, then the caller(msg.sender) will be the one who gets liquidated, which isn't correct.

Recommendations:

- It will revert during execution because of the `isValidDNft(to)` modifier that needs to be removed. The parameter/argument for this modifier is the actual id of the dNft, but the `to` is the numerical address representation of the "receiver". Not valid.

- The line `dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id));` is incorrectly using `msg.sender` as the address whose tokens will be burned. This is incorrect, as the caller isnt, shouldn't and cannot be the token owner being liquidated. Therefore the `msg.sender` should be changed to: `dNft.ownerOf(id)` which is a correct representation of the dNft id owner, for which the tokens should be burned.

- Should add the following check at beginning of function: `require(dNft.ownerOf(id) != msg.sender, "owner cannot liquidate themselves");` to ensure dNft id owner cannot successfully call this function and liquidate themselves.

- add another check after above require check: `require(address(to) != address(0), "to cannot be zero address");`



## Assessed type

Invalid Validation
Missing 'from' Parameter in burnDyad Function restricts burning of minted tokens in certain cases
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Lack of a 'from' parameter in the burnDyad function restricts 
an user from burning tokens from the address they minted to.

Impact: high
likelihood: mid

## Proof of Concept

1. VaultManager contract that allows users to mint Dyad tokens using the mintDyad function. 
This function accepts a `to` parameter, 
specifying the address to which the Dyad tokens should be minted.

2. User Alice calls the mintDyad function to mint Dyad tokens and 
specifies an address `0xBOB....` as the recipient (i.e., to parameter). 
that is not the same as msg.sender 

3. Later, Alice realises that the colleteral value is dropping 
and so wants to burn some Dyad to maintain a good health factor.

4. However, since the burnDyad function in the VaultManager contract does not include a `from` parameter, 
Alice cannot specify the address from which to burn the Dyad tokens.

5. Alice can only burn Dyad tokens from their own address (msg.sender). 
They cannot burn Dyad tokens from the address they minted to, 
even though they may want to manage their Dyad tokens across different addresses.

6. Alice gets liquidated as a result.

## Tools Used

Manual Analysis

## Recommended Mitigation Steps

Modify the burnDyad function in the VaultManager contract to include a from parameter. 
This parameter would allow users to specify the address from which they want to burn Dyad tokens, 
providing flexibility in managing Dyad tokens across different addresses.

Also implement the access control modifier to ensure that msg.sender owns the corresponding ID.


## Assessed type

Other
Malicious user can block other's withdrawals
Audit: 2024-04-dyad-findings
Severity: HIGH

# 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
`VaultManagerV2::withdraw` Checks if the last deposit to the DNft id was in the same block, if it was, then it reverts. A malicious user can call `VaultManagerV2::deposit` with any amount of asset to someone else's DNft id in the same block front-running the withdraw call, therefore making it impossible to withdraw.

## Proof of Concept
```  
  function withdraw(
    uint    id,
    address vault,
    uint    amount,
    address to
  ) 
    public
      isDNftOwner(id)
  {
    if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();
```
The `withdraw` function starts with a check, if the last deposit into the DNft id happened in the same block, if it did, the function reverts. This check is implemented against flash loan attacks, however, it can be used by a malicious user to block withdraw by front-running it.

```
  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);
  }
```
The `deposit` function only has an `isValidDNft(id)` modifier on it, not the `isDNftOwner(id)` modifier, so anybody can call it, as it only checks if the given id is valid.

```
  modifier isValidDNft(uint id) {
    if (dNft.ownerOf(id) == address(0))   revert InvalidDNft(); _;
  }
```
Then the `deposit` saves the `block.number` to the `idToBlockOfLastDeposit[id]`, so the `withdraw` function call will revert if it is called in the same block after the `deposit` function.
This means that anybody can deposit to a valid DNft id as little amount of asset as he wants, and the `withdraw` function will revert for the DNft id in the same block.
As there is no minimum amount, there is no need for substantial capital and this can be done for a long time, essentially blocking the withdraw function for the given id.


## Tools Used
Manual Review
## Recommended Mitigation Steps
- Modify the deposit function to include the `isDNftOwner(id)` modifier. This will ensure that only the owner of the DNft id can deposit assets into it, preventing unauthorized users from manipulating the idToBlockOfLastDeposit mapping.
- Introduce a minimum deposit amount requirement for the `deposit` function. This will prevent malicious actors from blocking withdrawals with negligible deposits.


## Assessed type

Token-Transfer
There is no incentive to liquidate users who have large holdings of Kerosene as collateral
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary

Liquidators have no incentive to liquidate users who hold a proportion of their collateral in Kerosene which increases likelihood of bad debt accumulating in the protocol

## Vulnerability Details

The Collateral Ratio is used to measure whether the health of a user's position and whether they can be liquidated or not.

It is based on the USD value of a user's holdings of both Exogenous (e.g wEth, wsEth) & Endogenous (e.g. kerosene) collateral and if a user has a ratio less than `1.5e18` they can be liquidated.

The amount of collateral a liquidator receives for liquidating is also derived from this measure.

However users are only paid out from the liquidatee's Exogenous collateral, i.e. vaults stored against a user's NFT id in `VaultManagerV2::vaults`, as indicated in the function below, and not on their `Kerosene` collareral.

This means that the larger the proportion of kerosene making up a potential liquidatee's collateral, the smaller the payout becomes for the liqudiator; making liquidations of users with kerosene less likely.



```
  function liquidate(
    uint id,
    uint to
  )
    external
      isValidDNft(id)
      isValidDNft(to)
    {
      // @audit : cr is based on USD value of Exo and Endo collateral
>>>   uint cr = collatRatio(id);
      if (cr >= MIN_COLLATERIZATION_RATIO) revert CrTooHigh();
      dyad.burn(id, msg.sender, dyad.mintedDyad(address(this), id));

      uint cappedCr               = cr < 1e18 ? 1e18 : cr;
      uint liquidationEquityShare = (cappedCr - 1e18).mulWadDown(LIQUIDATION_REWARD);
      uint liquidationAssetShare  = (liquidationEquityShare + 1e18).divWadDown(cappedCr);

      uint numberOfVaults = vaults[id].length();
      for (uint i = 0; i < numberOfVaults; i++) {
          // @audit : only exogenous vaults pay out
>>>       Vault vault      = Vault(vaults[id].at(i));
          uint  collateral = vault.id2asset(id).mulWadUp(liquidationAssetShare);
          vault.move(id, to, collateral);
      }
      emit Liquidate(id, msg.sender, to);
  }
```

## POC

- Mary has `$50_000` worth of Kerosene
- Mary has `100 wEth` deposited which at `$1000` is worth `$100_000`
- This allows Mary to mint `100_000 Dyad` for a Collateral Ratio of `150%`
  - Of her Collateral Ratio therefore; `100% is Exogenous` collateral & `50% is Endogenous`
- Then the price of the Endogenous collateral, Kerosene, drops due to holders burning Dyad, TVL reducing or Kero being distributed while the price of the Exogenous collateral (wEth) remains stable
- Mary's new `Collateral Ratio is 140%` and she can be liquidated
- Nobody will liquidate her though because:
  - They will receive none of her Endogenous collateral
  - For a `Collateral Ratio of 140%` liquidator only stands to gain `77% of her Exogenous collateral` for burning their `100_000 Dyad` which
kerosineDenominator in unboundedVault would create problem in future times
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.unbounded.sol#L50
https://github.com/code-423n4/2024-04-dyad/blob/main/src/staking/KerosineDenominator.sol#L17


# Vulnerability details

## Impact
- kerosineDenominator in unboundedVault would create problem in future times.
- As time passes the `kerosine.balanceOf(MAINNET_OWNER)` would linearly decrease thus impacting the denominator() in Kerosine Denominator.
- This would impact in increase of assetPrice() in unboundedVault as kerosineDenominator was in the denominator of the equation.
- Thereby increasing the price of kerosene very much.



## Tools Used
Manual Review

## Recommended Mitigation Steps
- Consider only Kerosene's total supply.


## Assessed type

Other
Self-Liquidation and Price Manipulation Vulnerability in VaultManagerV2.sol
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

### Impact
This vulnerability allows users to manipulate asset prices and liquidate themselves, potentially leading to unfair profit generation and destabilization of the asset pricing mechanism within the platform.

### Proof of Concept
In `VaultManagerV2.sol:205`, the function [liquidate](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205) permits users to liquidate their own positions. This can be exploited by a user who deposits a small amount into a kerosene vault and a larger amount into endogenous collateral vaults. Due to the calculation mechanism of kerosene vaults, which scales the price with the total value locked (TVL), this results in an inflated kerosene price. The user can then mint DYAD tokens proportional to the inflated USD value of their NFT and subsequently liquidate themselves to another NFT, effectively bypassing withdrawal restrictions.

```solidity
function testKerosoneManipulation() public {
        uint256 initialTVL = 500_000 ether;

        // Simulate legitimate user's deposits
        uint256 nftId = mintDNft(address(this));
        deposit(weth, nftId, address(wethVault), initialTVL);

        uint256 kerosinePriceBefore = unboundedKerosineVault.assetPrice();
        console.log("Kerosine Price Before: %e", kerosinePriceBefore);

        uint256 nftBob1 = mintDNft(BOB);
        uint256 nftBob2 = mintDNft(BOB);

        uint256 attackAmount = initialTVL / 10; // 10% of total TVL
        // For this attack to work, the attacker must have 26% of the amount he will request from flash loans
        weth.mint(BOB, attackAmount); // So here the attacker needs to have 26% of attackAmount in legitimate funds and 74% from a flash loan
        deal(address(kerosine), BOB, attackAmount / 150);

        vm.startPrank(BOB);
        vaultManager.add(nftBob1, address(wethVault));
        vaultManager.addKerosene(nftBob1, address(unboundedKerosineVault));

        weth.approve(address(vaultManager), attackAmount);
        kerosine.approve(address(vaultManager), attackAmount / 150);

        vaultManager.deposit(nftBob1, address(wethVault), attackAmount);
        vaultManager.deposit(nftBob1, address(unboundedKerosineVault), attackAmount / 150);

        uint256 kerosinePriceAfter = unboundedKerosineVault.assetPrice();
        // The price of kerosine is inflated right now
        console.log("Kerosine Price After: %e", kerosinePriceAfter);

        uint256 dyadToMint = vaultManager.getTotalUsdValue(nftBob1);
        vaultManager.mintDyad(nftBob1, dyadToMint, BOB);

        vaultManager.liquidate(nftBob1, nftBob2);
        vm.stopPrank();
    }
```

### Tools Used
- Manual Review

### Recommended Mitigation Steps
1. **Restrict Self-Liquidation:** Modify the [liquidate](https://github.com/code-423n4/20