xanoutas/solidity-security-findings huggingface dataset:
Function `rOUSG.burn` cannot burn tokens from sanctioned addresses.
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L618-L632
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L554-L560
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L586-L595
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/kyc/KYCRegistryClientUpgradeable.sol#L100-L102
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/kyc/KYCRegistry.sol#L128-L135


# Vulnerability details

## Impact
Admin can burn rOUSG tokens from any account via `rOUSG.burn`. But it's not true for sanctioned account addresses, which breaks the functionality of the function.

## Proof of Concept
The target account's KYC status is first checked in function `rOUSG.burn` before burning, and the calling process is `rOUSG.burn => rOUSG._burnShares => rOUSG._beforeTokenTransfer => KYCRegistryClientUpgradeable._getKYCStatus => KYCRegistry.getKYCStatus`. The target account address is required to be KYCed and not sanctioned. Therefore, if the target account is a sanctioned address, the `burn` will revert and admin will not burn the tokens, which does not achieve the functionality of the function `burn`.
```solidity
618:    /**
619:@>   * @notice Admin burn function to burn rOUSG tokens from any account
620:     * @param _account The account to burn tokens from
621:     * @param _amount  The amount of rOUSG tokens to burn
622:     * @dev Transfers burned shares (OUSG) to `msg.sender`
623:     */
624:    function burn(
625:      address _account,
626:      uint256 _amount
627:    ) external onlyRole(BURNER_ROLE) {
628:      uint256 ousgSharesAmount = getSharesByROUSG(_amount);
629:      if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
630:        revert UnwrapTooSmall();
631:   
632:@>    _burnShares(_account, ousgSharesAmount);
```
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L618-L632

```solidity
554:    function _burnShares(
555:      address _account,
556:      uint256 _sharesAmount
557:    ) internal whenNotPaused returns (uint256) {
558:      require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS");
559:  
560:@>    _beforeTokenTransfer(_account, address(0), _sharesAmount);
```
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L554-L560

```solidity
586:    function _beforeTokenTransfer(
587:      address from,
588:      address to,
589:      uint256
590:    ) internal view {
591:      // Check constraints when `transferFrom` is called to facliitate
592:      // a transfer between two parties that are not `from` or `to`.
593:      if (from != msg.sender && to != msg.sender) {
594:@>      require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd");
595:      }
```
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L586-L595

```solidity
100:    function _getKYCStatus(address acc
mint/redeem limit can't be reached when the unminted/unredeemed tokens < minimumDepositAmount/minimumRedemptionAmount
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L286-L290
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L402-L406


# Vulnerability details

## Impact

Investors can't mint/redeem all the mint/redeem limit.

## Proof of Concept

The _mint() function in OUSGInstantManager has a minimumDepositAmount to prevent investors from depositing small amounts:

```solidity
uint256 public minimumDepositAmount = 100_000e6;
```

_mint() also includes a global limit check to prevent investors from minting unlimited amounts of OUSG tokens:

```solidity
_checkAndUpdateInstantMintLimit(usdcAmountIn);
```

If the usdcAmountIn to be minted is less than the remaining mint limit, _mint will revert with "RateLimit: Mint exceeds rate limit".

The issue arises when the first check (minimumDepositAmount) prevents investors from reaching the remaining mint limit when: (instantMintLimit - currentInstantMintAmount) < minimumDepositAmount.

Consider the following scenario:

- resetInstantMintDuration = 7 days
- instantMintLimit = 10,000,000
- After 3 days, investors have minted 9.1 million from the instantMintLimit, resulting in currentInstantMintAmount = 9,910,000
- Now, investors cannot mint the remaining mint limit because it is less than minimumDepositAmount. 
- Therefore, the remaining 90k cannot be minted by investors in the next 4 days.

The same issue exists in the _redeem() function: when usdcAmountToRedeem < minimumRedemptionAmount, the function will throw, and the remaining redeem limit cannot be redeemed by investors.

Submitting as medium:

> Assets not at direct risk, but the function of the protocol or its availability could be impacted.

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider allowing investors to mint/redeem the remaining limits when the unminted/unredeemed tokens are less than minimumDepositAmount/minimumRedemptionAmount. Here's an example for the mint function:

```solidity
    uint256 minDeposit = (instantMintLimit - currentInstantMintAmount) <
      minimumDepositAmount
      ? (instantMintLimit - currentInstantMintAmount)
      : minimumDepositAmount;
    require(
      usdcAmountIn >= minDeposit,
      "OUSGInstantManager::_mint: Deposit amount too small"
    );
