xanoutas/solidity-security-findings huggingface dataset:
Missing BUIDL minimum check in ousgInstantManager.sol::setMinimumBUIDLRedemptionAmount can lead to wrong protocol calculations and behavior
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#L622-L630


# Vulnerability details

## Impact
The current implementation of the setMinimumBUIDLRedemptionAmount() function is missing a key check of the minimum BUIDL requirement as specified in the documentation, where a redemption amount of at least 250,000 BUIDL tokens is required. This lack exposes the protocol to potential exploitation by malicious users, allowing them to perform redemptions below the specified minimum threshold. This omission compromises security, potentially leading to financial losses and compromising the integrity of the redemption process.

## Proof of Concept

The function setMinimumBUIDLRedemptionAmount() can execute with an amount below 250,000 BUIDL tokens and observe that the setter is executed without confirming that the new amount is greater than or equal to 250,000 BUIDL.
For example minBUIDLRedeemAmount can be set to 0.


## Tools Used

None

## Recommended Mitigation Steps

In ousgInstantManager.sol::setMinimumBUIDLRedemptionAmount() add a require that will check if the function parameter is greater than or equal to 250,000 BUIDL.


## Assessed type

Other
In ousgInstantManager.sol during creation, in the constructor USDC and BUIDL potentially can be initialized with more than 6 decimals that 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#L190-L193
https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/ousgInstantManager.sol#L391-L398


# Vulnerability details

## Impact

The smart contract contains a vulnerability related to decimal checks that could lead to a denial of service (DoS) attack. Currently, the constructor checks whether the decimal values of _usdc and _buidl are equal, but does not enforce a specific decimal value. However, the _redeem() function introduces a requirement to check whether the decimal values of these tokens are specifically equal to 6. This design flaw introduces a potential DoS vulnerability.

## Proof of Concept

If a contract is created with decimal values for example _usdc=8 and _buidl=8, they will be equal and pass the requirement in the constructor, but then users will not be able to use the _redeem() function because it will always revert since the decimal requirement in the function is equal to 6.

## Tools Used

None

## Recommended Mitigation Steps

Consider creating a require in the constructor to ensure that the decimals will be 6.


## Assessed type

DoS
`redeem()` will be skipped in an edge case
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#L430


# Vulnerability details

## Impact
`Redeem()` will not work as intended and will be skipped in case where `usdcAmountToRedeem = usdcBalance`

## Proof of Concept
The function [`redeem()`](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L388-L456) is used to redeem `USDC` for `OUSG` while everything seems to be okay in the internal function there is and edge case which is not handled.

There is a conditional check while redeeming 

```solidity
if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {  
            _redeemBUIDL(usdcAmountToRedeem);
        } else if (usdcAmountToRedeem > usdcBalance) {
            _redeemBUIDL(minBUIDLRedeemAmount);
            emit MinimumBUIDLRedemption(
                msg.sender, minBUIDLRedeemAmount, usdcBalance + minBUIDLRedeemAmount - usdcAmountToRedeem
            );
        } else {
        emit BUIDLRedemptionSkipped(msg.sender, usdcAmountToRedeem, usdcBalance - usdcAmountToRedeem);
        }
```

The first if block checks if `usdcAmountToRedeem >= minBUIDLRedeemAmount` and if the USDC amount to redeem is greater than or equal to the minimum BUIDL redeem amount it proceeds further and redeems BUIDL,
And if the first if block is not satisfied it will move to the next else if block and it will check `usdcAmountToRedeem > usdcBalance` if the USDC amount to redeem is greater than USDC balance, but it is not handeling the case where ``usdcAmountToRedeem = usdcBalance` and in this particular case it will skip the redemption and will move to the final else block and just emits the event.

This is an edge case where redemption will be skipped and can be mitigated easily.
## Tools Used
Manual Review.
## Recommended Mitigation Steps
```diff
 if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {
            _redeemBUIDL(usdcAmountToRedeem);
-        } else if (usdcAmountToRedeem > usdcBalance) {
+        } else if (usdcAmountToRedeem > usdcBalance) {
            _redeemBUIDL(minBUIDLRedeemAmount);
            emit MinimumBUIDLRedemption(
                msg.sender, minBUIDLRedeemAmount, usdcBalance + minBUIDLRedeemAmount - usdcAmountToRedeem
            );
        } else {
            emit BUIDLRedemptionSkipped(msg.sender, usdcAmountToRedeem, usdcBalance - usdcAmountToRedeem);
        }
