xanoutas/solidity-security-findings huggingface dataset:
The `rateLimiter` functionality in the `Throttle.sol` contract can be bypassed
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/libraries/Throttle.sol#L37-L65


# Vulnerability details

## Impact

The `rateLimiter` functionality in the `Throttle.sol` contract can be bypassed. The `issuance rate limiter` can be bypassed by `sandwiching` the `redemption transaction` with `two issuance transactions` by a `issuer`.

Let's consider the following scenario:
```
hourly issuance rate is 200
hourly redemption rate is 300
```
Consider the `lastAvailabe` amounts are as follows:
```
issuanceThrottle.lastAvailable is 200
redemptionThrottle.lastAvailable is 300
```

Now let's consider the following transaction execution:
```
1. The User B decides to call the redmption of 300 RTokens.

The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 200
redemptionThrottle.lastAvailable is 300

2. User A sees the above transaction and front runs it by calling the issuance for 100 RTokens.

The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 100
redemptionThrottle.lastAvailable is 400

3. Now the User B's redemption transaction of 300 RTokens is executed.

The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 400
redemptionThrottle.lastAvailable is 0

4. Now the User A sanwitches User B's redemption transaction by calling the issuance of 200 RTokens.

The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 0
redemptionThrottle.lastAvailable is 200
```

In this scenario, `User A` was able to `issue a total of 300 RTokens (100 + 200)` within a single block, effectively `bypassing the hourly issuance rate limit of 200`.

The reason this exploit works is due to the way the `Throttle.useAvailable` function `updates` the `lastAvailable values` for `issuance and redemption`. When a `redemption occurs`, the `issuanceThrottle.lastAvailable value is increased by the redemption amount`, allowing for a `higher issuance amount`.

This behavior can be exploited by alternating between issuance and redemption operations, effectively accumulating a higher available amount for issuance than the configured hourly rate limit as explained in the previous example.

## Proof of Concept