```


## Assessed type

Invalid Validation
Lack of Sanity Check at `rOUSG::getOUSGPrice` in Case of Oracle Malfunction
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

In order to know the `OUSG` price, contracts `ousgInstantManager` and `rOUSG` query the oracle via the function `getOUSGPrice()`. Before using the price from the oracle, the contract `ousgInstantManager` does a sanity check that ensures that the price isn't unexpectedly low, the minimum allowed value is `105e18`.

However, the contract `rOUSG` doesn't do this sanity check. Therefore, in a scenario where the oracle malfunctions and it returns a price lower than `MINIMUM_OUSG_PRICE`, all the mint and redeem functions on `ousgInstantManager` will revert, preventing an unsafe usage of the oracle. 

On the other hand, when the oracle malfunctions and it returns a price lower than `MINIMUM_OUSG_PRICE`, the `rOUSG` contract will not block any functionality, and that will cause loss of funds for users unaware of the current oracle malfunction. 

Given this scenario, a user could easily lose funds by the following ways:

1. Transfering too many shares to other users/contracts by specifying an amount of tokens relative to the correct (and higher) price.
2. Unwrapping too many shares and receiving less tokens due to the wrong price

## Impact

When the `OUSG` oracle malfunctions and it returns a price unexpectedly low, the functionality at `rOUSG` contract won't be halted, causing losses of funds to users unaware of the problem. 

## Proof of Concept

The `ousgInstantManager` contract performs a sanity check when getting the `OUSG` price:

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L481-L484
```solidity
  
  // Safety circuit breaker in case of Oracle malfunction
  uint256 public constant MINIMUM_OUSG_PRICE = 105e18;

  // ...

  /**
   * @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
   */
  function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
    require(
      price > MINIMUM_OUSG_PRICE,
      "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
    );
  }
```

However, the `rOUSG` contract doesn't do the sanity check:

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378-L380
```solidity
  function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
  }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

To mitigate this issue is recommended to implement the sanity check on the reported `OUSG` price withing the `rOUSG` contract:

```diff
  function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
+   require(price > 105e18, Price unexpectedly low); 
  }
```


## Assessed type

Oracle
The `mutiexcall` let the arbitrary external call to load a huge data in memory. 
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

**Description:**
The `multiexcall` function allows arbitrary external calls to be load the huge data in memory. The attacker can load the huge data in memory and every time the new data is loaded in memory it costs more. This means that external contracts can be invoked which may return excessive amounts of data, leading to increased gas costs.

**Impact:** 
Calling external calls with large return data which load in memory can significantly increase gas costs for the transaction.

**Proof of Concept:**
1. The admin execute `multiexcall` function with the array of multiple addresses which also includes the address of the attacker.
2. The attacker invokes `multiexcall` large amount of return data which will load in memory.

**Recommended Mitigation:** 
 The recommended mitigation is to before assigning the return data to results[i], check its size and limit the amount of data stored to mitigate the impact of large return data. OR use assembly to make external calls.


## Assessed type

call/delegatecall
Reentrancy Vulnerability in multiexcall Function Due to Lack of Target Whitelisting
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts%2Fousg%2FrOUSGFactory.sol#L121-L132


# Vulnerability details

