xanoutas/solidity-security-findings huggingface dataset:
Users are unable to redeem when there is USDC in the OUSGInstantManager and not enough buidl balance
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#L426-L439
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L458-L469


# Vulnerability details

## Summary

Users are unable to redeem their OUSG tokens for USDC if there are USDC leftovers in `OUSGInstantManager` and `buidl.balanceOf(address(OUSGInstantManager)) < minBUIDLRedeemAmount`

## Vulnerability Details

When a user wants to redeem his tokens, the `OUSGInstantManager` implements a few checks:

```solidity
    if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {
      // amount of USDC needed is over minBUIDLRedeemAmount, do a BUIDL redemption
      // to cover the full amount
      _redeemBUIDL(usdcAmountToRedeem);
    }
```

If the USDC amount to be redeemed is more than the `minBUIDLRedeemAmount`, which is `250_000e6`, then we call the `buidlRedeemer` to get the full amount and transfer it to the user.

```solidity
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, if the amount to be redeemed is below the `minBUIDLRedeemAmount`, but is still higher than the USDC balance of the contract, then we redeem the `minBUIDLRedeemAmount` which means there can be some USDC leftovers after we transfer the user his redeemed amount. This becomes an issue if another user wants to redeem his tokens but the buidl balance of the contract is not enough to call the `buidlRedeemer`. Even though the user's order can be satisfied, it isn't and the transaction is reverted. Please take a look at the steps below for a clearer explanation:

(Assuming the price of OUSG is the same for easier calculations)
Let's say we have 2 users that deposited their USDC tokens.
User1 has 100_000e6 tokens deposited and User2 has 200_000e6.
At this point of time the USDC balance of `OUSGInstantManager` is 0 and the `buidlRedeemer` has 300_000e6 (`buidl.balanceOf(address(this)) = 300_000e6`).
1. User1 redeems all of his OUSG tokens worth 100_000e6 USDC.
2. The `OUSGInstantManager` redeems 250_000e6 (since this is the minimum amount) buidl from `buidlRedeemer` and transfers 100_000e6 to User1.
3. Now the USDC balance of `OUSGInstantManager` is 250_000e6 - 100_000e6 = 150_000e6 and `buidl.balanceOf(address(this)) = 50_000e6`
4. User2 wants to redeem all of his OUSG tokens which are worth a total of 200_000e6 USDC. This transaction will fail because when trying to redeem from `buidlRedeemer` this require statement will fail:

```solidity
    require(
      buidl.balanceO
rOUSG uses `msg.sender` directly instead of `_msgSender() `
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#L221
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L252
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L281


# Vulnerability details

## Impact
rOUSG contract uses `msg.sender` directly, so an expectedly desired type of transactions won't work.

## Proof of Concept
The code is using OpenZeppelin's `ContextUpgradeable` contract which is intended to allow meta-transactions. It works by using doing a call to `_msgSender()` instead of querying `msg.sender` directly, because the method allows those special transactions.
The problem is that the transfer function use `msg.sender` directly instead of `_msgSender()`, which breaks this intent and will not allow meta-transactions.

https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L281
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L285
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L307
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L332-L337
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L401-L404
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L418-L421
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L436-L442
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L593-L594
https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/rOUSG.sol#L635-L638


## Tools Used
Manual review

## Recommended Mitigation Steps
Recommend to use `_msgSender()` instead of `msg.sender`.




## Assessed type

Context
Admin cannot burn rOUSG tokens from any account.
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/4791cd1946f834f27addbc162c1817e897de7bf9/contracts/ousg/rOUSG.sol#L618-L640


# Vulnerability details

## Impact

The functionality intended to allow administrators to burn rOUSG tokens from any account fails when the account in question is either not KYC compliant or is on a sanctions list. This limitation undermines the admin's ability to manage tokens in a way that may be necessary for regulatory compliance, token supply adjustment, or other critical administrative functions.

## Proof of Concept

In rOUSG contract, the `burn` function allows admin to burn tokens from any specified account.

```solidity
  /**
   * @notice Admin burn function to burn rOUSG tokens from any account
   * @param _account The account to burn tokens from
   * @param _amount  The amount of rOUSG tokens to burn
   * @dev Transfers burned shares (OUSG) to `msg.sender`
   */
  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);
  }
```
https://github.com/code-423n4/2024-03-ondo-finance/blob/4791cd1946f834f27addbc162c1817e897de7bf9/contracts/ousg/rOUSG.sol#L618-L640

When the `_burnShares` function is called, it has a `_beforeTokenTransfer` hook that checks if the `from` address (the account which token is burnt) is KYCed or in sanction list. If the account is not KYCed and in sanction list, then the call would revert. 

```solidity
  function _beforeTokenTransfer(
    address from,
    address to,
    uint256
  ) internal view {
    // Check constraints when `transferFrom` is called to facliitate
    // a transfer between two parties that are not `from` or `to`.
    if (from != msg.sender && to != msg.sender) {
      require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd");
    }

    if (from != address(0)) {
      // If not minting
      require(_getKYCStatus(from), "rOUSG: 'from' address not KYC'd");
    }

    if (to != address(0)) {
      // If not burning
      require(_getKYCStatus(to), "rOUSG: 'to' address not KYC'd");
    }
  }
```

This setup inadvertently restricts the admin's ability to burn tokens, meaning break the functionality of the burn function

## Tools Used

Manual

## Recommended Mitigation Steps

In case of burning from admin, allow to burn without checking the KYC status of the account.

```diff
  function _beforeTokenTransfer(
    address from,
    address to,
    uint256
  ) internal view {
+  bool isBurningByAdmin = (to == address(0) && hasRole(BURNER_ROLE, msg.sender));
+  if (!isBur
OUSG price can be staled.
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The use of stale price data from the Oracle in the conversion process between USDC, OUSG, and rOUSG can result in inaccurate conversions.

## Proof of Concept

From overview: 

        Background on the OUSG Price Oracle
        The current price of OUSG is pulled from the oracle contract RWAOracleExternalComparisonCheck.sol. This oracle uses the change in the SHV ETF price (determined by pulling from a Chainlink Oracle) to constrain what price can be set by Ondo Finance.

The `RWAOracleExternalComparisonCheck` contract provides the last known RWA price and the timestamp of this price via the `getPriceData` function:

```solidity
        /**
        * @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;
        }
```

HOwever, In the contracts `OUSGInstantManager` and `ROUSG`, the price is fetched from the Oracle without checks for data recency. Stale data can be returned without detection, risking inaccurate token conversions.

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

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


We can also see that there are many reasons the RWA Oracle cannot timely update prices such as Chainlink Price oracle stale price or a price change that violates the max absolute change threshold.

```solidity
    if (block.timestamp > round.updatedAt + MAX_CL_WINDOW) {
      revert ChainlinkOraclePriceStale();
    }

    // Chainlink price update must not be comparing the same rounds against
    // eachother
    if (round.roundId == lastSetRound.roundId) {
      revert ChainlinkRoundNotUpdated();
    }

    // Ensure at least `MIN_PRICE_UPDATE_WINDOW` seconds have passed since
    // last RWA price update
    if (block.timestamp < priceTimestamp + MIN_PRICE_UPDATE_WINDOW) {
      revert PriceUpdateWindowViolation();
    }

    int256 rwaPriceChangeBps = _getPriceChangeBps(rwaPrice, newPrice);
    // Never allow a price change that violates the max absolute cha
OUSGInstantManager will not provide instant redemption even when there is enough tokens
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#L430-L439


# Vulnerability details

## Impact

OUSGInstantManager will not allow instant redemptions in certain scenarios even when there are sufficient tokens available.

## Proof of Concept

Investors call the OUSGInstantManager.redeem function for instant redemption. If the required USDC amount exceeds minBUIDLRedeemAmount, the contract executes a BUIDL redemption to cover the full amount:

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

If usdcAmountToRedeem > usdcBalance, indicating insufficient USDC held by the contract, the OUSGInstantManager performs a BUIDL redemption for the minimum required amount of BUIDL. Any remaining USDC is retained in the contract for future redemptions:

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

If neither of the above conditions is met, indicating sufficient USDC already held by the OUSGInstantManager, it covers the redemption and fees without further BUIDL redemption.

The issue is that if usdcAmountToRedeem > usdcBalance but the OUSGInstantManager BUIDL balance < minBUIDLRedeemAmount, resulting in transaction failure due to insufficient balance.

Consider the following scenario:

- OUSGInstantManager USDC balance: 40k
- OUSGInstantManager BUIDL balance: 20k
- An investor intends to redeem 50k OUSG
- The redeem function triggers the second block (usdcAmountToRedeem > usdcBalance), attempting to redeem 250k (minBUIDLRedeemAmount) BUIDL
- The transaction fails at this point:
 
```solidity
    require(
      buidl.balanceOf(address(this)) >= minBUIDLRedeemAmount,
      "OUSGInstantManager::_redeemBUIDL: Insufficient BUIDL balance"
    );
```

- The OUSGInstantManager could provide instant redemption by redeeming only the 20k BUIDL balance, but there's currently no logic in place for this in the implementation.

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 implementing logic to handle such cases. For instance:

```solidity
    if (usdcAmountToRedeem >= minBUIDLRedeemAmount) {
      _redeemBUIDL(usdcAmountToRedeem);
    } else if (usdcAmountToRedeem > usdcBalance && buidl.balanceOf(address(this) > minBUIDLRedeemAmount)) {
      _redeemBUIDL(minBUIDLRedeemAmount);
      emit MinimumBUIDLRedemption(
        msg.sender,
        minBUIDLRedeemAmount,
        usdcBalance + minBUIDLRedeemAmount - usdcAmountToRedeem
      );

    else if (usdcAmountToRedeem <= (usdcBalance + buidl.balanceOf(address(this)))) {
      _redeemBUIDL(buidl.balanceOf(address(this)));
    } else {
      emit BUIDLRedemptionSkipped(
        msg.sender,
        usdcAmountToRedeem,
        usdcBalance - usdcAmountToRedeem
      );
    }
```

Also, update the _red
A sanctioned or non-KYC'd user can mint OUSG tokens
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#L323


# Vulnerability details


## Impact
If a sanctioned or non-KYC'd user calls the `mint` function in the `OUSGInstantManager` contract, they can mint OUSG tokens to any address they choose.

During the minting of OUSG tokens, the `msg.sender` and `to` address are checked in the `_beforeTokenTransfer` function. However, in this case, the `msg.sender` will be the address of the `OUSGInstantManager` contract.

```solidity
  function _mint(
    uint256 usdcAmountIn,
    address to    
  ) internal returns (uint256 ousgAmountOut) {
    // .... code

    //@audit-issue: If msg.sender is sanctioned, they can mint OUSG to an address which is not sanctioned.
    ousg.mint(to, ousgAmountOut);
  }
```

The problem arises because the original `msg.sender` is not checked and in the context of the OUSG contract, the `msg.sender` during the sub-calling is different.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Ensure that the original `msg.sender` is KYC'ed and not sanctioned.


## Assessed type

Invalid Validation
Admin/Burner should be able to burn shares of the sanctioned users (users who no longer have a KYC Registry)
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#L624-L640


# Vulnerability details

## Impact

The `rOUSG.sol` contract is part of Ondo Finance's ecosystem. A critical aspect of `rOUSG` involves compliance with Know Your Customer (KYC) regulations (aka sanctions lists), which are enforced through checks in the `_beforeTokenTransfer()` function to ensure only verified users can transact. The KYCRegistry acts as a whitelist for Ondo's permissioned tokens. OUSG and rOUSG token contracts query this registry to check that addresses are KYC verified before executing certain actions.
**Users are added and removed from the KYC list via permissioned functions.**
However, a notable flaw in the contract's design is the lack of an exception for administrative roles to bypass these checks for the purpose of burning tokens belonging to users who have become unverified or sanctioned after initially passing KYC verification.
This leads to a situation, where users who lose their KYC status or get sanctioned cannot move their tokens, effectively locking their funds within the protocol (sanctioned user's funds are locked). Another flaw is that even administrators or designated burners with `DEFAULT_ADMIN_ROLE/BURNER_ROLE` cannot redeem or burn these locked tokens, preventing the protocol from managing assets or complying with potential regulatory orders to seize assets.

## Proof of Concept

- Burn Flow:

```solidity
/**
 * @notice Admin burn function to burn rOUSG tokens from any account
 * @param _account The account to burn tokens from
 * @param _amount  The amount of rOUSG tokens to burn
 * @dev Transfers burned shares (OUSG) to `msg.sender`
 */
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);
}