```



## Assessed type

DoS
The user is unable to redeem the remaining rOUSG using redeemRebasingOUSG() for USDC.
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#L418


# Vulnerability details

## Vulnerability Detail
This issue pertains to the calculation process of OUSG, rOUSG, and shares in mintRebasingOUSG() and redeemRebasingOUSG(). Therefore, the focus will be on explaining these parameters.

Let's take a look at  how mintRebasingOUSG() and redeemRebasingOUSG() are calculated respectively when we assume getOUSGPrice() equals 1 and getOUSGPrice() equals 2.

**getOUSGPrice() = 1**

- mintRebasingOUSG()

    if ousgAmountOut=1e18
    
    --> wrap(ousgAmountOut)
    ousgSharesAmount=1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER
    
    --> _mintShares(msg.sender, ousgSharesAmount)

    rousgAmountOut = ((ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER)* getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)

    --> rousgAmountOut= 1
```
user's usdc mint  1e18 ousg
get 1 rousg 
mint  1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER shares
```

- redeemRebasingOUSG
    if rousgAmountIn = 1 
    --> unwrap(rousgAmountIn)
    ousgSharesAmount = (rousgAmountIn* 1e18 *   OUSG_TO_ROUSG_SHARES_MULTIPLIER)/ getOUSGPrice()

    --> _burnShares(msg.sender, ousgSharesAmount);

    ousg= ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    --> ousg= 1e18
```
user transfer 1 rousg
use  1e18 ousg to redeem usdc
burn 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER shares
```

**getOUSGPrice() = 2**

- mintRebasingOUSG()

    if ousgAmountOut=1e18
    --> wrap(ousgAmountOut)
    ousgSharesAmount=1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER
    --> _mintShares(msg.sender, ousgSharesAmount)

    rousgAmountOut = ((ousgAmountOut * OUSG_TO_ROUSG_SHARES_MULTIPLIER)* getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)

    --> rousgAmountOut= 2
```
user's usdc mint  1e18 ousg
get 2 rousg
mint  1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER shares
```

- redeemRebasingOUSG
    if rousgAmountIn = 1 
    --> unwrap(rousgAmountIn)
    ousgSharesAmount = (rousgAmountIn* 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)/ getOUSGPrice()

    --> _burnShares(msg.sender, ousgSharesAmount);

    ousg= ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    --> ousg= 1e18
```
user transfer 1 rousg
use   ((1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)/ 2)/OUSG_TO_ROUSG_SHARES_MULTIPLIER  ousg to redeem usdc
burn (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)/2 shares
```


this table summarizing the state of user transactions and shares in mintRebasingOUSG() and redeemRebasingOUSG() under different prices.

if getOUSGPrice() = 1 
|  | mintRebasingOUSG |redeemRebasingOUSG|
| -------- | -------- | --------|
| user transfer | 1e18 ousg |1 rousg|
| user get | 1 rousg | 1e18 ousg|
| shares |mint  1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER |burn 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER shares|


if getOUSGPrice() = 2
|  | mintRebasingOUSG |redeemRebasingOUSG|
| -------- | -------- | --------|
| user transfer | 1e18 ousg |1 rousg|
| user get | 2 rousg |  ((1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER)
Users can take advantage of unwrap if price goes below 1e18
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#L415-L422
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L431-L443


# Vulnerability details

## Impact
Due to the price only being taken into account when unwrapping tokens users that wrap when the `getOUSGPrice` function in the rOUSG contract returns a price less than 1e18 the users that wrap tokens will imitatly realize a loss since they will not be able to unwrap all of their shares an only take back the portion they put in until the price goes above the value 

```
    uint256 ousgSharesAmount = getSharesByROUSG(_rOUSGAmount);
```

This can be taken advantage of by having a user wrap a large amount and unwrap the allowed amount leaving them with more shares in the rOUSG and the same balance of OUSG.

## Proof of Concept
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L431-L443
```
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, address(0), ousgSharesAmount);
  }
```

## Tools Used
vscode

## Recommended Mitigation Steps
Consider not using the price when unwrapping tokens





## Assessed type

Other
Arbitrage on wrap()
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#L417


# Vulnerability details

## Impact
In the wrap() , the ousgSharesAmount is calculated using a fixed ratio.
```
 uint256 ousgSharesAmount = _OUSGAmount * OUSG_TO_ROUSG_SHARES_MULTIPLIER;
