xanoutas/solidity-security-findings huggingface dataset:
Critical Vulnerability in ONDO Contract: Addressing the Rug Pull Risk in the Burn 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/rOUSG.sol#L618-L640


# Vulnerability details

Crypto is the land of rug pulls, and even the most credible players can flee with investors’ money. Recall SBF, who defrauded FTX users and laundered their funds. Also, the Africrypt exchange founders stole almost $4 billion in one of the most extraordinary rugs in crypto.

## Impact
As stated by the contract in the docs "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 impact of the identified vulnerability in the ONDO contract's burn function is significant and exposes users to the risk of losing their OUSG tokens. The current implementation allows the OUSG tokens, which belong to `_account`, to be transferred to `msg.sender`, which is the address with the burners role. This creates a potential for a rug pull, where the contract owner or administrator could maliciously withdraw the OUSG tokens. As long as the function exist and can send users money to the caller and not te actual owner, Even if the burners role is assigned to only trusted accounts, the risk of a rug pull remains high, jeopardizing the security and trustworthiness of the ONDO contract and its ecosystem. 

## Proof of Concept
1.The function burns the shares on an account holding rOUSG  (address _account,
  uint256 _amount)
2.BURNS the share of that account     _burnShares(_account, ousgSharesAmount);
But transfer of the equivalent OUSG is sent to the caller of the burn function is not the account holder. Thus this function allows a burner to pass in any account burn the shares associated with it an get the OUSG token value . 

 ousg.transfer(
    msg.sender,
    ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
  ); 
3.Since wrap rOUSG is still OUSG but wrapped the token should be sent back to the account instead and not msg.sender
### Code Snippet Before Mitigation
 
```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);
}
```



## Tools Used

Manual code analysis was used to identify and analyze the vulnerability in the ONDO contract.

## Recommended Mitigation Steps

To mitigate the identified vulnera
Initialization functions can be front-run
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#L102


# Vulnerability details

## Impact
Several implementation contracts have initialization functions that can be front-run, allowing an attacker to incorrectly initialize the contracts.

## Proof of Concept
An attacker could front-run these functions and initialize the contracts with malicious values
```
 function initialize(
    address _kycRegistry,
    uint256 requirementGroup,
    address _ousg,
    address guardian,
    address _oracle
  ) public virtual initializer {
    __rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle);
  }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Maybe review the solidity docs for proper implementation


## Assessed type

Upgradable
Calling mint function instead of internal _mint function
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#L323


# Vulnerability details

## Impact
The mint in "ousg.mint(...)" does not exist, so calling it directly will fail since the ERC20 base contract does not have a mint function.

## Proof of Concept
There is a bug in the _mint function calling ousg.mint(...) instead of the super _mint.

```
  ousg.mint(to, ousgAmountOut);
```

This will fail since the ERC20 base contract does not have a mint function. It only has an internal "_mint" function.

## Tools Used
Manual review

## Recommended Mitigation Steps
The fix would be to call the internal _mint function instead:

```
ousg._mint(to, ousgAmountOut);
```


## Assessed type

DoS
`rOUSG::wrap` can be leveraged by a malicious user to mint `rOUSG` for zero cost under certain conditions
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#L437


# Vulnerability details

## Impact
A malicious user can leverage the `wrap()` function in the `rOUSG` contract to mint `rOUSG` for no cost engineering a loss for the Ondo protocol under certain conditions.

Consider the scenario presented below for this attack:

- Alice executes `wrap(uint256 _rOUSGAmount)`.
- The `ousgSharesAmount` amount of `rOUSG` shares calculated for the `wrap()` transaction is minted to Alice's balance
- The contract attempts to transfer `oUSG` from Alice into the `rOUSG` contract as payment for wrapping into `rOUSG` but the transfer fails
- The return value is `false` because the `transferFrom()` doesn't revert if it fails for any reason according to the EIP specification and implementation the `oUSG` contract utilizes.

As a result, Alice has gained `rOUSG` for zero cost. She can repeat this for as long as the transfers continue to return `false` essentially minting `rOUSG` each time free of charge.

This issue rather rare, will occur when one of the following is true in the `oUSG` contract. Note that the `transferFrom()` call returning false is not limited to the scenarios described below:

- Since `oUSG` is an ERC-20 token that mimics OZ's implementation, it returns true/false that determines whether a `transferFrom()` call is successful or not.
- The `oUSG` token contract would need to be set to paused/locked during the time of the `rOUSG` mint.


## Proof of Concept
```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); // @audit mint succeeds but the transferFrom() below fails hence free mint of `rOUSG`
@>  ousg.transferFrom(msg.sender, address(this), _OUSGAmount); // call could return false with no `oUSG` transferred
    emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
    emit TransferShares(address(0), msg.sender, ousgSharesAmount);
  }
