xanoutas/solidity-security-findings huggingface dataset:
Discrepancies in Token Wrapping and Unwrapping Functionalities
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSG.sol#L415-L443
https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L254-L276


# Vulnerability details

## Impact
In the `rOUSG.sol` contract, there exists functionality for wrapping and unwrapping tokens, which converts between `OUSG` and `rOUSG` tokens. However, there are discrepancies in the calculation methodologies between the wrap and unwrap functions. Additionally, there is a further inconsistency in the way wrapping is performed within the `mintRebasingOUSG` function of the `OUSGInstantManager` contract.

Hence, users receive different amounts of tokens for the same input depending on whether they are wrapping or unwrapping, leading to confusion and potential loss of value.

## Proof of Concept
1. In the `wrap` function, the calculation for `ousgSharesAmount` is straightforward. It takes the input `_OUSGAmount` and multiplies it by `OUSG_TO_ROUSG_SHARES_MULTIPLIER`. However, this calculation does not consider the current price of `OUSG`, which may lead to inconsistencies in the conversion ratio between `OUSG` and `rOUSG` tokens:

https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSG.sol#L415-L422

```solidity
  function wrap(uint256 _OUSGAmount) external whenNotPaused {
    require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens");
    uint256 ousgSharesAmount = _OUSGAmount * OUSG_TO_ROUSG_SHARES_MULTIPLIER;
    _mintShares(msg.sender, ousgSharesAmount);
    ousg.transferFrom(msg.sender, address(this), _OUSGAmount);
    emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
    emit TransferShares(address(0), msg.sender, ousgSharesAmount);
  }
```

2. In contrast to the `wrap` function, the `unwrap` function calls the `getSharesByROUSG` function to calculate `ousgSharesAmount` based on the input `_rOUSGAmount`. This function internally utilizes `getOUSGPrice` to determine the price of `OUSG`, thus accounting for fluctuations in the token price. Consequently, the unwrap function ensures that the correct amount of `OUSG` is returned to the user based on the current price:

https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSG.sol#L431-L443

```solidity
  function unwrap(uint256 _rOUSGAmount) external whenNotPaused {
    require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens");
    uint256 ousgSharesAmount = getSharesByROUSG(_rOUSGAmount);
    if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
      revert UnwrapTooSmall();
    _burnShares(msg.sender, ousgSharesAmount);
    ousg.transfer(
      msg.sender,
      ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    );
    emit Transfer(msg.sender, address(0), _rOUSGAmount);
    emit TransferShares(msg.sender, add
Manipulation Oracle Price Leads to Financial Discrepancy
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/rOUSG.sol#L378


# Vulnerability details

## Description 
In The getOUSGPrice function the issue is present in it and it’s directly influences the contract's financial calculations for minting and redeeming OUSG and it’s arise due to the contract's reliance on an external oracle for fetching OUSG prices without any present of  secondary validation or fallbacks


```solidity
function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
  }

```

## Impact

The vulnerability is can lead to financial discrepancies within the contract and this resulting in unfair advantages of financial gaine for malicious actors, ultimately affecting all stakeholders. Over time, consistent manipulation of oracle prices even within what is considered 'safe' by the contract's own sanity checks can result in substantial financial losses


## Proof of Concept
 
As scenario i fuzz with a scenario that the 
contract's behavior over 10,000 iterations with variable oracle prices. 
The oracle prices were slightly manipulated within acceptable bounds with scenario of oracle price inaccuracies. 
And as results it’s showcased a net gain in USDC received from redemptions compared to USDC spent on minting
(Total USDC Spent: 10,000,000, Total USDC Received: 10,031,547.76), and this  indicating a financial imbalance in the contract's operations over time
Here is the fuzz test 
```python 

import random
import numpy as np

