xanoutas/solidity-security-findings huggingface dataset:
Kerosene vaults can break the protocol functionality
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L50-L61


# Vulnerability details

## Impact
Users can't use `kerosene` vaults in the usd total value calculation because it breaks the protocol core functionality due to the `UnboundedKerosineVault.assetPrice()` function invokes itself. But it can't cause asset locking since users are able to burn `dyad` and bypass the function while withdrawal.


## Proof of Concept
Users can use `kerosene` tokens to overcollaterize their position when `dyad` minting. It can be counted as part of total usd value in the `collatRatio` function.
```solidity
  function mintDyad(
    uint    id,
    uint    amount,
    address to
  )
    external
      isDNftOwner(id)
  {
    uint newDyadMinted = dyad.mintedDyad(address(this), id) + amount;
    if (getNonKeroseneValue(id) < newDyadMinted)     revert NotEnoughExoCollat();
    dyad.mint(id, to, amount);
    if (collatRatio(id) < MIN_COLLATERIZATION_RATIO) revert CrTooLow();
    emit MintDyad(id, amount, to);
  }
```
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L156-L169
The `kerosene` vault has to be added to the `KerosineManager` contract before users will be able to add it to the `vaultsKerosene` mapping.
```solidity
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);
  }
```
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L80-L91
But when a `kerosine` vault is added to the `vaultsKerosene` mapping for a dNFT the `getKeroseneValue` becomes DoSed due to self invoke in the `UnboundedKerosineVault.assetPrice()` function.
```solidity
  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
```
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L56-L61




## Tools Used
Manual review


## Recommended Mitigation Steps
Consider receiving collateral vaults from the `Licenser` contract instead of the `KerosineManager` contract. It also demands a similar with the `KerosineManager` implementation of the `Licenser` contract. The alternative recommendation is skipping the vault asset when it equals the `kerosine` token a
Re-Entrancy Vulnerability in withdraw()
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The important function `withdraw` can be used by an attacker to exploit a re-entrancy vulnerability. As Solidity is based on the concept of re-entrancy, it allows contracts to call other contracts which in turn call back into the first contract before the first function call has finished. This can lead to unintended behavior and malicious attacks.

The function `withdraw` in this case can be easily exploited due to its lack of state change after the external call. Since the function calls an external contract `Vault` through `_vault.withdraw` before it checks for the `collatRatio` condition, an attacker can carry out a re-entrancy attack.

This vulnerability can be exploited as follows:

- Attacker calls the `withdraw` function.
- The `withdraw` function interacts with the `Vault` contract.
- The `Vault` contract sends out assets to the attacker.
- The `Vault` contract re-enters the `withdraw` function of the initial contract before it reaches the state change, and withdraws more assets until the `collatRatio` condition is no longer met.

## Proof of Concept
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134-L153

```solidity
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); // External call
    if (collatRatio(id) < MIN_COLLATERIZATION_RATIO)  revert CrTooLow(); // State change after the external call
}
```

## Tools Used

Manual review

## Recommended Mitigation Steps

To prevent re-entrancy vulnerabilities, it is recommended to adhere to the pattern known as "checks-effects-interactions". Ensure that function's state is first updated before any external contracts are interacted with.

In this case, the `withdraw` function can be made secure by first running the state check (`collatRatio` in this case), and then following this with the external call.

Another preventive measure is to use a re-entrancy guard that operates by locking the contract when a function is called, and doesn't unlock it back until the function is completely executed. This ensures that no state variables are changed before the execution of external interactions. Example of a re-entrancy guard:
```solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract VaultManagerV2 is IVaultManager, Initializable, ReentranceGuard {
...
```


## A
Kerosene price manipulation via collateral withdrawal
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L50-L68


# Vulnerability details

## Impact
Kerosene token price can be manipulated via relatively small asset withdrawal to push particular positions under `MIN_COLLATERIZATION_RATIO` for their liquidation.