```
And this value is used to _mintShares for the user.
```
_mintShares(msg.sender, ousgSharesAmount);
```

No matter if the OUSG price rises or falls, by  _mintShare(),  users  can use same ousg amount and get same ousgSharesAmount.

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

## Tools Used

## Recommended Mitigation Steps

When calculating ousgSharesAmount in wrap(), the OUSGPrice needs to be consider.


## Assessed type

Context
Admin check on retrieve tokens can be removed
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#L819


# Vulnerability details

## Impact
Although the protocol is aware of this sort of issue , but they wanted to have a different approach about if another way can be used 

> [$NOTE From sponsors$ : malicious Admin/Operator - we are aware that admins have the ability to call various unrestrained setters, rOUSG burns, a retrieve Tokens function, and a multicall function. We would be open to suggestions on ways we can further restrain the setters without decreasing operational overhead, convenience, or flexibility of the code. The permissioned multicall function on OUSGInstantManager will only be used to transfer non-ERC20 assets out of the contract if the assets are accidentally deposited. Other permissioned usages of the multicall function resulting in problems are not valid. ]

## Proof of Concept
I don't think we would need a `adminrole` check in this , since the OZ.IERC20 transfer sends the token to any reccipient. Moreover a malicious owner can transfer the tokens to his account or any other person account. 
```
  function retrieveTokens(
    address token,
    address to,
    uint256 amount
  ) external onlyRole(DEFAULT_ADMIN_ROLE) {
    IERC20(token).transfer(to, amount);
  }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use withdrawing instead of transferring, Let the token owners withdraw their amount of tokens instead of granting the `admin` total control of the tokens.

Or else if the protocol wants to maintain the admin role they can use to track the token share of a particular address and send them that amount , since if any rebasing token is accidentally deposited in the protocol , and the amt increases overtime , but the admin only sends them the accidentally deposited amt , not the rebased amount.


## Assessed type

Access Control
No staleness check on Oracle in instantRedeem in Manager contract flow
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#L480


# Vulnerability details

# No staleness check on Oracle in instantRedeem in Manager contract and unwrap in rOUSG flow

Within the redemption/minting flow for OUSGInstantManager, if the `RWADynamicOracle` or any oracle for that matter is stale value will not be accrued. And the oracle will return the previous range end. Consequenty if the `range.end` has elapsed value is not being accrued. This is especially dangerous when pairing with instant minting and redemption functionality. A user may unknowingly instanlty redeem their rOUSG/OUSG tokens in the period after `range.end` has elapsed prior to a new range being set and consequently forfeit their yield. 

**recomendation**
Add an oracle livelyness check on in the manager contract. It seems that the `RWADynamicOracle` allows for the timestamp to be passed but it will always return the current timestamp. I would recomend returning the timestamp from which the price was derived.

```contracts/rwaOracles/RWADynamicOracle.sol:L#77
  function getPrice() public view whenNotPaused returns (uint256 price, uint256 derivedTimestamp) {
    uint256 length = ranges.length;
    for (uint256 i = length; i > 0; --i) {
      Range storage range = ranges[i - 1];
      if (range.start <= block.timestamp) {
        if (range.end <= block.timestamp) {
          return (derivePrice(range, range.end - 1), range.end - 1);
        } else {
          return (derivePrice(range, block.timestamp), block.timestamp);
        }
      }
    }
  }
```
Then pass this additional return to the `getPriceData` function, and then validate the timestamp from which the rOUSG/OUSG price is derived like so.

```contracts/ousg/ousgInstantManager.sol:L#479
  function getOUSGPrice() public view returns (uint256 price) {
    uint256 timestamp;
    (price, timestamp) = oracle.getPriceData();
    require(
      price > MINIMUM_OUSG_PRICE,
      "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
    );
    require(
        timestamp == block.timestamp,
        "Oracle Price is Stale"
    );
  }
```

## Impact 
User may mint/redeem OUSG/rOUSG at an incorrect value, resulting in a financial loss to either the protocol or the user.




## Assessed type

Oracle
Pausing `RWADynamicOracle` Inability to call Admin Function burn shares even if rOUSG remains unpaused
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#L624-L640
https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/rwaOracles/RWADynamicOracle.sol#L77


# Vulnerability details

# Pausing `RWADynamicOracle` Inability to call Admin Function burn shares even if rOUSG remains unpaused

If the `RWADynamicOracle` is paused the admin function `burn(account,amount)` will cease to work, Even if rOUSG is in an `unPaused` state.

This is due to the conversion between amount and shares that this function implies. It is not best practice to have pause functionality on a tertiary contract break admin functions on the token contract.

```
function burn(
    address _account,
    uint256 _amount
  ) external onlyRole(BURNER_ROLE) {
    uint256 ousgSharesAmount = getSharesByROUSG(_amount);
    if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
      revert UnwrapTooSmall();

    _burnShares(_account, ousgSharesAmount);

    ousg.transfer(
      msg.sender,
      ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    );
    emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
    emit TransferShares(_account, address(0), ousgSharesAmount);
  }
```


## Tools Used
Visual analysis, Foundry

