xanoutas/solidity-security-findings huggingface dataset:
assetPrice() in kerosine.unbounded.sol will always revert when new vault would be deployed
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
As mentioned in docs migration is going to happen from vaultManager to vaultManagerv2 , So during migration as mentioned in Deploy.V2.s.sol new WETH vault and new wSTETH vault will be deployed. And in that deploy script dyad is not deployed again. So considering that it can cause underflow in assetPrice and it will revert the function as in that numerator is calculated using above two values. 

Also in observation 6.4 is mentioned that while calulating tvl, kerosene  
vaults should not be consider. so only weth steth are remained.

## Proof of Concept

"uint numerator   = tvl - dyad.totalSupply();

here tvl will be totalvalue locked of vaults ( WETH and wSTETH ) which is calculated by balanceOf(address(vault)) , which will be very less when new vaults will be deployed of above 2 vaults. 

But dyad contract address will be old only, dyad.totalSupply() would be very large.

And new deployed vaults would not be able to cope up with dyad totalsupply, so  it will be less and underflow will occur and function will always revert, so we will not be to  get assetprice from vault.kerosine.unbounded.sol .

## Tools Used

Manual Review

## Recommended Mitigation Steps

So instead of getting value of number of token from balanceOf(addres(vault)) , it should be mantained via state variable (internal accounting), so that value can be maintained while migrating too.


## Assessed type

Under/Overflow
Newly deployed `Licenser` implementation will cause minting of `DYAD` tokens to fail.
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/script/deploy/Deploy.V2.s.sol#L39


# Vulnerability details

## ISSUE

