xanoutas/solidity-security-findings huggingface dataset:
First depositer in rToken can ensure that second always suffers a loss of funds
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

If there is "ever" a second depositer he will loose funds 
## Proof of Concept
First depositer can misuse [furnace.melt()](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/Furnace.sol#L65-L79) to skew basketNeeded & totalSupply such that all subsequent depositers loose funds. 
```solidity
function melt() public {
		....
        lastPayoutBal = rToken.balanceOf(address(this)) - amount;
        ///@audit ^ sets balance dynamically 
        if (amount != 0) rToken.melt(amount);
    }
```
Scenario-
1. First depositer mints some dust amount of rToken lets say 40 (not 40e18)
2. He then proceeds to transfer half of them to furnace
3. lets say the ratio of furnace is high and it melts half of these funds in ~2 hours
4. So now basketsNeeded=40 but totalSupply = 30, i.e baskets needed 33% > totalSupply
So now in [issueTo()](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L133-L143) amtBaskets will be 33% greater than what it should be as supply !=0
```solidity
        uint192 amtBaskets = supply != 0
            ? basketsNeeded.muluDivu(amount, supply, CEIL)
            : _safeWrap(amount);
        emit Issuance(issuer, recipient, amount, amtBaskets);

        // Get quote from BasketHandler including issuance premium
        (address[] memory erc20s, uint256[] memory deposits) = basketHandler.quote(
            amtBaskets,
            true,
            CEIL
        );
```
and this inflated amtBaskets will be used for input inside quote, leading to asking 33% more collateral than what it originally should but when redeeming it will only give him collateral worth of original amt. i.e if 2nd depositer mints 1000 rToken it will ask him to pay 1333 usd worth collateral tokens & when he redeems it will only give him 1000 usd worth collateral.
The coded POC shows how **functionalities of protocol like quote() , redeem() will be broken** leading to reverts & how only customRedemptions will work **alongside with loses.**
POC-
```ts
it('should show second depositer funds loss',async () => {
      // max payout rate
      await furnace.connect(owner).setRatio(bn(1e14));
      await token0.connect(addr1).approve(rToken.address,fp('10000'));
      await token2.connect(addr1).approve(rToken.address,fp('10000'));
      await backingManager.connect(owner).setBackingBuffer(0);
      await basketHandler.setPrimeBasket([token0.address,token2.address],[fp('0.5'),fp('0.5')]);
      await basketHandler.refreshBasket();
      await rToken.connect(addr1).issue(40);
      expect(await furnace.lastPayoutBal()).to.equal(0);
      await rToken.connect(addr1).tr
in `BackingManagerP1::rebalance` Wrong assertion will lead to panic reverts
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
- unintended behavior (`compromiseBasketsNeeded` would never be called), and the RToken state would stay undercollateralized
- left in state of under collateralization leading permeant freeze of RSR if the RToken is unweighable   

## Proof of Concept
The problem arises in `BackingManagerP1::rebalance` 
- which is a function used to rebalance the collaterals and open trades on them in un collateralized state
```solidity
File: BackingManager.sol
148:         (TradingContext memory ctx, Registry memory reg) = tradingContext(basketsHeld);
149:         (
150:             bool doTrade,
151:             TradeRequest memory req,
152:             TradePrices memory prices
153:         ) = RecollateralizationLibP1.prepareRecollateralizationTrade(ctx, reg);
154: 
155:         if (doTrade) {
156:             IERC20 sellERC20 = req.sell.erc20();
157: 
158:             // Seize RSR if needed
159:             if (sellERC20 == rsr) {
160:                 uint256 bal = sellERC20.balanceOf(address(this));
161:                 if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal);
162:             }
163: 
164:             // Execute Trade
165:             ITrade trade = tryTrade(kind, req, prices);
166:             tradeEnd[kind] = trade.endTime(); // {s}
167:             tokensOut[sellERC20] = trade.sellAmount(); // {tok}
168:         } else {
169:             // Haircut time
170:             compromiseBasketsNeeded(basketsHeld.bottom);
171:         }
```
in Line 153 we call `prepareRecollateralizationTrade` and return `bool doTrade` seeing the implementation of it 

```solidity
File: RecollateralizationLib.sol
33:     function prepareRecollateralizationTrade(TradingContext memory ctx, Registry memory reg)
34:         external
35:         view
36:         returns (
37:             bool doTrade,
38:             TradeRequest memory req,
39:             TradePrices memory prices
40:         )
41:     {
42:         // Compute a target basket range for trading -  {BU}
43:         // The basket range is the full range of projected outcomes for the rebalancing process
44:         BasketRange memory range = basketRange(ctx, reg);
45: 
46:         // Select a pair to trade next, if one exists
47:         TradeInfo memory trade = nextTradePair(ctx, reg, range);
48: 
49:         // Don't trade if no pair is selected
50:         if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) {
51:             return (false, req, prices);
52:         }
53: 
54:         // If we are selling a fully unpriced asset or UNSOUND collateral, do not cover deficit
55:         // untestable:
56:         //     sellLow will not be zero, those assets are skipped in nextTradePair
57:         if (
58:             trade.prices.sellLow == 0 ||
59:             (trade.sell.isColla
`rsrRewardsAtLastPayout` is incorrectly updated to a smaller value in `seizeRSR`.
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`rsrRewardsAtLastPayout` is incorrectly updated to a smaller value in `seizeRSR`. This results in the staker pool receiving less rewards in subsequent reward payouts than expected.

## Proof of Concept
In function `seizeRSR`, the amount of seized `stakeRSR`, `draftRSR`, and `rewards` are calculated based on the seized ratio, where `seizedRatio = ceil(rsrAmount / rsrBalance)`. At line 472, the seized `rsrRewards` is calculated as `(rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance`, and is then added to the total `seizedRSR`. Then the seized `rsrRewards` should be subtracted from the current `rsrRewards` to update `rsrRewardsAtLastPayout`. However, it is the total `seizedRSR` is subtracted from the current `rsrRewards` instead of the seized `rsrRewards`. This results in `rsrRewardsAtLastPayout` being incorrectly updated to a smaller value.
```solidity
// Function: StRSR.sol#seizeRSR()

471:        // Remove RSR from yet-unpaid rewards (implicitly)
472:        seizedRSR += (rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance;
473:        rsrRewardsAtLastPayout = rsrRewards() - seizedRSR;
```
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L471-L473

The `rsrRewardsAtLastPayout` is used in `_payoutRewards` to calculate the payout amount (L609-L610). If `rsrRewardsAtLastPayout` is incorrectly updated to a smaller value, the staker pool will receive less rewards than expected.
```solidity
// Function: StRSR.sol#_payoutRewards()

600:        // Do an actual payout if and only if enough RSR is staked!
601:        if (totalStakes >= FIX_ONE) {
602:            // Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
603:            // Apply payout to RSR backing
604:            // payoutRatio: D18 = FIX_ONE: D18 - FixLib.powu(): D18
605:            // Both uses of uint192(-) are fine, as it's equivalent to FixLib.sub().
606:            uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);
607:
608:            // payout: {qRSR} = D18{1} * {qRSR} / D18
609:@>          payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
610:@>          stakeRSR += payout;
611:        }
```
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L600-L611

## Tools Used
VS Code

## Recommended Mitigation Steps
Update the `rsrRewardsAtLastPayout` by subtracting the seized `rsrRewards` from the current `rsrRewards` instead of the total `seizedRSR`.
```solidity
// Function: StRSR.sol#seizeRSR()

        // Remove RSR from yet-unpaid rewards (implicitly)
-       seizedRSR += (rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance;
-       rsrRewardsAtLastPayout = rsrRewards() - seizedRSR;
+       uint256 rewardsToTake = (rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance;
+       seizedRSR += rewardsToTake;
+       rsrRew
Custom Redemption Vulnerability in Reserve Protocol Allows Value Extraction
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L245-L338
https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BasketHandler.sol#L437-L440


# Vulnerability details

## Impact
The custom redemption feature, implemented in the RTokenP1 contract, allows users to redeem ``RTokens`` in exchange for a mix of current and previous baskets. This feature assumes that previous baskets aren't worth more than the target value of the current basket. However, this assumption can be violated if a collateral asset in a previous basket has appreciated significantly.
As funds intended for revenue traders are reduced, it could impact the protocol's ability to maintain its peg and provide returns to RSR stakers.

## Proof of Concept
https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L245-L338
https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BasketHandler.sol#L437-L440

Consider the following scenario:
- The basket contains token X, which is supposed to be pegged to value c.
- Token X depegs upwards and is now worth 2c.
After delayUntilDefault passes, the basket gets disabled.
- A basket refresh is executed, and token Y is introduced as the backup token.
Half of token X is traded for the required Y tokens.
- Before anyone calls 'forwardRevenue', an attacker calls custom redemption with half from the current basket and half from the previous one.
- The attacker receives 0.5X + 0.5Y per each RToken, which is worth 1.5c, instead of the intended 1c value.
This shown here, this code does not check if the collateral used is sound or if its price is higher than the peg price, allowing the exploit
```solidity
// code from RToken.sol
function redeemCustom(
    address recipient,
    uint256 amount,
    uint48[] memory basketNonces,
    uint192[] memory portions,
    address[] memory expectedERC20sOut,
    uint256[] memory minAmounts
) external notFrozen {
    // ... (checks and state updates)

    (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quoteCustomRedemption(
        basketNonces,
        portions,
        baskets
    );

    // ... (token transfers)
}

// Relevant code from BasketHandler.sol
function quoteCustomRedemption(
    uint48[] memory basketNonces,
    uint192[] memory portions,
    uint192 amount
) external view returns (address[] memory erc20s, uint256[] memory quantities) {
    // ... (calculation logic)
}
```

## Tools Used
manual code review
## Recommended Mitigation Steps
Implement additional checks in the ``redeemCustom`` function to ensure that redemptions using previous baskets don't allow extracting more value than intended. You could add a mechanism to adjust redemption values based on current market prices, not just target prices, to prevent exploitation of appreciated assets. I
Calling beginEra() in the StRSR initializer will incorrectly reset state variables
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L201
https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L671
https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L683


# Vulnerability details

## Impact
The initializer function in StRSR.sol incorrectly bundles a call to `beginEra()`. This will incorrectly reset the era back to 1, reverting user funds to that state and reset the stakeRSR and totalStakes variables to 0 and will lead to loss of funds.

## Proof of Concept
As of writing of this report, the [proxy contract](https://etherscan.io/address/0x18ba6e33ceb80f077DEb9260c9111e62f21aE7B8) of the StRSR contract holds ~ 17,000 USD in tokens. The contract is well in use and thus the `stakeRSR` and `totalStakes` variables are non-zero. The StRSR.sol contract bundles a call to `beginEra()` and beginDraftEra in it's intializer function:

https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L201
```js
    function init(
        ...
    ) external initializer {
        ...
        beginEra();
        beginDraftEra();
    }
```

Both of these calls will incorrectly reset critical state variables to 0, breaking the accounting of the protocol and putting user funds at risk.

https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L671
```js
    function beginEra() internal virtual {
        stakeRSR = 0;
        totalStakes = 0;
        stakeRate = FIX_ONE;
        era++;

        emit AllBalancesReset(era);
    }
```

https://github.com/reserve-protocol/protocol/blob/master/contracts/p1/StRSR.sol#L683
```js
    function beginDraftEra() internal virtual {
        draftRSR = 0;
        totalDrafts = 0;
        draftRate = FIX_ONE;
        draftEra++;

        emit AllUnstakingReset(draftEra);
    }
```
The era should not be tied to the contract being updated, it should only really be called when the proxy contract is initialized for the very first time. Any subsequent intialization during upgrade of the proxy contract should have the beginEra call removed from the initializer. 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Remove the call to `beginEra()` from the initializer and instead bundle it in the deployment script for that particular version of the contract.


## Assessed type

Upgradable
GnosisTrade's MAX_ORDERS limit can be circumvented to DoS RToken recollateralization
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

The GnosisTrade contract leverages the Gnosis EasyAuction contract for batch sales.

This contract has a limitation in the fact that its settlement function has a potentially unbounded loop that can cause auctions with large numbers of orders to not be settled:

```Solidity
File: EasyAuction.sol
450:     // @dev function settling the auction and calculating the price
451:     function settleAuction(uint256 auctionId)
452:         public
453:         atStageSolutionSubmission(auctionId)
454:         returns (bytes32 clearingOrder)
455:     {
---
468:         do {
469:             bytes32 nextOrder = sellOrders[auctionId].next(currentOrder);
470:             if (nextOrder == IterableOrderedOrderSet.QUEUE_END) {
471:                 break;
472:             }
473:             currentOrder = nextOrder;
474:             (, buyAmountOfIter, sellAmountOfIter) = currentOrder.decodeOrder();
475:             currentBidSum = currentBidSum.add(sellAmountOfIter);
476:             console.log("CurrentBidSum %d, buyAmountOfIter %d", currentBidSum, buyAmountOfIter);
477:         } while (
478:             currentBidSum.mul(buyAmountOfIter) <
479:                 fullAuctionedAmount.mul(sellAmountOfIter)
480:         );
```

This particular limitation appears to be known to the Reserve team, and addressed via the introduction of two constants: `MAX_ORDERS` and `DEFAULT_MIN_BID`:

```Solidity
File: GnosisTrade.sol
27:     // Upper bound for the max number of orders we're happy to have the auction clear in;
28:     // When we have good price information, this determines the minimum buy amount per order.
29:     uint96 public constant MAX_ORDERS = 5000; // bounded to avoid going beyond block gas limit
30: 
31:     // raw "/" for compile-time const
32:     uint192 public constant DEFAULT_MIN_BID = FIX_ONE / 100; // {tok}
---
122:         // Don't decrease minBuyAmount even if fees are in effect. The fee is part of the slippage
123:         uint96 minBuyAmount = uint96(Math.max(1, req.minBuyAmount)); // Safe downcast; require'd
124: 
125:         uint256 minBuyAmtPerOrder = Math.max(
126:             minBuyAmount / MAX_ORDERS,
127:             DEFAULT_MIN_BID.shiftl_toUint(int8(buy.decimals()))
128:         );
```

This mitigation is however insufficient if we consider the following facts:
1. the EasyAuction contract sorts orders with the best prices first
2. sending a single order can be extremely cheap (for example for USDC, DEFAULT_MIN_BID corresponds to $0.01)
3. orders can be sent to EasyAuction in batches
4. EasyAuction keeps receiving new orders for as long as the auction is open, regardless of how many were received before and for what amount 
5. settlement processes orders in their sorted sequence

It is possible to leverage the above properties to submit orders at a (small) loss
Malicious proposals can be executed in the Governance
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L452
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/governance/Governance.sol#L131


# Vulnerability details

## Impact

In governance systems, there’s always a risk of malicious users creating harmful `proposals`. 
It’s crucial to prevent these `proposals` from being executed, which is the primary role of the governor.
For a malicious `proposal` to succeed, the malicious user must have enough (e.x. `51%` of) `votes` at the time the `vote` begins. 
The governor takes a `snapshot` of all users' `votes` at that moment. 

When a malicious `proposal` is made, honest users will work to gather more `votes` than the malicious party.
To help with this, a `voting delay` exists. 
In the `Reserve Governor`, the `minimum voting delay` is set to `1 day`. 
However, there are situations where this `delay` might not be effective, allowing malicious users to still gather enough `votes` to pass their harmful `proposals`. 
This is really dangerous.
## Proof of Concept

Let's say the `current era` in `StRSR` is `2`, and a malicious user has enough `votes` to create any `proposal` they want. 
They create a malicious `proposal` at `time T`. 
If the `voting delay` is `D`, the `vote` will begin at `T + D`, and the `snapshot` of `votes` for this `proposal` will be `T + D` (`line 290, 295`).
```
function propose(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    string memory description
) public virtual override returns (uint256) {
    uint256 currentTimepoint = clock();  // T
290:    uint256 snapshot = currentTimepoint + votingDelay();  // T + D
    uint256 deadline = snapshot + votingPeriod();
    
    _proposals[proposalId] = ProposalCore({
        proposer: proposer,
295:        voteStart: SafeCast.toUint64(snapshot), // T + D
        voteEnd: SafeCast.toUint64(deadline),
        executed: false,
        canceled: false,
        __gap_unused0: 0,
        __gap_unused1: 0
    });
}
```
Honest users have enough `votes` to defeat this `proposal` or to gather more `votes` than the malicious user. 

However, if the `StRSR` moves to the `next era` (`era 3`) at `T + D - 1` (or any time close to the `vote start`), all users' `votes` from `era 2` will disappear (`line 452`).
```
function seizeRSR(uint256 rsrAmount) external {
    if (stakeRSR == 0 || stakeRate > MAX_STAKE_RATE) {
        seizedRSR += stakeRSR;
452:        beginEra();
    }
}
```

The malicious user can then quickly `stake` some `RSR` to get new `votes`, while the honest users don’t have enough time to gather sufficient `votes`. 
In this case, the actual `voting delay` might act as short as `1 second`, for example.

When the `vote` begins, the malicious user has enough `votes` to pass their `proposal`. 

The `proposalSnapshot` function returns the `vote start time`, which is `
Incorrect buying asset calculation in `nextTradePair`
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Invalid assets(e.g. assets not in current basket) can be bought which will make collateralization worse.

## Proof of Concept
When the protocol becomes under-collateralized, trading events happen to sell over-collateralized tokens and to buy tokens with deficient supply.

The logic to choose what tokens to buy and sell are done via `nextTradePair` function:
```solidity
if (ctx.bals[i].gt(needed)) {
    // Choose token to sell
} else {
    // needed(Bottom): token balance needed at bottom of the basket range
    needed = range.bottom.mul(ctx.quantities[i], CEIL); // {buyTok};

    if (ctx.bals[i].lt(needed)) {
        uint192 amtShort = needed.minus(ctx.bals[i]); // {buyTok}
        (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/buyTok}

        // {UoA} = {buyTok} * {UoA/buyTok}
        uint192 delta = amtShort.mul(high, CEIL);

        // The best asset to buy is whichever asset has the largest deficit
        if (delta.gt(maxes.deficit)) {
            trade.buy = reg.assets[i];
            trade.buyAmount = amtShort;
            trade.prices.buyLow = low;
            trade.prices.buyHigh = high;

            maxes.deficit = delta;
        }
    }
}
```

If current balance is greater than needed collateral amount, it is considered to be chosen as token to sell. Otherwise, the asset is considered to be bought based on the deficiency.

However, when choosing token to buy, it does not check if the asset exists in current basket.
This exposes a vulnerability for invalid tokens to be bought using surplus, and this will make collateralization of protocol worse.

## Tools Used
Manual Review

## Recommended Mitigation Steps
When choosing a token to buy, it should only look in tokens in current basket.


## Assessed type

Context