```solidity
    function useAvailable(
        Throttle storage throttle,
        uint256 supply,
        int256 amount
    ) internal {
        // untestable: amtRate will always be > 0 due to previous validations
        if (throttle.params.amtRate == 0 && throttle.params.pctRate == 0) return;

        // Calculate hourly limit
        uint256 limit = hourlyLimit(throttle, supply); // {qRTok}

        // Calculate available amount before supply change
        uint256 available = currentlyAvailable(throttle, limit); //@audit-info - get teh avialbel amount

        // Update throttle.timestamp if available amount changed or at limit
        if (available != throt
Incorrect auction trade execution due to `issuancePremium` being applied in the `RecollateralizationLib.basketRange` computation
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/mixins/RecollateralizationLib.sol#L116
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BasketHandler.sol#L429
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/plugins/assets/RTokenAsset.sol#L66
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BasketHandler.sol#L446


# Vulnerability details

## Impact

The `BasketHanlder.price(bool)` function is used to return the price of a `Basket Unit`. The passed in `boolean input parameter` indicates whether to apply the issuance premium to the high price. The `BasketHandler.price(bool)` function is called in the `RecollateralizationLib.basketRange` function to determine the `BasketRange.top` and `BasketRange.bottom` values for a given `TradingContext` and `AssetRegistry`.

```solidity
        (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(false); 
```

As you can see when calculating the `basket unit price` the `issuance premium` is not applied (`false` is passsed in). But there is a scenario where the `issuance premium` is applied as explained below:

1. The `Baket` can include a different `RTokenCollateral` (Not the RToken which the current basket maps to) as one of its collaterals. This is because when the primary basket is set only collaterals which are not allowed are `rsr`, `rToken` and `stRSR` as verified in the `BasketHandler.requireValidCollArray` function.
2. During the `BackingManager.rebalance` function the `RecollateralizationLib.prepareRecollateralizationTrade` is called which inturn calls the `BasketHandler.basketRange` function. Here the `ctx.bh.price(false)` is called.
3. Now to determine the low and high price of the `Basket Unit`, the `BasketHandler.price(bool)` function iterates through all the collaterals in the basket and calculates the `low and high` price of each of the collaterals as shown below:

```solidity
                (uint192 lowP, uint192 highP) = coll.price();
```

4. Since `RTokenCollateral` is also an active collateral of the `Basket` the `RTokenAsset.price()` is called which inturn calls the `RTokenAsset.tryPrice` function. In the `tryPrice` function the `basketHandler.price(true)` call is made which applies the `issuance premium` to the price of all of the collaterals which back the `RTokenCollateral`, as shown below:

```solidity
        (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(true); 
```

5. As a result the `RTokenCollateral` price returned by the `RTokenAsset.price()` accounts for the `issuancePremium` if there is a depeg between the (tok/ref) of the respective collateral tokens of the RTokenCollateral. And this price which has the `issuancePremium` accounted for, is returned to the `BasketHandler.price(bool)` function.

6. Eventhough the `quantity` of the `RTokenCollateral` is not applied the `issuancePremium (since it bool is false)` its price is applied the `issuancePremium` as explained
Potential for Inaccurate Melting Due to Balance Discrepancies
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Furnace.sol#L65


# Vulnerability details

## Vulnerability Details
The core problem lies in the fact that the function uses `lastPayoutBal` to calculate the melt amount, but then updates `lastPayoutBal` based on the current balance. This can lead to inconsistencies if the actual balance has changed between melt operations.

## Code Snippet

```solidity
function melt() public {
    // ... (earlier parts of the function)

    uint256 amount = payoutRatio.mulu_toUint(lastPayoutBal);
    
    lastPayout += numPeriods;
    lastPayoutBal = rToken.balanceOf(address(this)) - amount;
    
    if (amount != 0) rToken.melt(amount);
}
```

## The problem
1. `amount` is calculated based on `lastPayoutBal`.
2. `lastPayoutBal` is then updated using the current balance (`rToken.balanceOf(address(this))`).
3. If the actual balance has changed since the last melt (due to deposits or other operations), this new `lastPayoutBal` won't accurately reflect the starting balance for the next period.

## Scenario
1. Initial `lastPayoutBal`: 1000 tokens
2. 500 tokens are deposited to the contract
3. `melt()` is called:
   - `amount` is calculated based on 1000 tokens
   - `lastPayoutBal` is updated to `(1500 - amount)`, which doesn't accurately represent the starting balance for the next period

## Impact
- The melting process may not accurately account for new deposits or withdrawals.
- Over time, this could lead to either over-melting or under-melting compared to the intended behavior.

## Potential fix
To address this, the function should either:
1. Use the current balance to calculate the melt amount, or
2. Keep track of deposits/withdrawals between melt operations.

A possible implementation of the first approach:

```solidity
function melt() public {
    // ... (earlier parts of the function)

    uint256 currentBalance = rToken.balanceOf(address(this));
    uint256 amount = payoutRatio.mulu_toUint(currentBalance);
    
    lastPayout += numPeriods;
    lastPayoutBal = currentBalance - amount;
    
    if (amount != 0) rToken.melt(amount);
}
```

This change ensures that melting is always based on the current balance, accounting for any changes since the last melt operation.



## Assessed type

Context
RSR Stakers Unintentionally Slashed During Collateral Depegging Despite Sufficient Collateral Backing
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L300
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L317-L321
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L163
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L176


# Vulnerability details

## Description
A collateral that has depegged, can still be considered SOUND, as long as the current price remains within the boundaries of the ORACLE_ERROR.  In other words, if, ORACLE_ERROR is 1% at the peg price of 1e8, a collateral can be SOUND down to 0.99e8 and up to 1.01e8.  Because of this, a depegged collateral can be used to `rebalance()` the `BackingManager` as long as it is the most-in-surplus.

With this, a call to `isEnoughToSell()` on `TradeLib.sol` is used to determine if there is a sufficient amount of collateral in the `BackingManager.sol` to cover the rebalancing.  
```
TradeLib.isEnoughToSell(
                        reg.assets[i],
                        ctx.bals[i].minus(needed),
                        low,
                        ctx.minTradeVolume
                    )
```
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L317-L322

The problem is that `isEnoughToSell()` takes the current collateral price (in this case, the depegged price) and uses it as the `low` parameter. 
e.g. At peg of 1e8, the low may be `990000000000000000` at a 1% oracle error, however, at a depegged price of 0.99e8, the low is now `980100000000000000`
```
(uint192 low, uint192 high) = reg.assets[i].price();
```

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L300

Because the `low` parameter is now lower that it would normally be, the `minTradeSize`, used in `isEnoughToSell` will actually be inflated due to dividing by a smaller price:
e.g. At-peg, `minTradeSize = 101010101010101010101011 [1.01e23]`
     De-peg, `minTradeSize = 102030405060708091011122 [1.02e23]`
```
    function minTradeSize(uint192 minTradeVolume, uint192 price) private pure returns (uint192) {
        // {tok} = {UoA} / {UoA/tok}
        uint192 size = price == 0 ? FIX_MAX : minTradeVolume.div(price, CEIL);
        return size != 0 ? size : 1;
    }
```

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L176

Consequently, because `isEnoughToSell` compares whether the `ctx.bals[i].minus(needed)` is >= `minTradeSize`, there are scenarios where at-peg `isEnoughToSell` returns true, utilizing the collateral to rebalance, but when depegged it r
Inflated rsrTotal Due to Unchecked DAO Fee Calculation in totals() Function
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Distributor.sol#L204


# Vulnerability details

## Vulnerability Details
The `totals()` function is responsible for calculating the total shares for RSR and RToken distribution. It's a critical component for ensuring proper revenue distribution and maintaining system invariants.

The issue is in the DAO fee calculation part of the function. The current implementation can lead to an inflation of the `rsrTotal`, potentially breaking system invariants and causing unexpected behavior in revenue distribution.

## Details
1. The function calculates base totals correctly.
2. When a DAO fee is present, it adds an additional amount to `rsrTotal`.
3. This addition can cause the total distribution to exceed 100% (or `MAX_DISTRIBUTION`).
4. The function doesn't adjust other distributions to accommodate the DAO fee, leading to an inflated total.

## Code Snippet
```solidity
if (feeRecipient != address(0) && feeNumerator != 0) {
    revTotals.rsrTotal += uint24(
        (feeNumerator * uint256(revTotals.rTokenTotal + revTotals.rsrTotal)) /
        (feeDenominator - feeNumerator)
    );
}
```

## Impact
1. Violation of system invariants: The total distribution may exceed 100%.
2. Incorrect revenue distribution: Other recipients may receive less than their intended share.
3. Potential for system instability or unexpected behavior in other components that rely on these totals.

## Scenario
Suppose the initial distribution is:
- RToken: 50%
- RSR: 50%
- DAO Fee: 10%

The `totals()` function will return:
- RToken: 50%
- RSR: 55.55% (50% + 5.55% DAO fee)

Total distribution: 105.55%, which exceeds 100%.

## Fix
To address this issue, the function should be modified to incorporate the DAO fee without inflating the total distribution. Here's a potential fix:

1. Calculate the DAO fee separately.
2. Adjust the RSR and RToken totals proportionally to accommodate the DAO fee.
3. Ensure the final total (including the DAO fee) equals `MAX_DISTRIBUTION`.

Example implementation:

```solidity
function totals() public view returns (RevenueTotals memory revTotals) {
    // Calculate base totals as before
    ...

    DAOFeeRegistry daoFeeRegistry = main.daoFeeRegistry();
    if (address(daoFeeRegistry) != address(0)) {
        (address feeRecipient, uint256 feeNumerator, uint256 feeDenominator) = main.daoFeeRegistry().getFeeDetails(address(rToken));

        if (feeRecipient != address(0) && feeNumerator != 0) {
            uint256 totalBeforeFee = revTotals.rTokenTotal + revTotals.rsrTotal;
            uint256 daoFee = (totalBeforeFee * feeNumerator) / feeDenominator;
            
            // Adjust RSR and RToken totals proportionally
            revTotals.rsrTotal = uint24((uint256(revTotals.rsrTotal) * (totalBeforeFee - daoFee)) / totalBeforeFee);
            revTotals.rTokenTotal = uint24((uint256(revTotals.rTokenTotal) * (totalBeforeFee - daoFee))
RToken can manipulate distribution to avoid paying DAO fees
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Distributor.sol#L204-L227
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Distributor.sol#L240


# Vulnerability details

Revenue produced by RTokens is sold for both RSR and RTokens according to a distribution defined in the [`Distributor`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Distributor.sol). The `BackingManager` [splits](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L247-L261) the collateral tokens to be sold proportionately to the RSR/RToken distribution ratio and sends them to the `rsrTrader` and `rTokenTrader`. When trades settle, the obtained RSR or RTokens are sent to the `Distributor`, which distributes them no longer according to the RSR/RToken ratio but to the different destinations for the specific token. The sum of all destinations for each token is used to derive the ratio.

The DAO fee is added to the RSR share of the initial split and paid when RSR is distributed.

However, the current implementation allows governance to manipulate the distribution settings without much effort in a way that can significantly reduce the amount of DAO fees paid.

This can be achieved through a combination of two different root causes:
- an RSR destination can be added that prevents rewards from being immediately distributed
- the RSR/RToken ratio is calculated twice: once in the  `BackingManager`, and once in the `Distributor`, and it is can be modified between the two

Essentially, the distribution can be set in a way that temporarily accumulates RSR revenue in in the `rsrTrader` according to one RSR/RToken ratio, and then later redistributed with a different ratio.

## Impact
RTokens can avoid paying most of the DAO fee

## Proof of Concept
Assuming a 10% DAO fee, governance can execute the following steps to pay only about 1% in fees:

1. Set the distribution such that `rsrTotal == 0` before DAO fees [are added](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Distributor.sol#L221).
2. Add the RSR token itself as a distribution target. Like stRSR, [RSR](https://etherscan.io/token/0x320623b8e4ff03373931769a31fc52a4e78b5d70#code) does not allow transferring to itself, so the distribution will always fail.
3. 10% of revenue will accumulate as RSR in the `rsrTrader`.
4. After some time, change the distribution such that `rTokenTotal == 0` and add another destination with non-zero `rsrDist` (e.g., `stRSR`).
5. Remove RSR as a distribution target and call `rsrTrader.distributeTokenToBuy()`.
6. Only 10% of the accumulated RSR will go to the DAO, which is effectively 1% of the total revenue.
7. Repeat this process as needed.

## Tools Used
Manual review

## Recommended Mitigation
Upgradeability of collateral assets opens up the doors to reentrancy vulnerabilities
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L148-L154
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L214-L224


# Vulnerability details

This issue revisits reentrancy concerns that were raised in previous audits of the protocol ([Issue #297](https://github.com/code-423n4/2023-01-reserve-findings/issues/297), [Issue #347](https://github.com/code-423n4/2023-01-reserve-findings/issues/347), [Issue #95](https://github.com/code-423n4/2023-01-reserve-findings/issues/95), [Issue #304](https://github.com/code-423n4/2023-01-reserve-findings/issues/304)). While the protocol team initially decided to mitigate these concerns by not supporting ERC-777 assets, this approach does not work if the protocol aims to be secure against arbitrary upgrades of collateral assets as per the in-scope ERC-20 [behaviours](https://github.com/code-423n4/2024-07-reserve/tree/3f133997e186465f4904553b0f8e86ecb7bbacbf?tab=readme-ov-file#erc20-token-behaviors-in-scope).

The referenced findings remain largely relevant to the current version of the protocol. The `RToken` contract, for instance, is vulnerable to reentrancy via external token call in the functions `issue()`/`issueTo()` and `redeem()`/`redeemTo()`. These functions interact with multiple ERC20 tokens that make up the collateral basket.

If any of the tokens in the basket were to implement transfer hooks through an upgrade, an attacker could exploit this vulnerability to reenter the protocol in an inconsistent state.

For example, in the `RToken.issue()` function:

```solidity
function issue(address recipient, uint256 amount) external {
    // ... checks and effects ...
    _mint(recipient, amtRToken);
    
    for (uint256 i = 0; i < erc20s.length; ++i) {
        IERC20Upgradeable(erc20s[i]).safeTransferFrom(
            issuer,
            address(backingManager),
            deposits[i]
        );
    }
}
```

If an attacker obtained control flow on any of the `transferFrom()` calls to the erc20s, they could call back into the protocol before all transfers are complete, allowing them to steal funds as per the findings referenced above.

## Impact
Theft of funds

## Proof of Concept
1. A collateral token is upgraded to implement ERC-777, or simply hacked
2. Attacker calls `RToken.issue()` with a large amount
3. During the token transfer, the upgraded token's `tokensToSend` hook is triggered
4. Inside this hook, the attacker calls `BackingManager.manageTokens()`
5. `manageTokens()` sees an inconsistent state where `RToken.basketsNeeded` has been updated but not all collateral has been transferred
6. This leads to incorrect calculations in `manageTokens()`, potentially allowing the attacker to extract excess collateral

## Tools Used
Manual review

## Recommended Mitigation Steps
As pointed out by the sponsor [in this comment](https://github.com/code-423n4/2023-01
Governance can still disable auctions
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Broker.sol#L199
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Broker.sol#L219


# Vulnerability details

This issue builds upon the previous finding reported in https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20, where it was noted that governance (controlled by stRSR stakers) has an incentive to disable RSR auctions to protect their stakes in undercollateralized scenarios.

As a result, the `setBatchTradeDisabled()` and `setDutchTradeDisabled()` functions in the Broker contract were [removed](https://github.com/reserve-protocol/protocol/commit/b4200598b43445e3a7961f8e22e625038046158c#diff-0ae6879ac597eef7843c322b5f66497aa4e1b0e8ca1c351d9c7870373565882d). However, this mitigation did not effectively address the issue, as governance still retains the ability to disable auctions through alternative means.

Specifically, the [`setBatchAuctionLength()`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Broker.sol#L197) and [`setDutchAuctionLength()`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Broker.sol#L219) functions allow governance to set the auction length to 0, which also has the effect of disabling the respective auction type.

Furthermore, while direct disabling of Dutch auctions for specific collateral types is no longer possible, this is still easy to achieve permanently at a low cost. By bidding at 50% over the best price in a rebalancing DutchTrade involving RSR, anyone can trigger a violation report. Once reported, governance could simply choose never to re-enable the auction.

## Impact
In undercollateralized scenarios, RToken holders can be forced to bear disproportionate losses to the benefit of RSR stakers.

## Proof of Concept
1. Governance calls `setBatchAuctionLength(0)`.
2. Governance actor bids 50% over best price in a rebalancing DutchTrade with RSR, triggering a report violation.
3. Governance doesn't re-enable RSR auctions.
4. In undercollateralization, RSR auctions can't occur.
5. RToken users are forced to redeem from baskets, causing further depegging.
6. stRSR holders effectively cannot bear any losses, while continuing to earn revenue.

## Tools Used
Manual review

## Recommended Mitigation Steps
Modify the `setBatchAuctionLength()` and `setDutchAuctionLength()` functions to only allow non-zero values within the specified range. Additionally, consider disallowing dutch auctions from being disabled for RSR.






## Assessed type

Governance