The `multiexcall` function  calls non-whitelisted  `target` addresses , exposing it to potential reentrancy attacks. 
The `multiexcall` function iterates through an array of `ExCallData` structures and invokes the call function on each `target` address without performing any validation or whitelisting checks. allows attackers to exploit the lack of `target` validation if the `gaurdian` inputs malicious addresses into the `exCallData` array, enabling reentrancy attacks.

## Impact
This opens up access for the `target` to call back into the contract and modify it's state, the rouge `target` can call back into the `multiexcall` function.


## Proof of Concept
````solidity
  function multiexcall(
    ExCallData[] calldata exCallData
  ) external payable override onlyGuardian returns (bytes[] memory results) {
    results = new bytes[](exCallData.length);
    for (uint256 i = 0; i < exCallData.length; ++i) {
      (bool success, bytes memory ret) = address(exCallData[i].target).call{
        value: exCallData[i].value
      }(exCallData[i].data);
      require(success, "Call Failed");
      results[i] = ret;
    }
  }
```
If the target address gets spoofed by an attacker or if the target address acts maliciously they can call back into the function to drain the Eth.

## Tools Used
Manual Review 

## Recommended Mitigation Steps
Include a nonReentrant modifier to the function and whitelist each target whitelisting the target ensures the address can't get spoofed.


## Assessed type

call/delegatecall
An arbitrary external call can gas grief the admin. 
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


**Description:** 
As the readme of contest sates "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." And the `multiexcall` function does not have gas limit which can cause potential gas griefing to the admin if the arbitrary external call can exploit the gas refund mechanism to drain the admin's funds.
Gas refund mechanism: The gas refund allows users to recoup some of the gas fees they spent executing a smart contract. The refund is calculated based on the difference between the gas used and the gas limit specified in the transaction

**Impact:** 
The arbitrary external call attackers can deliberately craft external call that consume large amounts of gas or enter into infinite loops, causing the transaction to run out of gas and revert. As a result, the admin loses the transaction fees.

**Proof of Concept:**
1. The admin execute `multiexcall` function with the array of multiple addresses which also includes the address of the attacker.
2. The arbitrary contract invokes the multiexcall with an expensive fallback functions  with large array of calls. which causes the gas grief to the admin.

**Recommended Mitigation:** 
The recommended mitigation is to use `gasleft()` function to estimate and limit the amount of gas allocated to each external call. The `gasleft()` function is used to check the amount of gas remaining in the current transaction. OR specified the gas limit for every external call.


## Assessed type

ETH-Transfer
getOUSGPrice() functions don't care about price timestamp and are vulnerable to oracle malfunctions
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In scope, there are two `getOUSGPrice()` functions [ousgInstantManager.getOUSGPrice()](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L479-L485) and [rOUSG.getOUSGPrice()](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378-L380). The former has a protection against oracle malfunction: if the price returned by the oracle is too low, the transaction reverts. The latter doesn't have this protection, as we reported in another issue.

However, both of these functions don't care about price timestamps. This means that in case of oracle malfunction (if oracle doesn't keep updating the price value), the functionalities of `ousgInstantManager` and `rOUSG` keep working, computing their values on an old price value.

## Proof of concept

As described inside the Discord channel, this is the oracle contract: [RWAOracleExternalComparisonCheck](https://etherscan.deth.net/address/0x0502c5ae08E7CD64fe1AEDA7D6e229413eCC6abe). So, this is the `oracle.getPriceData()` function:

```
L115-L128