function _burnShares(
  address _account,
  uint256 _sharesAmount
) internal whenNotPaused returns (uint256) {
  require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS");

  _beforeTokenTransfer(_account, address(0), _sharesAmount);

  uint256 accountShares = shares[_account];
  require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE");

  totalShares -= _sharesAmount;

  shares[_account] = accountShares - _sharesAmount;

  return totalShares;
}

function _beforeTokenTransfer(address from, address to, uint256) internal view {
  // Check constraints when `transferFrom` is called to facliitate
  // a transfer between two parties that are not `from` or `to`.
  if (from != msg.sender && to != msg.sender) {
    require(_getKYCStatus(msg.sender), "rOUSG: 'sender'
Users temporary will cannot redeem their tokens under certain condition
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#L335-L470


# Vulnerability details

## Impact

The `OUSGInstantManager.sol` contract governs the minting and redemption processes for `OUSG/rOUSG` tokens within the Ondo Finance ecosystem. The contract employs `minimumDepositAmount` and `minimumRedemptionAmount` as mechanisms to set thresholds for the least amount of USDC that can be used to mint OUSG tokens and the minimum amount of OUSG tokens that can be redeemed back into USDC, respectively. These parameters are crucial for maintaining operational integrity and preventing spam transactions.

```solidity
  // Minimum amount of USDC that must be deposited to mint OUSG or rOUSG
  // Denoted in 6 decimals for USDC
  uint256 public minimumDepositAmount = 100_000e6;

  // Minimum amount of USDC that must be redeemed for to redeem OUSG or rOUSG
  // Denoted in 6 decimals for USDC
  uint256 public minimumRedemptionAmount = 50_000e6;
```

However, they can also restrict user actions based on changing protocol conditions.
For example, a user who mints OUSG tokens under one set of conditions finds themselves can be unable to redeem their tokens when the `minimumRedemptionAmount` (or both `minimumDepositAmount` and `minimumRedemptionAmount`) is later increased (new `minimumRedemptionAmount` becomes greater than the "old" `minimumDepositAmount`). This change can trap the user's tokens within the protocol under the new conditions, despite their actions being compliant with the terms at the time of minting.

This also leads to broken protocol invariant: USDC is not permanently locked.

## Proof of Concept

1. Alice mints OUSG tokens when the `minimumDepositAmount` is 60,000 USDC, and the `minimumRedemptionAmount` is 50,000 OUSG.
2. The protocol later increases these thresholds to 80,000 USDC and 70,000 OUSG, respectively, to adjust to new economic conditions.
3. Alice, wanting to redeem her tokens, discovers she is unable to do so because her holdings do not meet the new minimum redemption threshold.

- [`OUSGInstantManager.sol#_mint()`](https://github.com/code-423n4/2024-03-ondo-finance/blob/main/contracts/ousg/ousgInstantManager.sol#L278-L324) function

```solidity
function _mint(
  uint256 usdcAmountIn,
  address to
) internal returns (uint256 ousgAmountOut) {
  require(
    IERC20Metadata(address(usdc)).decimals() == 6,
    "OUSGInstantManager::_mint: USDC decimals must be 6"
  );
  require(
    usdcAmountIn >= minimumDepositAmount,
    "OUSGInstantManager::_mint: Deposit amount too small"
  );
  _checkAndUpdateInstantMintLimit(usdcAmountIn);
  if (address(investorBasedRateLimiter) != address(0)) {
    investorBasedRateLimiter.checkAndUpdateMintLimit(msg.sender, usdcAmountIn);
  }

  require(
    usdc.allowance(msg.sender, address(this)) >= usdcAmountIn,
    "OUSGInstantManager::_mint: Allowance must be given to OUSGInstantManager"
  );

  uint256 usdcfees = _getInstantMintFees(usdcAmount