```

Notice that the return value of the `ousg.transferFrom(msg.sender, address(this), _OUSGAmount);` call above in the `wrap()` function is not checked. Rather yet, it is not implementing a `safeTransferFrom()` for the call that can revert the transaction when the transfer fails hence preventing this exploit.

## Tools Used
Manual review + foundry

## Recommended Mitigation Steps
Our recommendation is that the Ondo team should handle the return value of the `ousg.transferFrom()` call by enforcing a revert of the transaction when the return value is `false`. Or, the `ousg.transferFrom(msg.sender, address(this), _OUSGAmount);` function can be further protected to use `safeTransferFrom()` as can be seen below:

```diff
+ ousg.safeTransferFrom(msg.sender, address(this), _OUSGAmount);
- ousg.transferFrom(msg.sender, address(this), _OUSGAmount);
`
User tokens are sent to the admin instead of being returned to the source _account  after burning shares.
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#L634-L637


# Vulnerability details

## Summary
The `rOUSG::burn()` function is restricted to be called only by addresses with the `BURNER_ROLE`. This function reduces the `share balance` of the `_account` by `ousgSharesAmount`, effectively removing those `shares` from circulation.

Once the `shares` are burned, the function transfers an equivalent amount of `tokens` to the `msg.sender`. The amount of tokens transferred is calculated by dividing `ousgSharesAmount` by `OUSG_TO_ROUSG_SHARES_MULTIPLIER`. This is done to convert `shares` back to `tokens`.

Since `msg.sender` is the address with the `BURNER_ROLE`, the tokens are transferred to this address and not to the `_account` that own them.

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

```solidity
  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
    ); // @audit Tokens sent to the caller(BURNER_ROLE)

    emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount));
    emit TransferShares(_account, address(0), ousgSharesAmount);
  }
```
The vulnerability lies in the line:
```solidity
ousg.transfer(
      msg.sender,
      ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    );
```
In this line, the burned tokens are transferred to `msg.sender`, which is the address with the `BURNER_ROLE (admin)`. As a result, `the tokens are sent to the admin instead of being returned to the _account from which they were burned`.

## Impact
Sending the tokens to the `admin` means that the user who is the owner of the `_account` loses their `shares` through the burning process as well as their `tokens` permanently. 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Adjust the `burn()` function to ensure that the burned tokens are returned to the `_account` as shown here:

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

    // @audit Return the burned tokens to the _account instead of the admin
    ousg.transfer(
        _account,
        ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER
    );
    emit Transfer(address(0), _account, getROUSGByShares(ousgSharesAmount));
    emit TransferShares(_account, address(0), ousgSharesAmount);
}
```





## Assesse
REENTRANCY
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSGFactory.sol#L70
https://github.com/code-423n4/2024-03-ondo-finance/blob/be2e9ebca6fca460c5b0253970ab280701a15ca1/contracts/ousg/rOUSGFactory.sol#L108


# Vulnerability details

## Impact
The provided code is not directly vulnerable to reentrancy attacks. However, there are still potential risks associated with external calls made within the deployRebasingOUSG function, particularly the call to rOUSGProxied.initialize(). Depending on the implementation of the initialize function in the ROUSG contract, it could potentially trigger reentrancy if it makes external calls to untrusted contracts or if it doesn't follow best practices to avoid reentrancy.



## Recommended Mitigation Steps

To mitigate these risks, it's essential to ensure that the initialize function in the ROUSG contract and any other external calls within the deployRebasingOUSG function are carefully audited and follow best practices for secure contract design.




## Assessed type

Reentrancy
CONTROLLED LOW-LEVEL CALL
Audit: 2024-03-ondo-finance-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The contract was using delegatecall() or call() which was accepting address controlled by a user. This can have devastating effects on the contract as a delegate call allows the contract to execute code belonging to other contracts but using it’s own storage. This can very easily lead to a loss of funds and compromise of the contract.

## Proof of Concept

    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) {
            address target = exCallData[i].target;
            // Ensure that only trusted contracts can be called
            require(trustedContracts[target], "ROUSGFactory: Only trusted contracts allowed");

            try target.call{value: exCallData[i].value}(exCallData[i].data) returns (bytes memory ret) {
                results[i] = ret;
            } catch Error(string memory errorMsg) {
                revert(errorMsg);
            } catch (bytes memory) {
                revert("External call failed");
            }
        }
    }

## Tools Used
SolidityScan

## Recommended Mitigation Steps
Do not allow user-controlled data inside the delegatecall() and the call() function.




## Assessed type

call/delegatecall
Contract logic for instant redemptions is not functioning as expected due to Redemption Limit Exceedance in OUSGInstantManager Contract.
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#L388-L456


# Vulnerability details

## Impact
The `OUSGInstantManager` contract is designed to enable instant minting and redemption of OUSG and rOUSG tokens, with a focus on the redemption logic. A critical issue was identified in the enforcement of the instantRedemptionLimit by the `InstantMintTimeBasedRateLimiter`. This flaw significantly impacts the contract's functionality, particularly in the `redemption` process.