/**
* @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 price, uint256 timestamp)
{
    price = uint256(rwaPrice);
    timestamp = priceTimestamp;
}
```

So, the oracle returns the timestamp value. However, inside [ousgInstantManager.getOUSGPrice()](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L479-L485):

```
479    function getOUSGPrice() public view returns (uint256 price) {
480      (price, ) = oracle.getPriceData();
481      require(
482        price > MINIMUM_OUSG_PRICE,
483        "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
484      );
485    }
```

and [rOUSG.getOUSGPrice()](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L378-L380):

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

the returned value of the timestamp is ignored.

## Tools Used
Visual inspection

## Recommended Mitigation Steps

[ousgInstantManager.getOUSGPrice()](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L479-L485)

```diff
479    function getOUSGPrice() public view returns (uint256 price) {
-480     (price, ) = oracle.getPriceData();
+        uint256 timestamp;
+        (price, timestamp) = oracle.getPriceData();
481      require(
482        price > MINIMUM_OUSG_PRICE,
483        "OUSGInstantManager::getOUSGPrice: Price unexpectedly low"
484      );
+        require(
+
transferFrom uses allowance even if _sender == _recipient
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
rOUSG#transferFrom attempts to use allowance even when _sender = _recipient. This breaks compatibility with a large number of protocol who opt to use the transferFrom method all the time (pull only) instead of using both transfer and transferFrom (push and pull). The ERC20 standard only does an allowance check when spender != from. The result of this difference will likely result in tokens becoming irreversibly stranded across different protocols.

## Proof of Concept
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L281-L286
The trasnferFrom method shown above always uses allowance even if _sender = _recipient.

Token won't be compatible with some protocols and will end up stranded


## Tools Used
Manual Review

## Recommended Mitigation Steps
Only use allowance when _sender != _recipient


## Assessed type

ERC20
Incorrect Decimals Comparison in constructor
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The constructor in `ousgInstantManager` contract checks for decimal equivalency between OUSG/rOUSG and USDC/BUIDL. The error message for the first check mentions "OUSG decimals must be equal to USDC decimals", which contradicts what's being checked (OUSG vs. rOUSG). The intention might be to ensure OUSG and rOUSG have the same decimals, but the message is incorrect or misleading.

Misleading error messages erode trust, as they can confuse developers leading to incorrect assumptions about the contract's logic.

## Proof of Concept
[Loc:](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L187)

```solidity
require(
  IERC20Metadata(_ousg).decimals() == IERC20Metadata(_rousg).decimals(),
  "OUSGInstantManager: OUSG decimals must be equal to USDC decimals"
);
require(
  IERC20Metadata(_usdc).decimals() == IERC20Metadata(_buidl).decimals(),
  "OUSGInstantManager: USDC decimals must be equal to BUIDL decimals"
);

```
## Tools Used
Manual

## Recommended Mitigation Steps
Change to:

```solidity
require(
  IERC20Metadata(_ousg).decimals() == IERC20Metadata(_rousg).decimals(),
  "OUSGInstantManager: OUSG decimals must be equal to rOUSG decimals"
);

```


## Assessed type

Error
rOUSG token misses oracle sanity checks
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

`rOUSG` and the `ousgInstantManager` contract both uses the same price oracle for getting the `OUSG` token value.

But only the `ousgInstantManager` contract does sanity checks on the provided price before using it. The `rOUSG` token misses any checks on the provided price and in doing so its operations can also use `OUSG` price that for the `ousgInstantManager` are deemed as too low.

## Proof of Concept

This is the code when `ousgInstantManager` contract gets `OUSG` price:

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

The `rOUSG` rebasing token doesn't make any sanity checks on the returned oracle price:

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

And uses any provided price in all its core operations.

## Tools Used
Manual review

## Recommended Mitigation Steps

Add same price expectation when using the oracle on the rebasing OUSG token like is done in the `ousgInstantManager` contract.

```diff
contract ROUSG is 
    ...
+ // Safety circuit breaker in case of Oracle malfunction
+ uint256 public constant MINIMUM_OUSG_PRICE = 105e18;

  function getOUSGPrice() public view returns (uint256 price) {
    (price, ) = oracle.getPriceData();
+   require(
+     price > MINIMUM_OUSG_PRICE,
+     "ROUSG::getOUSGPrice: Price unexpectedly low"
+   );
  }
  ...
```


## Assessed type

Invalid Validation