In [`DeployV2.s.sol`](https://github.com/code-423n4/2024-04-dyad/blob/main/script/deploy/Deploy.V2.s.sol), a new version of the `Licenser` is [deployed](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/script/deploy/Deploy.V2.s.sol#L39). The [DYAD](https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Dyad.sol) contract (0x305B58c5F6B5b6606fb13edD11FbDD5e532d5A26) uses the [old Licenser implementation](https://etherscan.io/address/0x305b58c5f6b5b6606fb13edd11fbdd5e532d5a26#readContract#F5) (0xd8bA5e720Ddc7ccD24528b9BA3784708528d0B85). This Licenser contract was used during construction of the DYAD contract and this is what the DYAD contract will read from when performing checks (i.e. checking a licensed vault/vaultManager) before minting tokens. There is no function to update the Licenser in the DYAD contract.

If this new Licenser version is deployed and a vault manager is added to it, it will break calls to DYAD as the modifier [`licensedVaultManager()`](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Dyad.sol#L20-L23) will still check in the [old implementation](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/Dyad.sol#L9) (The DYAD contract cannot update the Licenser).

```solidity

    //@audit-info - Modifier checks if caller is licensed. Caller is `vaultManagerV2`
    modifier licensedVaultManager() {
        if (!licenser.isLicensed(msg.sender)) revert NotLicensed();
        _;
    }

```

This will cause the `licenser.isLicensed()` check (in the modifier) to return false since it cannot read from the new Licenser. This will lead to reverts in the `mint()` function of the DYAD contract.

## IMPACT

Users trying to mint `DYAD` tokens from `VaultManagerV2` will experience failure in the call due to the new vault manager not being licensed.

## POC

```solidity

// SPDX-License-Identifier: MIT
pragma solidity =0.8.17;

import "forge-std/console.sol";
import {StdUtils} from "lib/forge-std/src/StdUtils.sol";
import "forge-std/Test.sol";
import {IERC20} from "lib/forge-std/src/interfaces/IERC20.sol";

import {DeployV2, Contracts} from "../../script/deploy/Deploy.V2.s.sol";
import {Licenser} from "../../src/core/Licenser.sol";
import {Parameters} from "../../src/params/Parameters.sol";
import {IDNft} from "src/interfaces/IDNft.sol";

contract V2Test is StdUtils, Test, Parameters {
    Contracts contracts;

    IDNft dnft = IDNft(MAINNET_DNFT);

    address payable weth = payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address payable wsteth = payable(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);

    address[] users;

    address payable user1 = payable(makeAddr("user1"));
    address payable user2 = payable(makeAddr("user2"));
    address payable user3 =
A user could self-liquidate to earn LIQUIDATION REWARD
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An attacker can call the `liquidate` function to liquidate its own position. This is done in order to earn the LIQUIDATION REWARD. 
As there is no check present to prevent users from liquidating its position, so a user can liquidate its own position to gain the reward.

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

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended to add a check to prevent users from liquidating their own positions


## Assessed type

Other
`UnboundedKerosineVault`Contract Allows Flash Loan Attack Due to Lack of Withdrawal Checks
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The identified vulnerability in the `UnboundedKerosineVault` contract allows for the potential exploitation of a flash loan attack. This could result in the unauthorized withdrawal of assets from the vault, leading to financial loss or disruption to legitimate users and stakeholders. Without proper validation checks on withdrawal amounts, attackers can manipulate the vault balance and withdraw assets beyond its actual reserves.

## Proof of Concept
```
function withdraw(
    uint    id,
    address to,
    uint    amount
) 
    external 
    onlyVaultManager
{
    id2asset[id] -= amount;
    asset.safeTransfer(to, amount); 
    emit Withdraw(id, to, amount);
}
```
In the above code:

The withdraw function subtracts the specified amount from the id2asset mapping without sufficient validation on the withdrawal amount.
Assets are then transferred to the specified recipient using the safeTransfer function without proper checks on the vault's reserves.
To exploit this vulnerability, an attacker could borrow a large amount of assets through a flash loan, manipulate the vault balance, and withdraw assets beyond the vault's reserves within the same transaction.
## Tools Used
Manual 
## Recommended Mitigation Steps
```
contract UnboundedKerosineVault is KerosineVault {
    // Other contract variables and functions...

    function withdraw(
        uint    id,
        address to,
        uint    amount
    ) 
        external 
        onlyVaultManager
    {
        require(amount > 0, "Invalid withdrawal amount"); // Check if withdrawal amount is greater than zero
        require(id2asset[id] >= amount, "Insufficient funds"); // Check if the vault has sufficient funds for withdrawal
        
        id2asset[id] -= amount;
        asset.safeTransfer(to, amount); 
        emit Withdraw(id, to, amount);
    }

    // Other contract functions...
}
```
In this updated withdraw function:

I added a require statement to check if the withdrawal amount is greater than zero, ensuring that only valid withdrawal amounts are processed.
I added another require statement to verify if the vault has sufficient funds for the withdrawal. This prevents the withdrawal of assets beyond the vault's reserves.


## Assessed type

Other
Attacker can restricts the availability of `VaultManagerV2::withdraw` function for a particular `DNft` token.
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

An attacker can call `VaultManagerV2::deposit` function with a fake vault ( or deposit 1 wei in a vault ) for a particular `DNft` token every block which will lead to user not being able to withdraw due `DepositedInSameBlock` revert in `VaultManagerV2::withdraw` function.

The `VaultManagerV2::deposit` function is as follows ( [https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119](https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L119) ):

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

Here, there's no check for whether `msg.sender` is the owner of the `DNft` token or not. So, anyone can call `VaultManagerV2::deposit` function for a particular `DNft` token. Also, there's no check for whether the `vault` is a valid vault or not ( i.e vault is licensed or not ). So anyone can call this function with arbitrary amount as attacker's fake vault will have a dummy `asset::transferFrom` function which will always return `true`. And even if there's check for whether the `vault` is a valid vault or not, attacker can deposit 1 wei in the vault every block to restrict the availability of `VaultManagerV2::withdraw` function for a particular `DNft` token.

The `VaultManagerV2::withdraw` function is as follows ( [https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134](https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134) ):

```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);
    if (collatRatio(id) < MIN_COLLATERIZATION_RATIO)  revert CrTooLow();
    }