Redemption Logic Flaw: The logic for checking against the `instantRedemptionLimit` during redemptions is flawed. This issue prevents users from redeeming the minimum required amount of BUIDL tokens, which is assumed to be 250,000 BUIDL tokens.

Storage Variables: 

- `instantRedemptionLimit`: A `uint256` variable in slot 7, representing the limit for instant redemptions.
- `currentInstantRedemptionAmount`: A `uint256` variable in slot 8, tracking the current amount of instant redemptions.
- `minimumRedemptionAmount`: A `uint256` variable in slot 16, likely representing the minimum redemption amount.

Rate Limiters: The contract utilizes rate limiters to control the amount of USDC a user can redeem within a specific timeframe, aiming to prevent abuse or manipulation. However, the implementation of these limiters is flawed, leading to the observed issue.

The redemption process involves several steps, including approving the `redemption` amount, calling the `redemption` function, and handling the redemption logic within the `_redeem` internal function. This function performs checks on USDC and BUIDL token decimals, calls an external oracle for the current OUSG price in USDC, enforces a minimum `redemption` amount, and calculates `redemption` fees.

THE PROBLEM IS THAT: IT WILL ALWAYS REVERT!

The `_redeem` function interacts with two potential rate limiters: the `InstantMintTimeBasedRateLimiter` and an optional `investorBasedRateLimiter`. The logic error in `_checkAndUpdateInstantRedemptionLimit` prevents users from `redeeming` the minimum required amount of BUIDL tokens, disrupting the intended functionality. If the `investorBasedRateLimiter` is active, it checks and updates the redeem limit for the user with `investorBasedRateLimiter.checkAndUpdateRedeemLimit(msg.sender, usdcAmountToRedeem);`.

The impact of this logic issue is multifaceted. It affects the contract's functionality, user experience, and security. Users are unable to redeem tokens.

## Proof of Concept
Alice, a user with a significant amount of OUSG tokens, intends to redeem a large amount of USDC exceeding the intended `instantRedemptionLimit`.
Initially, Alice's redemption attempt fails due to the logic error in `_checkAndUpdateInstantRedemptionLimit`. The transaction reverts with an error message indicating the redemption amount exceeds the allowed limit```[FAIL. Reason: Settlement: exceeds allowed amount]```.
No oracle sanity check in ROUSG getOUSGPrice function
Audit: 2024-03-ondo-finance-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Unlike the `getOUSGPrice` function in `OUSGInstantManager`, the `getOUSGPrice` in `ROUSG` lacks a sanity check for unexpectedly low prices. The lack of this check allows `ROUSG` to still work if the oracle returns a zero or unexpectedly low price.

## Proof of Concept

In OUSGInstantManager `getOUSGPrice` has a sanity check to ensure that the price is not below a threshold.

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

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

However `ROUSG` is lacking this sanity check.

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

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add the sanity check to `ROUSG`, if a minimum is not desired then just ensure it is above zero in case the oracle is not returning a price. Ideally this would be the same function as used in OUSGInstantManager which can be inherited from a common contract to avoid code duplication.


## Assessed type

Oracle
Fee on Transfer
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#L278
https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L388


# Vulnerability details

### [M-1] Clearly there is fee on transfer on the `OUSGInstantManager::_mint` and `OUSGInstantManager::_redeem` functions

**Description** the `OUSGInstantManager::_mint` and `OUSGInstantManager::_redeem` functions will charge a fee on the transfer Contrary to what was said in docs. it should be clearly mentioned in the docs that there is a fee on transfer.

**Impact** Breaking the functionality of the protocol Contrary to what was said in the docs. it just breaks the logic of the function and operate in a different way than what was said in the docs.

**Proof of Concept:** every time a user or a role wants to call the  `mint` or `redeem` functions (or any other function that calls the `_redeem` and `_mint` functions) they will be charged a fee. 
 

**Recommend Mitigation** mention this thing in the docs and known issues and modify and review the ERC20 token behaviors chart.






## Assessed type

ETH-Transfer
No oracle staleness 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/rOUSG.sol#L379
https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L480


# Vulnerability details

## Impact

The oracle returns a price and timestamp but the timestamp is ignored. This allows the protocol to use stale price data and could lead to malicious users deliberately taking advantage of this for a profit.

## Proof of Concept

*2 occurrences of this issue*

[https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L480](https://github.com/code-423n4/2024-03-ondo-finance/blob/78779c30bebfd46e6f416b03066c55d587e8b30b/contracts/ousg/ousgInstantManager.sol#L480-L481)

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

```solidity
(price, ) = oracle.getPriceData();
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Validate the timestamp, ideally within a minimum threshold of block.timestamp so the protocol is automatically protected from outdated prices.


## Assessed type

Oracle