def simulate_oracle_price(min_price=105, max_price=150, manipulation_factor=0.1):
    """
    Simulates oracle price with optional manipulation.
    Prices are normally distributed around a mean with a slight manipulation factor.
    """
    mean_price = (max_price + min_price) / 2
    manipulated_price = mean_price * (1 + random.uniform(-manipulation_factor, manipulation_factor))
    return max(min_price, min(max_price, manipulated_price))

def mint_ousg(usdc_amount, oracle_price):
    """
    Simplified minting logic based on oracle price.
    """
    return usdc_amount / oracle_price

def redeem_ousg(ousg_amount, oracle_price):
    """
    Simplified redemption logic based on oracle price.
    """
    return ousg_amount * oracle_price

def fuzz_test(iterations=10000, usdc_per_iteration=1000):
    """
    Fuzz test simulating oracle manipulation and its impact over multiple iterations.
    """
    total_usdc_spent = 0
    total_usdc_received = 0
    
    for _ in range(iterations):
        current_price = simulate_oracle_price()
        ousg_minted = mint_ousg(usdc_per_iteration, current_price)
        total_usdc_spent += usdc_per_iteration
        
        # Assume price manipulation occurs here
        manipulated_price = simulate_oracle_price()
        usdc_received = redeem_ousg(ousg_minted, manipulated_price)
        total_usdc_received += usdc_received
    
    print(f"Total USDC Spent: {tot
Guardian can execute arbitrary transaction via the multiexec function.
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L794
https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/rOUSGFactory.sol#L121


# Vulnerability details

## Impact
The function 'multiexcall' enables batch execution for the guardian role. The guardian cancan execute any malicious transactions to drain the funds from manager and factory

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use multi-sig to prevent the attack.


## Assessed type

call/delegatecall
`ousgInstantManager` assumes 1:1 `USDC`/`USD`
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L306-L323
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L399-L400
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L455


# Vulnerability details

## Impact
If USDC depegs `OUSG` can be bought at a discount or sold at a premium depending on which way the depeg is.

As the mints and redeems are instant this could be used for arbitrage, even though that seems to be mitigated by off chain agreements.

## Proof of Concept
`ousgInstantManager` uses [an oracle](https://etherscan.io/address/0x0502c5ae08e7cd64fe1aeda7d6e229413ecc6abe#readContract) to evaluate the price of `OUSG`, which in turn relies on the [`SHV/USD`](https://etherscan.io/address/0xc04611C43842220fd941515F86d1DDdB15F04e46) chainlink feed.

The issue here is that the `SHV/USD` chainlink oracle is denominated in `USD` but `ousgInstantManager` uses this as 1:1 with `USDC` in [`_mint`}(https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L307-L319):
```solidity
File: contracts/ousg/ousgInstantManager.sol

        // price is queried in USD
307:    uint256 ousgPrice = getOUSGPrice();
308:    ousgAmountOut = _getMintAmount(usdcAmountAfterFee, ousgPrice);
...
        // payment is taken in USDC
319:    usdc.transferFrom(msg.sender, usdcReceiver, usdcAmountAfterFee);
```
and [`_redeem`](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L399-L455):
```solidity
File: contracts/ousg/ousgInstantManager.sol

        // the USD amount of OUSG is used
399:    uint256 ousgPrice = getOUSGPrice();
400:    uint256 usdcAmountToRedeem = _getRedemptionAmount(ousgAmountIn, ousgPrice);
...
        // but funding done in USDC
455:    usdc.transfer(msg.sender, usdcAmountOut);
```

`USDC` doesn't always follow `USD` 1:1. It can depeg occasionally. Most famously on March 11th 2023 when it traded well under $1 for an extended period of time.

Were something similar to happen a user would be able to buy `OUSG` at a discount if `USDC` is below peg. By buying `USDC` cheap and then getting its `USD` value in `OUSG`. Or you could sell `OUSG` at a premium if `USDC` was above peg, since they could then sell the `USDC` for higher value in `USD`.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Consider implementing an off chain monitoring that pauses `ousgInstantManager` were a large enough depeg to happen. Or use a more complex oracle with a comparison of `USDC/USD` as well.


## Assessed type

Oracle
Redemption amount is scaled down excessively leading to loss of funds
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L699


# Vulnerability details

## description
the fuction `_getRedemptionAmount` takes the ousg to be burned and multiplies it by the price, then we scale down to usdc decimals.
```solidity
   function _getRedemptionAmount(
    uint256 ousgAmountBurned,
    uint256 price
  ) internal view returns (uint256 usdcOwed) {
    uint256 amountE36 = ousgAmountBurned * price;
    usdcOwed = _scaleDown(amountE36 / 1e18);
  }
```
The problem is we divide `AmountE36` by 1e18, this is incorrect because `price` will not be in 18 decimals. Let us look at the `getOUSGPrice` comments.

```solidity
   * @notice Returns the current price of OUSG in USDC
   *
   * @dev Sanity check: this function will revert if the price is unexpectedly low
   *
   * @return price The current price of OUSG in USDC
```
as we can see, the returned price of ousg will be in usdc which is 6 decimals already.

this means we are excessively scaling down the redemption amount by 1e12. `amountE36` variable is wrongly assumed to be 36 decimals when in reality this variable will be `price` (which is in terms of usdc or 6 decimals) multiplied by ousg amount( which is in terms of 18 decimals) which is actually 1e24 not 1e36.

this excessive amount of down scaling will cause redemption amounts to be severely undervalued and result in a huge loss of funds for users attempting to redeem.

## Impact
excessive amount of down scaling will cause redemption amounts to be severely undervalued and result in a huge loss of funds for users attempting to redeem.
## Proof of Concept
the mistake is evident without a coded poc i will provide a written poc to describe the flow

1. User wishes to redeem either rOUSG or OUSG
2. The _redeem function is called 
3. the function first finds the price of ousg
4. as i have shown the price will be in usdc/ 6 decimals
5. `_getRedemptionAmount` will be called with the amount of ousg to redeem and the price which is in 6 decimals
6 in the function we overly downscale, because of the incorrect assumption that price is in terms of ousg decimals ie 18
7. because of this we then downscale to get to usdc decimals but we downscale toomuch
8. the usdc owed to the user is downscaled by a magnitude of 1e12
9. a user who should have redeemed for example 1e13 of usdc will now only get 1e1 of usdc, essentially losing almost all of his funds

## Tools Used
manual review
## Recommended Mitigation Steps
in the `_getRedemptionAmount` function, change the following 

from 
```solidity
    uint256 amountE36 = ousgAmountBurned * price;
    usdcOwed = _scaleDown(amountE36 / 1e18);
```
to 

```solidity
    uint256 amountE24 = ousgAmountBurned * price;
    usdcOwed = _scaleDown(amountE36 / 1e6);
```





## Assessed type

Decimal
getOUSGPrice() didn't check the staleness of the price data
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L479


# Vulnerability details

## Impact
getOUSGPrice() is being used to query the price of OUSG, this function is being called by these contracts OUSGInstantManager, and rOUSG. The function is simply called the oracle to get the price data. 

However, getOUSGPrice() function that being called by these contract (OUSGInstantManager, rOUSG), is not checking if the price data is stale or not. this might lead to some issue, if there is some problem in the oracle that makes the price data unreliable. and make the contract mint more or mint less than it supposed to be.

Since the oracle itself already provide the timestamp of the price (https://etherscan.io/address/0x0502c5ae08E7CD64fe1AEDA7D6e229413eCC6abe#code), consider utilizing the timestamp information to check the staleness of the price data.

## Proof of Concept
- https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378
- https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L479

## Tools Used
Manual


## Assessed type

Oracle
Insufficient safeguards for handling price data in `ROUSG::getOUSGPrice` may lead to arithmetic underflow or overflow error
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/rOUSG.sol#L378


# Vulnerability details


## Impact

A price oracle delivers up-to-date price information, enabling smart contracts to engage with real-world data. This functionality supports automated trading of assets like shares and swaps. When incorporating price oracles into smart contracts, it’s crucial to implement protective measures to ensure the security and reliability of the data received.

The `ROUSG::getOUSGPrice` function retrieves price data for OUSG tokens from `IRWAOracle`. This data includes both the price and timestamp. The function directly utilizes the price data from the oracle without any additional validation or checks. Subsequently, the OUSG price is directly used as input for four other functions namely `ROUSG::balanceOf`, `ROUSG::totalSupply`, `ROUSG::getSharesByROUSG`, and `ROUSG::getROUSGByShares`. As such, the following safeguards have not been implemented in `ROUSG::getOUSGPrice` (and also `IRWAOracle::getPriceData`):
* Data Validation: The smart contract did not validate the data received from the oracle to ensure its integrity and validity. 
* Fallback Mechanism: There is no fallback mechanism such as backup oracles or a default value to ensure the contract can still function properly.
* Deviation Checks: There are no deviation checks to compare the new price with the previous price and only update if the deviation is within acceptable limits can help prevent sudden price spikes or manipulations.
* Timestamp Checks: There are no verifying the timestamp of the oracle data to ensure it is not too old or from the future can help maintain data validity.

Our tests noted unvalidated price data caused “panic: arithmetic underflow or overflow” error, and impacted the processing of `ROUSG::BalanceOf` function.


## Proof of Concept

We added 2 Foundry tests into `rOUSG.t.sol` to demonstrate the issue of unvalidated price data and its impact on the `ROUSG::BalanceOf` function. Each of the 2 Foundry tests used a different value as price data. There was a normal value of `$1` and an exceptionally large number of `$2**255-1`.

For the `test_unvalidated_pricedata()` Foundry test, the price data used was the normal value of $1. It was a successful test. The price data did not negatively impact the processing of the `ROUSG::BalanceOf` function.

For the `testFail_unvalidated_pricedata` Foundry test, the price data was set to the maximum positive int256 value (i.e. `$2**255-1`).  It was a successful testFail. Unlike the previous Foundry test, this test showed that price data impacted the processing of `ROUSG::BalanceOf` function. Refer to the detailed output for the failed test due to “panic: arithmetic underflow or overflow” error. 

To perform the tests:

* First, add the 2 Foundry tests into `rOUSG.t.sol`:
```javascript
   function test_unvalidated_pricedata() public dealAliceROUSG(1e18) {
       /
BUIDL tokens in the manager contract can be force converted into USDC DOSing operations at a small fee
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L426-L429


# Vulnerability details

## Impact

The InstantManager contract takes USDC deposits from users and mints them OUSG tokens, and also allows users to redeem them by breaking BUIDL positions. The contract is expected to have some USDC balance on it and some BUIDL balance on it, the USDC not accruing any value and the BUIDL tokens accuing yield.

The issue is thatwhen users try to redeem OUSG tokens, the contract first tries to redeem the BUIDL tokens.

```solidity
if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {
    _redeemBUIDL(usdcAmountToRedeem);
} else if (usdcAmountToRedeem > usdcBalance) {
    _redeemBUIDL(minBUIDLRedeemAmount);
} else {
}
```

As seen in the snippet above, if the `usdcAmountToRedeem` is greater than the `minBUIDLRedeemAmount`, the contract tries to break the BUIDL position. Otherwise, it tries to see if it has enough USDC to carry out a payout without breaking the BUIDL position. 

This means that even if the contract has enough USDC on it to carry out a withdrawal, it will still prefer to break the BUIDL position if the `usdcAmountToRedeem` is large. Thus a well funded user can keep depositing and withdrawing USDC, trigerring this BUIDL breaking.

When users deposit USDC tokens, their deposits are kept as USDC tokens and not deposited into the BUIDL position. So if a user comes in and deposits 250k USDC, their tokens will just be sitting in the contract accruing no yield. However if the same user now came to withdraw the same 250k USDC tokens, the contract will use up their BUIDL tokens to pay for that, even though it has 250k USDC tokens sitting right there. This allows users to make the contract run out of BUIDL tokens via constant deposits and withdrawals, even thought he contract has enough USDC to fund the withdrawals without dipping into the BUIDL tokens.

If the contract runs out of BUIDL tokens and has converted all BUIDL tokens into USDC tokens, then large withdrawals cannot be done anymore, since the `_redeemBUIDL` function will revert. This scenario is already acknowledged in the sponsor provided notes. This attack path demonstrates a way this state can be reached, DOSing the contract. The attacker only pays the deposit and withdrawal fees.

## Proof of Concept

The attack can be carried out in the following steps.

1. Assume the contract has 1 mill BUIDL tokens.
2. ALICE comes and deposits 300kUSDC, the contract now has 300kUSDC and 1 mill BUIDL tokens.
3. ALICE withdraws 300k USDC (ignoring fees for now). The contract breaks up 300k BUIDL tokens, converts them to UDSC and pays back ALICE. All the while having 300k USDC in the contract right there.
4. ALICE deposits back 300k USDC. Now the contract has 600k USDC and 700k BUIDL tokens.
5. ALICE withdraws 300k USDC. Now contract has 600k USDC and 400k BUIDL tokens.
6. ALICE deposits back 300k USDC. Now
Missing require in setOracle() can lead to DoS
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/ousgInstantManager.sol#L638-L642


# Vulnerability details

## Impact

The setOracle() function poses a potential vulnerability whereby passing address 0 as a function parameter could trigger problematic behavior, potentially resulting in a system crash and denial of service (DoS). This could lead to severe consequences, including financial losses, especially if it occurs during critical operations or transactions.

## Proof of Concept

The function can execute, with 0 as an address parameter in the setOracle() function call. After that always when getOUSGPrice() is called to get ousg price it will revert.

## Tools Used

None

## Recommended Mitigation Steps

The setOracle() should check if function parameter is not address(0). 


## Assessed type

DoS
`ousgInstantManager` could work with outdated OUSG price because `getOUSGPrice` does not check the `priceTimestamp` returned by the oracle
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L479-L485


# Vulnerability details

[ousgInstantManager::getOUSGPrice](https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L479-L485) fetches the current price of OUSG in USDC from the Oracle. 
```js
function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
    require(
      price > MINIMUM_OUSG_PRICE,
      "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
    );
}
```
Below is the code of `getPriceData` oracle's function that the contract interacts with:
```js
/**
* @notice Retrieve the last set RWA price data
*
* @dev `price` is in 18 decimals, `timestamp` is unix seconds since epoch
*/
function getPriceData() external view override returns (uint256, uint256) {
  return (uint256(rwaPrice), priceTimestamp);
}
```
We can see that the oracle's function returns a second value--`priceTimestamp`, which is the timestamp in which the RWA token price was last set. The issue is that `ousgInstantManager::getOUSGPrice` does not check for this value, in which it could receive an outdated price from the oracle(the Oracle did not update the price for *a considerably* long time).
## Impact
`getOUSGPrice` is used by critical functions e.g.`_getMintAmount`, `_getRedemptionAmount`. Those functions are susceptible to using outdated OUSG price

## Tools Used
Manual review
## Recommended Mitigation Steps
Consider making necessary checks on the `priceTimestamp` returned by the Oracle. Below is a suggestion for an updated code of `ousgInstantManager::getOUSGPrice`:
```diff
function getOUSGPrice() public view returns (uint256 price) {
    (price,
+    priceTimestamp
    ) = oracle.getPriceData();
    require(
      price > MINIMUM_OUSG_PRICE,
      "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
    );
+    require(
+      block.timestamp - priceTimestamp < ALLOWED_DELAY_INTERVAL,
+      "OUSGInstantManager::getOUSGPrice: Price returned by Oracle is outdated"
+    )
}
```
It is up to the dev team to decide what would be the appropriate value of `ALLOWED_DELAY_INTERVAL`


## Assessed type

Invalid Validation