```

Here, the `if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();` check will revert if user has deposited in the same block. So, attacker can restrict the availability of `VaultManagerV2::withdraw` function for a particular `DNft` token by depositing in the same block every block.

The cost of attack is only gas fees for calling `VaultManagerV2::deposit` function every block.
`KEROSENE` assets are permanently stuck in `VaultManagerV2` contract.
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `VaultManagerV2::withdraw` function is as follows ( [https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134](https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/VaultManagerV2.sol#L134) ):

```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);
    if (collatRatio(id) < MIN_COLLATERIZATION_RATIO)  revert CrTooLow();
    }
```

`KerosineVault` such as `BoundedKerosineVault` and `UnboundedKerosineVault` doesn't have any function named `oracle` thus the `VaultManagerV2::withdraw` function will revert if user tries to withdraw any amount of `KEROSENE` assets from the vault.

## Proof of Concept

NOTE: There's another issue due to which `BoundedKerosineVault` and `UnboundedKerosineVault` can't be added to `VaultManagerV2` contract ( I've made another submission for that in QA report titled "Can't add `KerosineVault` through `VaultManagerV2::addKerosene` function" ). So, the following changes are done in `VaultManagerV2::addKerosene` function to create a working POC:

```diff
    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 (!vaultLicenser.isLicensed(vault))                   revert VaultNotLicensed();
    if (!vaultsKerosene[id].add(vault))                     revert VaultAlreadyAdded();
    emit Added(id, vault);
    }
```

NOTE: This is just a temporary change to create a working POC. Actual fix is different and more complex ( thus out of scope for this current submission ) and is mentioned in the QA report ( as mentioned in previous NOTE ).

Add this to a newly created test file:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity =0.8.17;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import {DeployBase, Contracts} from "../script/deploy/DeployBase.s.sol";
import {Parameters} from "../src/params/Parameters.sol";
import {DNft} from "../src/core/DNft.sol";
import {Dyad} from "../src/core/Dyad.sol";
import {Licenser} from "../src/core/Licenser.sol";
import {VaultManager} from "../src/core/VaultManager.sol";
import {Vault} from "../src/core/Va
Malicious users can prevent withdrawal by depositing small values for victim NFTs
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An attacker can deposit a low amount, or even zero, of assets to prevent another user withdrawal. Actually attackers can create vaults which are under their control. So they can deposit any amounts of any tokens through the `VaultManagerV2.deposit` with the result of  `idToBlockOfLastDeposit[id]` updating  of the victim for the gas cost only.


## Proof of Concept
There is only restriction `isValidDNft(id)` in the `deposit` function.
```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);
  }