## Recommended Mitigation Steps
Given that shares are just ousg units scaled up by 1e5, it would be better to have this function burn shares as opposed to having the function take rOUSG `_amount` as a parameter.

eg:
```
function burn(
    address _account,
    uint256 _shares
  ) external onlyRole(BURNER_ROLE) {
   
    if (_shares < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
      revert UnwrapTooSmall();

    _burnShares(_account, _shares);

    ousg.transfer(
      msg.sender,
      _shares / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    );
    emit TransferShares(_account, address(0), _shares);
  }
```

If this change is made, rOUSG will remain burnable via the admin even though the `RWADynamicOracle` is in a paused state





## Assessed type

Error
Function `retrieveTokens` in `ousgInstantManager` can deem the redemptions useless
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#L813-L825


# Vulnerability details

## Impact

Calling `retrieveTokens` in order to retrieve both USDC and BUIDL tokens from the contract would result in ceasing the redemption functionality.

## Proof of Concept

When a user wants a redemption of their OUSG for a given amount of USDC, they call `redeem` which in turn calls the internal function `_redeem`.

```Solidity
    function _redeem(uint256 ousgAmountIn) internal returns (uint256 usdcAmountOut) {
        
        ...

        uint256 usdcBalance = usdc.balanceOf(address(this));

        if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {
            // amount of USDC needed is over minBUIDLRedeemAmount, do a BUIDL redemption
            // to cover the full amount
            _redeemBUIDL(usdcAmountToRedeem);
        } else if (usdcAmountToRedeem > usdcBalance) {
            // There isn't enough USDC held by this contract to cover the redemption,
            // so we perform a BUIDL redemption of BUIDL's minimum required amount.
            // The remaining amount of USDC will be held in the contract for future redemptions.

@>          _redeemBUIDL(minBUIDLRedeemAmount);
            emit MinimumBUIDLRedemption(
                msg.sender, minBUIDLRedeemAmount, usdcBalance + minBUIDLRedeemAmount - usdcAmountToRedeem
            );
        } else {
         ...
```

For whatever reason, an admin calls [`retrieveTokens`](https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/ousgInstantManager.sol#L813-L825) for all of the BUIDL tokens held in the contract, but at the same time, a user decides they want to exchange their OUSG tokens for USDC. 
A few scenarios can take place:
 - Their request gets fulfilled if there's enough USDC in the contract.
 - If there isn't enough USDC to cover the redemption, `_redeemBUIDL` is called, and the redemption is fulfilled.

However, if all of the USDC tokens and BUIDL tokens are retrieved, the user would be stuck with OUSG tokens and the most they can do is wrap them.

## Code snippet
```Solidity
  /**
   * @notice Rescue and transfer tokens locked in this contract
   * @param token The address of the token
   * @param to The address of the recipient
   * @param amount The amount of token to transfer
   */
  function retrieveTokens(
    address token,
    address to,
    uint256 amount
  ) external onlyRole(DEFAULT_ADMIN_ROLE) {
    IERC20(token).transfer(to, amount);
  }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

One reasonable approach is to add a timelock to the function `retrieveTokens`. That way, the users can be notified prior to the execution of the call in order for them to take appropriate action.

Here's an example of a timelock contract that can be refactored for this specific case: https://solidity-by-example.org/ap
The return value  of the getPriceData() of the IRWAOracle interface is not checked
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#L379


# Vulnerability details

## Summary
The `getPriceData()` function of the `IRWAOracle` interface returns `price data`, but the success value of this operation is not checked when used in the `ROUSG` contract. When success is `false`, the `Oracle` may return `stale data`.

## Proof of Concept
- https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/interfaces/IRWAOracle.sol#L20-L24

```solidity
  function getPriceData()
    external
    view
    returns (uint256 price, uint256 timestamp);
}
```

Here it is used in `ROUSG` contract:
- https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/rOUSG.sol#L378-L380

```solidity
  function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData(); // @audit No success check
  }
```

## Impact
If the `oracle.getPriceData()` call fails for any reason (e.g., the oracle contract does not exist, it throws an error, or it returns an unexpected result), the function will not `revert` or handle this failure gracefully. 
This could lead to the function returning an `incorrect or uninitialized price value`, which could be misleading or misused by calling contracts or users.

## Tools Used
Manual Review

## Recommended Mitigation Steps
The function should be updated to explicitly check the success of the `oracle.getPriceData()` call and to handle errors appropriately.

```solidity
  function getOUSGPrice() public view returns (uint256 price) {
    (bool success, uint256 _price) = oracle.getPriceData();
    require(success, "Failed to get price data from the oracle"); // @audit Success checked
    price = _price;
}
```








## Assessed type

Oracle