xanoutas/solidity-security-findings huggingface dataset:
Users are unable to bump their nonce
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L922
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L166


# Vulnerability details

## Description

Users can grant an allowance or delegate their voting power by signing a message, which is then used by the user to whom the allowance or delegation should be given. However, there is no mitigation for cases when a signature is signed, and the signer wants to cancel it.

## Proof of concept

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L922

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L166

The signatures have an expiration after which they will become unusable, but sometimes the users might wish to cancel them earlier than the expiration date, but they don't have such an option.

## Recommendation

Consider adding functionality that would bump user nonces in `StRSR` and delegation nonces in `StRSRVotes`.



## Assessed type

Other
`BackingManager` is unable to rebalance if RSR is not registered
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#L358


# Vulnerability details

## Description

The `BackingManager` takes care of the backing of its `RToken` by constantly balancing the amounts of the respective underlying assets with trades. It tries to sell the token it has more of than necessary for the token it has less of than needed. If it does not have a good candidate token to sell, it will choose to sell the `RSR` token. However, if the `RSR` is not registered in the `AssetRegistry`, it cannot rebalance.

## Proof of concept

If the respective `RToken` system does not intend to use the `RSR` token as revenue or a collateral asset, the owners will not register it to the `AssetRegistry`. However, if it is not registered in the `AssetRegistry`, then if such a scenario as the above one occurs, the `BackingManager` would be unable to rebalance due to an out-of-bounds revert [here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/mixins/RecollateralizationLib.sol#L358). Note that this behavior is documented for the further array accesses. Still, it should not behave this way, as it breaks core functionality.

Normally, if RSR was registered but the `BackingManager` did not have enough of it, it would choose to go for the haircut (i.e., decrease the number of baskets needed). There should not be a limitation like this if the system owners do not wish to slash governance or simply do not wish to include the `RSR`.

# Impact and likelihood

Since this issue breaks a core protocol functionality, this issue should be judged as **MEDIUM** severity.

## Recommendation

Consider checking whether the `rsrIndex` was assigned a correct value if it should be used as the sell asset:

```diff
        // Use RSR if needed
-       if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) {
+       if (address(trade.sell) == address(0) && address(trade.buy) != address(0) && rsrIndex != reg.erc20s.length) {
            (uint192 low, uint192 high) = reg.assets[rsrIndex].price(); // {UoA/RSR}

            // if rsr does not have a registered asset the below array accesses will revert
            if (
                high != 0 &&
                TradeLib.isEnoughToSell(
                    reg.assets[rsrIndex],
                    ctx.bals[rsrIndex],
                    low,
                    ctx.minTradeVolume
                )
            ) {
                trade.sell = reg.assets[rsrIndex];
                trade.sellAmount = ctx.bals[rsrIndex];
                trade.prices.sellLow = low;
                trade.prices.sellHigh = high;
            }
        }
```



## Assessed type

Error
Invalid assets can still be used along the protocol
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/AssetRegistry.sol#L213
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Main.sol#L125


# Vulnerability details

## Description

Adding an asset to the `AssetRegistry` requires the asset to be valid inside the `AssetPluginRegistry`. This condition is also enforced during the upgrade of the system. However, if an asset is still being used and it was deprecated or invalidated in the `AssetPluginRegistry`, it is still valid in the respective `RToken` system.

## Proof of concept

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/AssetRegistry.sol#L213

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Main.sol#L125

# Impact and likelihood

An asset deprecation or invalidation means something is wrong with the asset; for example, an issue with the asset's smart contract was discovered. The presence of `AssetPluginRegistry` hints that these events should not be ignored for assets in use. Therefore, the severity of this issue should be considered **MEDIUM**.

## Recommendation

Consider calling `validateCurrentAssets` during `assetRegistry.refresh` and do not allow invalid assets to be used.


## Assessed type

Invalid Validation
Protocol does not implement protection against upgradeable ERC-20s
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/AssetRegistry.sol#L201-L208


# Vulnerability details

## Description

ERC-20 tokens, whose logic can be upgraded to a new implementation, could potentially introduce changes that could mean undetermined vulnerabilities for the protocol. Since the protocol does not implement any upgrade detection for such tokens, it can be vulnerable to such changes.

## Proof of concept

For a token like [TUSD](https://etherscan.io/token/0x0000000000085d4780B73119b644AE5ecd22b376#code), which has a proxy and implementation contract, if the implementation behind the proxy is changed, it can introduce features which break the protocol, like changing the balance over time like a rebasing token or even inject some malicious code unbeknownst to the owners.

# Impact and likelihood

The likelihood of such an issue occurring is **LOW**, and the impact should be deemed **HIGH** since it can not be determined. Therefore, the severity of this issue should be judged **MEDIUM**.

## Recommendation

Consider introducing a logic that would freeze interactions with the token if an upgrade was detected (for example [TUSD adapter](https://github.com/makerdao/dss-deploy/blob/7394f6555daf5747686a1b29b2f46c6b2c64b061/src/join.sol#L322) used by MakerDAO).



## Assessed type

Upgradable
Stake rate can be updated by functions that should not update it
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L223
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L251


# Vulnerability details

## Description

The inline docs of two functions in the `StRSR` contract mention that the `stakeRate` should remain unchanged after calling these functions. Specifically:

- [`stake`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L223)
- [`unstake`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L251)

However, both of these functions perform a call to `_payoutRewards` first, which updates the `stakeRate`:

- [in `stake`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L230)
- [in `unstake`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L266)

## Proof of concept

As we can see in the [`_payoutRewards`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L593) function, the `stakeRate` can be updated to a new value [here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L621-L623) every time the `stakeRSR` value is updated (which can be often).

# Impact and likelihood

The likelihood of this issue occurring is **HIGH**, as the assumption will be broken every time some rewards are paid out during `stake` or `unstake`. The impact of this should be considered **LOW**, as no funds are at risk, but the code will behave differently than expected. Therefore, the severity of this issue should be considered **MEDIUM**.

## Recommendation

Consider updating the `_payoutRewards` function so that it does not change `stakeRate` if called during `stake` or `unstake`. Alternatively, consider documenting this behavior.



## Assessed type

Error
Tokens with callback on transfer could potentially harm the system
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L179
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L160
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L253


# Vulnerability details

## Description

The Reserve Protocol aims to support any token. Although it is uncommon, there are tokens in the wild that perform a callback on transfer. The protocol is pretty well armed against such tokens being a root cause of an exploit by following CEI patterns, implementing reentrancy guards, etc. However, there is a sneaky vulnerability that potentially could harm some users and can arise when `BackingManager::forwardRevenue`, `RToken::redeem` or `RToken::redeemCustom` and a callback token are mixed together.

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L179

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L160

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L253

When the `BackingManager` is collateralized and holds some excess baskets, it can forward these as revenue using the `forwardRevenue` function. This situation occurs when the baskets held by the `BackingManager` are greater than or equal to the `basketsNeeded` in the `RToken` ([see here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L190)). If a system supports callback tokens, this can be misused by a malicious user when redeeming tokens, since at the time of redemption [here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L219) the `BackingManager` still has tokens, but `basketsNeeded` were already decreased [here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L203).

## Proof of concept

Let's assume the system is in good shape, 1e24 RTokens are minted, and 1e24 baskets are needed. One RToken would be redeemable for 0.5 of the callback token and 1 USDC. A user holding 1e23 of these RTokens would notice this vulnerability, and they would redeem their tokens to a smart contract, which would perform a callback on receiving the callback token.

First, during the `redeemTo` call, the baskets needed would be decreased [here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L203) to 9e23. Now during the callback, the attacker contract would call `BackingManager::forwardRevenue`, which would [mint](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L221-L224) 1e23 RTokens, as the baskets held by the `BackingManager` are still unchanged. Since `RToken` has no reentrancy guard, this call would pass.

The tokens considered extra could also be sent to the respective traders now. However, the real issue is that the original redeemer will get all the tokens they initially had the claim to, but the `RToken` i
`RevenueTrader` can sell unpriced assets for near zero value
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RevenueTrader.sol#L109


# Vulnerability details

## Description

The `RevenueTrader` contract trades excess balances of its assets registered in the asset registry for either RSR or RToken. The assets are sold via batch or Dutch auction, in batch auction to the buyer who bids the highest amount of buy assets for the selling assets, and in the Dutch auction to the buyer who bids first, with the price going down over time. However, the revenue trader can open trades even for unpriced assets, i.e., the assets that return `(0, FIX_MAX)` on `price()` (for example, when the oracle had a long timeout).

## Proof of concept

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RevenueTrader.sol#L109

Let's assume that `RevenueTrader` holds an asset A. The oracle for this asset has been in timeout longer than the `decayDelay + priceTimeout`. When we can call `manageTokens` providing this asset, the contract will load its price, which can result in `(0, FIX_MAX)` given the conditions mentioned above. When it comes to preparing the trade, the inline docs say

> Whether dust or not, trade the non-target asset for the target asset
> Any asset with a broken price feed will trigger a revert here

This is not true, however, since a price feed with a long timeout, as in our example, should be considered broken, and no revert is triggered. This leads to the `minBuyAmount` being 0. Although the trade would be regarded as dust at this point, the `RevenueTrader` does not care about dust trades, and since it has assets to sell, it will try to open the trade. Note that we can only open a `GnosisTrade`, as `DutchTrade` would revert on zero price.

Note that even if the trade launched is the batch auction, which allows other users to move the final price higher while the auction is ongoing, the trade is started via `manageTokens`, which is a permissionless call. This means that if a user spots such an opportunity and starts the trade themselves, they might be the only one with their eyes on the trade. This would mean that a potentially unprofitable trade would be opened, selling potentially valuable assets for near-zero value.

# Impact and likelihood

The likelihood should be considered **LOW** as it depends on a timeout of an oracle longer than the specified decay delay + price timeout. There is however still a The impact should be considered **HIGH** as it results in a loss of value for the respective RToken system. Therefore, the severity of the issue should be considered **MEDIUM**.

## Recommendation

Consider reverting in `prepareTradeSell` if the `sellLow` price is 0. This should be fine for reasonable assets expected to be used in the protocol, as a reasonable price feed should exist for any valid asset. However, this would eliminate a potential loss of value.



## Assessed type

Invalid Validation
Governance might be vulnerable during era transitions
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/plugins/governance/Governance.sol#L185


# Vulnerability details

## Description

The `StRSR` token gives voting power to the RSR stakers on an individual RToken. [In some cases](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L452), the `StRSR` is re-issued at a 1:1 rate, which starts a new era of the staking, when balances are zeroed out, and the total supply of `StRSR` is reset. This new period can be potentially dangerous for the respective RToken system since both quorum and proposer threshold are percentage values based on the total supply of `StRSR`. Let's examine the scenarios in more detail.

## Proof of concept

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/plugins/governance/Governance.sol#L185

1. Proposal from the previous era

There is a strict rule in governance that proposals must start in the same era as they are executed or queued. This is logical; since the votes are reset during an era bump, the proposals should be discarded as well. However, there is a little flaw: the time used to judge whether a proposal is from the same era is based on its `proposalSnapshot`, i.e., when the voting for that proposal started.

This seems counterintuitive due to the previously mentioned rule and the fact that the proposer of this proposal might not have the voting power to be a proposer anymore. The latter might be negligible since such a situation might also happen by buying, proposing, and dumping the voting power, but it is still counterintuitive. However, the real problem with this is that when the proposal was created, the voting power was distributed in some way that is no longer true. However, if voting started shortly after a new era, the voting power would be reset and might be easier to manipulate so that somebody could profit from the system during such times. This also leads us to another scenario.

2. New proposals

Since the proposal threshold can even be zero during this time (e.g., one block after a new era begins and the total supply of `StRSR` is 0, so is the proposal threshold), this opens up room for **1) spam proposals, as no stake is needed to create a proposal** as well as **2) malicious proposals**. Note that the "power" can now be obtained pretty easily, and the governance/staking might need some time to recover to a normal level.

# Impact and likelihood

We believe the role of the `CANCELLER` plays a crucial role in mitigating such issues, but if we assume that (as stated in the docs) `It is acceptable if it is relatively slow to act`, this issue might still pose a considerable risk to the protocol and its respective systems. Considering the presence of this role, the likelihood of such an issue should be judged **LOW**, while the impact should be judged **HIGH**. Therefore, the severity of this issue should be considered as **MEDIUM**.

## Recommendation

1. Consider implementing a c
Broken assumptions can lead to the inability to seize RSR
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L424


# Vulnerability details

## Description

The `seizeRSR` [function](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L424) takes RSR from the staking contract when `BackingManager` wants to sell RSR, [but it does not have enough](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L161). In this case, the stakers can lose a portion of their stake in order to keep the system healthy. However, an issue arises from broken assumptions about `stakeRSR` and `totalStakes`, which will make the contract unable to seize due to revert.

## Proof of concept

Let's assume the system becomes unhealthy, and the stakers will start unstaking their tokens in fear of an upcoming reset/seize. Then, the expected event comes, and the `BackingManager` tries to seize RSR from the staking contract.

This action is frontran however, with the last staker who:

1. unstakes everything
2. stakes one token
3. stakes one token again

Let's examine how does this break `seizeRSR`.

1. During the `unstake` [call](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L259):
   - `_payoutRewards` is called. Since `totalStakes` is still above `1e18`, `stakeRSR` can be increased and become greater than `totalStakes`. Let's assume that `totalStakes` would be `1000e18` and `stakeRSR` would end at `1001e18` after this step.
   - Now, the `stakeRate` would be updated. Since `stakeRSR` is not zero yet, and so is not `totalStakes`, it would be set to
     ```Solidity
       uint192((totalStakes * FIX_ONE_256 + (stakeRSR - 1)) / stakeRSR)
     ```
     which translates to
     ```Solidity
       uint192((1000e18 * 1e18 + (1001e18 - 1)) / 1001e18)
     ```
     resulting in `999000999000999001`
   - `totalStakes` would be decreased in `_burn` by `1000e18` to zero, which would result in `stakeRSR` being set to zero
2. During the `stake` [call](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L227):
   - `payoutRewards` is skipped because it was already called in this block
   - `newTotalStakes` in `mintStakes` is calculated as `(stakeRate * newStakeRSR) / FIX_ONE`, which translates to `(stakeRate * (stakeRSR + rsrAmount)) / FIX_ONE` which translates to `(999000999000999001 * (0 + 1)) / 1e18` resulting in 0, meaning 0 tokens are minted and `totalStakes` remains 0
   - `stakeRSR` is increased by 1
3. The second `stake` call will have the same effects
   - after this call, the `stakeRate` is `999000999000999001`, `totalStakes` is 0, and `stakeRSR` is 2 (note that this breaks [this assumption](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L619-L620))
4. Now we examine the important parts of the `seizeRSR` call
   - `stakeRSRToTake` is set to `(2 * rsrAmount + (rsrBalance - 1)) / rsrBalance`, which will result in 1 every time the we are not seizin
`GnosisTrade` can incorrectly report a violation due to the auction fee
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/plugins/trading/GnosisTrade.sol#L218


# Vulnerability details

## Description

The respective trades within a deployed system can report a violation if the resulting price of the trade does not meet some expectations. This would mean pausing the respective trade type (batch or Dutch trade). Regarding the batch auctions represented with `GnosisTrade`, this violation occurs when the resulting clearing price is less than the previously set worst-case price ([see here](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/plugins/trading/GnosisTrade.sol#L218)).

However, this "violation" can occur even if the tokens were indeed purchased for the intended worst-case price and should not be considered an error in the trading scheme. This incorrect violation report would block the batch trade in the RToken system, until the governance unblocks it, and it can occur due to the easy auction fees when not the whole amount of the sell token is sold.

## Proof of concept

Let's assume there is a 1.5% fee on the `GnosisTrade` contract, and we want to start a trade where `sellAmount = 10_000e6` and `minBuyAmount = 9850e6`. `minBuyAmtPerOrder` would be set to `1970000`, and the actual sell amount would be `9852216748`. `9999999999999` tokens would be transferred to the `EasyAuction` contract. One token would stay in the `GnosisTrade` contract.

A single sell order would be executed during the auction, where the buyer wants to buy `19700000` tokens for `19699999` tokens. If we checked the price now, it would be `999999949238578680203045685`, far above the worst-case price (which was `985000000000000000000000`).

When the trade settles, unsold buy tokens and a portion of the fee, since the fee is only applied to the sold tokens, will be sent to the trade contract. This would come to `9980000000`, and the buy tokens sent would come to `19699999`.

Now, during `settle`, the `sellBal` would be `9980000001` and `boughtAmt` would be `19699998`, meaning that `soldAmt` would result in `19999999`. The clearing price would be `shiftl_toFix(adjustedBuyAmt, 9).divu(adjustedSoldAmt, FLOOR)`, which translates to `(19699998 * 1e27) / 19999999` which comes to `984999949249997462499873124`, and slightly under the worst case price. However, this would trigger the report violation, even though it should not.

The following test proves the presence of the issue:

Firstly, we create some mock contracts that we will use later:

```Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "../trading/GnosisTrade.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockBroker {
    bool public isViolation;

    function reportViolation() external {
        isViolation = true;
    }
}

contract MockAsset {
    IERC20Metadata public immutable erc20;

    constructor(IERC20Metadata _erc20) {
        erc20 = _erc20;
    }
}

contract MockERC20 is ERC20 {
    con