```
So the `id` owner can't withdraw in the `block.number`
```solidity
  function withdraw(
    uint    id,
    address vault,
    uint    amount,
    address to
  )
    public
      isDNftOwner(id)
  {
    if (idToBlockOfLastDeposit[id] == block.number) revert DepositedInSameBlock();
```
https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L134-L143
## Tools Used
Manual review


## Recommended Mitigation Steps
Consider restriction of donating or/and implementing trusted addresses mapping, who can deposit on behalf of a particular `id` owner.


## Assessed type

MEV
Migration to VaultManagerV3 will harm users of the bounded vault
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-dyad/blob/main/src/core/Vault.kerosine.bounded.sol#L32


# Vulnerability details

## Impact
Users of the Bounded Kerosine Vault will lose their money.

## Proof of Concept
The protocol may decide to update VaultManagerV2 someday. Since the VaultManagerV2 is not upgradeable, they will need to deploy a new contract. 

Currently, they redeploy all used vaults and suggest users migrate the funds. However, users of the bounded vault will not be able to do so and lose funds. 
The current protocol design doesn't allow connecting old vaults a to new manager, at least because it will allow collateral to be accounted by both managers simultaneously. 

## Tools Used
Manual Analysis

## Recommended Mitigation Steps

Consider this case and experience from VaultManagerV1 and implement changes to VaultManagerV2. One of the solutions could be to freeze the old protocol (ManagerV2) without the possibility to deposit new collateral (only withdraw collateral and burn stablecoins).






## Assessed type

Upgradable
user can bypass flashloan protection mechanisms
Audit: 2024-04-dyad-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary

The protocol implements measures to prevent the use of flash loans for manipulating the exchange rate of the kerosene token. It does so by prohibiting the deposit and withdrawal of collateral in the same block. However, this protection can be circumvented using the liquidation function, allowing users to manipulate the exchange rate through strategic use of flash loans.

## Vulnerability Detail

The protocol's primary defense against flash loan attacks involves the `idToBlockOfLastDeposit[id]` mapping, which tracks the block number of the last deposit to a position:

```solidity=119
function deposit(
    uint    id,
    address vault,
    uint    amount
  ) 
    external 
      isValidDNft(id)
  {
    idToBlockOfLastDeposit[id] = block.number;
```

Withdrawals in the same block as the deposit are explicitly blocked to prevent the use of flash loans:

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

However, this measure only applies to the specific position ID. By transferring collateral between positions, users can evade this restriction. This is achievable through the liquidation function, which can move collateral from an undercollateralized position to another, thereby bypassing the block restriction.

#### overview of the attack:

1. Initial Setup: User establishes a position with substantial collateral but no minted DYAD. to be able to control the overcollaterization ratio and thus ba able to lower the price of kerosene.
2. because he hasn't minted any dyad , the overcollaterisation is high , and thus asset price of kerosine is high
3. User conducts a flash swap on the Uniswap WETH/kerosene pool and deposits the borrowed amount into position ID = 1.
4. user mints DYAD with the kerosene token just deposited at a just-sufficient collateralization ratio of 150%.
5. SelfThe user then intentionally drops the collateralization ratio to 149% by minting additional DYAD with position id = 2, setting up their own position for liquidation.
6. Collateral Transfer via Liquidation: The user liquidates their position, transferring 73.69% of the collateral to the secondary position which has no block restriction on withdrawals.
7. Withdrawal and Repetition: The user withdraws the transferred collateral and repeats the process, each cycle diminishing the collateral by 73.69% (for collateral of 149%) until it’s manageable to cover personally.
8. the repetition each time can be done with lowering the asset price of kerosine by more and more dyad with collateral already provided.

note that there is no fees paid to the protocol for minting dyad or liquidating so there is no loss for the attacker.
VaultManagerV2::liquidate() does not account for Kerosene vaults
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

## Impact

Liquidators will not receive the correct amount of assets when liquidating. Users will be disincentivized from liquidating positions backed by Kerosene. Malicious actors aware of this vulnerability can add Kerosene to their vault to decease the incentive of another user liquidating their position.

## Proof of Concept

`VaultManagerV2::liquidate()` iterates through each  `vault` in the `vaults` mapping:

```solidity
function liquidate(
	...
) 
	...
  {
		...
    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);
    }
	  ...
}
```

There is a separate mapping for Kerosene vaults, `vaultsKerosene`:

```solidity
mapping (uint => EnumerableSet.AddressSet) internal vaults; 
mapping (uint => EnumerableSet.AddressSet) internal vaultsKerosene;
```

This mapping is not accounted for during liquidations, therefore only the non Kerosene collateral will be moved to the liquidator and the target will keep the Kerosene.

## Tools Used

Manual review and Foundry.

## Recommended Mitigation Steps

Ensure `VaultManager::VaultManagerV2()` accounts for Kerosene vaults. [VaultManagerV2.sol#L205-L228](https://github.com/code-423n4/2024-04-dyad/blob/cd48c684a58158de444b24854ffd8f07d046c31b/src/core/VaultManagerV2.sol#L205-L228):

```solidity
	function liquidate(
	  uint id,
	  uint to
	) 
	  external 
	    isValidDNft(id)
	    isValidDNft(to)
	  {
	    ...
	    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);
	    }
	+   uint numberOfKeroseneVaults = vaultsKerosene[id].length();
	+   for (uint i = 0; i < numberOfKeroseneVaults; i++) {
	+       Vault keroseneVault = Vault(vaultsKerosene[id].at(i));
	+       uint kerosene = keroseneVault.id2asset(id).mulWadUp(liquidationAssetShare);
	+       keroseneVault.move(id, to, kerosene);
	+   }
	    ...
	}
```


## Assessed type

Error