## Proof of Concept
A malicious liquidator (dNFT owner) can keep in controlled vault assets without minting `dyad` token. The usd value of these assets can consist of a sufficient percentage of the difference between non kerosene usd value and `dyad` total supply.
```solidity
  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();
      uint denominator = kerosineDenominator.denominator();
      return numerator * 1e8 / denominator;
  }
```
In case of these assets instant withdrawal the `kerosene` price will be decreased by the same percent. For the `boundedKerosineVault` the decrease will be doubled.
The current `dyad.totalSupply()` is approx 622,967.4 DYAD (https://etherscan.io/token/0x305b58c5f6b5b6606fb13edd11fbdd5e532d5a26#readContract).
The min overcollateralized rate is 1.5. The non kerosene usd value except for the attacker assets can be 1,000,000.0 USD. So the difference is approx 400,000.0 USD. Assume the attacker keeps 200,000.0 USD in assets. So to push the `kerosene` price to 1% it is enough to withdraw 6,000.0 USD in asset equivalents.
The attacker can choose users with relatively low collateralization ratio and sufficient amount of `kerosene` token amounts, push the `kerosene` price down and liquidate victims positions.
## Tools Used
Manual review


## Recommended Mitigation Steps
Consider tracking time weighted average `kerosene` price for the position health estimation.


## Assessed type

Other
Attacker Can Trigger Mass Liquidations by Manipulating Kerosene Price
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.kerosine.unbounded.sol#L50-L69


# Vulnerability details

## Summary

An attacker can use the WETH/Kerosene pool on Uniswap to obtain large quantities of Kerosene through a flashswap. By depositing this Kerosene into the keroseneVault and minting substantial amounts of DYAD, the attacker can artificially lower the asset price of Kerosene. This price manipulation can result in the liquidation of positions collateralized fully or partially with Kerosene, allowing the attacker to profit from liquidation rewards. and users erroneously liquidated.

## Vulnerability Detail

The protocol calculates the price of Kerosene based on the total collateralization within the system, as shown in the `UnboundedKerosineVault`:

```solidity=50
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();
      uint denominator = kerosineDenominator.denominator();
      return numerator * 1e8 / denominator;
  }
```

The Kerosene token price is sensitive to the system's overall collateralization. By inflating the supply of DYAD without increasing the total value locked (TVL), an attacker can lower the Kerosene price, making positions with Kerosene collateral vulnerable to liquidation.
this can be done by providing collateral as kerosene and minting dyad with it.

## Proof Of Concept

#### Assumptions

- The WETH/KEROSENE pool price reflects the UnboundedKerosineVault asset price due to arbitrage activities.

#### Attack Path

1. the attacker takes a flashswap of kerosine tokens from the uniswap pool
2. deposits it into a position as collateral
3. mints dyad with this collateral
4. this will lower the asset price of kerosene tokens
5. anybody using kerosene as part or full of their position may get liquidated leading to mass liquidations 
6. the attacker runs away with the reward uses the bypass of flash loan protection to pay back the flashswap.
7. the users get mass liquidated erroneously

## Impact

This strategy can cause widespread liquidations, eroding trust in the protocol and destabilizing its economic model. It also presents a significant financial risk to all DYAD holders as their assets could be unfairly liquidated.

## Tools Used

Manual Review

## Recommended Mitigation Steps

implement protections against flash loan bypass by updating the `idToBlockOfLastDeposit` when there is movement of collateral between tokens, in `liquidate`:
```diff=205
function liquid
Withdrawal might revert due to underflow
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`VaultManagerV2::withdraw()` might revert due to underflow, causing confusing in the system.

## Proof of Concept

When calling `VaultManagerV2::withdraw()` with a particular `amount`, [`Vault::withdraw()` will be triggered](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L151).


If `amount` > `id2asset[id]`, [the call will revert due to underflow](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Vault.sol#L61).

## Tools Used
Manual review.

## Recommended Mitigation Steps
Update `Vault::withdraw()`:

```
function withdraw(
    uint256 id,
    address to,
    uint256 amount
) external onlyVaultManager {
+   id2asset[id] >= amount? id2asset[id] -= amount : 0;
    asset.safeTransfer(to, amount);
    emit Withdraw(id, to, amount);
}
```

Or better yet create a custom error for it.














## Assessed type

Under/Overflow
Checks using an incorrect mapping to verify Kerosene vaults leads to the DOSing of core functionality
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

Checks in `addKerosene()` and `getKeroseneValue()`, which should verify whether a vault has been licensed or not, will always revert

## Vulnerability Details

Checks in two different functions verify if a kerosene vault has been licensed by querying `keroseneManager::isLicensed()`.

However `keroseneManager` is the wrong contract to check because the vaults licensed in `keroseneManager` must be exogenous collateral vaults only as they are used in the TVL calculations in `UnboundedKerosineVault::assetPrice()`.

Therefore, `keroseneManager::isLicensed()` will always return false where the input is a Kerosene vault.

This is reflected in the deploy script `Deploy.V2.s.sol`, where only exogenous collateral vaults are licensed in `kerosineManager` while all vaults users can interact with, endogenous and exogenous, are licensed in `vaultLicenser`.

```solidity
    // SOME CODE

    // @audit : exogenous collateral vaults contributing to TVL
    kerosineManager.add(address(ethVault));
    kerosineManager.add(address(wstEth));

    // SOME CODE

    // @audit : all collateral vaults where users can deposit
    vaultLicenser.add(address(ethVault));
    vaultLicenser.add(address(wstEth));
    vaultLicenser.add(address(unboundedKerosineVault));
    // vaultLicenser.add(address(boundedKerosineVault));

    // SOME CODE
```

### `addKerosene()`

The line marked below in `addKerosene()` incorrectly checks if the provided vault has been licensed in `keroseneManager`. This prevents the addition of kerosene vaults by users to the `vaultsKerosene` mapping against their NFT id.

```solidity
  function addKerosene(
      uint    id,
      address vault
  )
    external
      isDNftOwner(id)
  {
    // SOME CODE

>>> if (!keroseneManager.isLicensed(vault))                 revert VaultNotLicensed();

    // SOME CODE
  }
```

### `getKeroseneValue()`

The line marked below in `getKeroseneValue()` incorrectly checks if the provided kerosene vault has been licensed in `keroseneManager`. This means only the usd value of the exogenous collateral associated to an NFT id will be calculated where the value of the endogenous collateral is what is actually required.

```solidity
  function getKeroseneValue(
    uint id
  )
    public
    view
    returns (uint) {
      // SOME CODE

>>>     if (keroseneManager.isLicensed(address(vault))) {
          usdValue = vault.getUsdValue(id);
        }

      // SOME CODE
  }
```

## Impact

User activity with Kerosene vaults is completely blocked because users can't add kerosene vaults to deposit into.
Users rewards in the form of Kerosene will not be usable in the protocol; essentially becoming worthless.
Only exogenous collateral
Missing Ownership Check in setKeroseneManager() Function
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `setKeroseneManager()` function in VaultManagerV2 contract does not have any permission checks to ensure that only the rightful owner or an authorized user can change the KeroseneManager. This means that any user can call this function, potentially allowing them to hijack the KeroseneManager.

## Proof of Concept

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

```solidity
function setKeroseneManager(KerosineManager _keroseneManager) external initializer {
  keroseneManager = _keroseneManager;
}
```

## Tools Used

## Recommended Mitigation Steps

I recommend adding a permission check to the function to ensure that only an authorized user can manipulate the KeroseneManager. A possible modification can be done as follows:

```solidity
modifier onlyOwner() {
    require(msg.sender == owner, "Caller is not the owner");
    _;
}

function setKeroseneManager(KerosineManager _keroseneManager) public onlyOwner {
  keroseneManager = _keroseneManager;
}
```
In the above modification, the `onlyOwner` modifier ensures that the function can only be called by the address that deployed the contract. You may need to declare and initialize an `owner` variable to store the contract creator's address during deployment.



## Assessed type

Access Control
No check for licensed vaults in the deposit()
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In the VaultManagerV2 contract, the deposit function is used to deposit collateral into the vault of the assets but there is a failure to check whether the vaults are licensed or not. This can thereby lead to loss of collateral to malicious users as users can deposit collateral into any vault instead of a licensed vault.

## Proof of Concept
```solidity
     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);
  }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Ensure you check if the vault to which deposits will  be made is licensed .


## Assessed type

Invalid Validation
Everyone can deposit into Note, but only the owner can withdraw
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Every user can deposit into a note, but only the owner of the note can withdraw. Leaving the deposited users with a loss of funds or on owner's mercy.

## Proof of Concept
```solidity
// POC that malicious nft owner can steal other users assets from vault
    // the following test case will pass because everyone is allowed to deposit to a Note
    // but only owner can withdraw from it. This will cause the depositors to rely on the mercy of the owner
    // If they want to withdraw their collateral
    function testPOCNonOwnerOfAnNftCantWithdrawHisCollateral() public {
        uint256 amountForDeposit = 0.5 ether;
        vm.prank(USER1);
        vaultManager.add(USER1_NFT, address(wethVault));

        vm.startPrank(USER2);
        weth.approve(address(vaultManager), 1 ether);

        vaultManager.deposit(USER1_NFT, address(wethVault), amountForDeposit);

        vm.expectRevert(IVaultManager.NotOwner.selector);
        vaultManager.withdraw(USER1_NFT, address(wethVault), amountForDeposit, USER2);
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Use `isDNftOwner` modifier instead of `isValidDNft`

```solidity
function deposit(
        uint id,
        address vault,
        uint amount
    ) external isDNftOwner(id){
// other code
}
```



## Assessed type

Token-Transfer
Remove() function in VaultManagerV2 can be denied by front running with small/micro deposit
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManager.sol#L70


# Vulnerability details

## Impact
The remove() function in the VaultManagerV2 checks for the asset in the particular vault to be not greater than zero, before removing the vault. This can be misused by a malicious actor to front run this transaction with a call to the deposit() function to the same id and vault with a small minimum amount to block the user from removing the vault. This could have higher impact if the user desires to remove one vault and add another one, as the number of vaults is restricted.

## Proof of Concept
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManager.sol#L63-L73

deposit function can be called by anyone 
https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManager.sol#L75-L86

## Tools Used
Manual Review

## Recommended Mitigation Steps
1.deposit function can be modified to accept atleast a minimum value
2. remove function can send the excess assets if any, back to the dNFT onwer while the vault is removed, instead of checking for the value to be zero


## Assessed type

Invalid Validation
VaultManagerV2::getNonKeroseneValue` return 0 it affects `VaultManagerV2::withdraw` sufficient collateralization check
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

**Description:**

If `VaultManagerV2::getNonKeroseneValue` return 0 it affects `VaultManagerV2::withdraw` sufficient collateralization check

**Impact:**
Can cause issues when checking if vault has enough collateral to withdraw

**Proof of Concept:**
If vault in `VaultManagerV2::getNonKeroseneValue` is not licensed it skips the `usdValue = vault.getUsdValue(id);` update and therefore returns 0 when calculating `totalUsdValue += usdValue`

When checking if the vault maintains sufficient collateralization in `VaultManagerV2::withdraw` it checks `0 - value < dyadMinted` therefore returning a negative value which can cause problems with checking if vault has enough collateralization

**Recommended Mitigation:**

Do a require statement to make sure `VaultManagerV2::getNonKeroseneValue` doesn't return 0 value.



## Assessed type

Other
Deposits into unlicensed vaults
Audit: 2024-04-dyad-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Deposits into unlicensed vaults

## Impact
A user can deposit collateral to a vault that is not supported anymore/ or that was never licensed.
This causes direct loss of funds to the user as they cannot withdraw funds from a vault that is delicensed or was previously not licensed. 
Although this can be categorized as a user error, the existence of the ``remove()`` basically depicts that a vaults license can be remove.
However, a user adding collateral on the smart contract directly to a previoult licensed vault will not revert.
## Proof of Concept
```solidity
  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);
    //@audit prevent deposit into unlicensed vaults
  }

```

## Tools Used
Manual Review, JosephDara


## Recommended Mitigation Steps

Prevent deposits into vaults not licensed in the protocol. And allow deposits from delicensed vaults


## Assessed type

Other