xanoutas/solidity-security-findings huggingface dataset:
RToken.redeem should claim rewards before sending tokens to user
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L156-L158


# Vulnerability details

## Impact
RToken.redeem should claim rewards before sending tokens to user. Because after that he will not receive them anymore.

## Proof of Concept
When user mints `RToken`, then he sends some tokens as collateral and they are stored by `BackingManager`. Some of collateral tokens can earn rewards and that's why `BackingManager` is `Trading` and [has functions to claim that rewards](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/mixins/Trading.sol#L60-L70).

When user redeems their RTokens, that means that `BackingManager` will retun part of collateral that it's currently storing. Once it will do that, then `BackingManager` will not be able to claim rewards for that part of tokens anymore. And these rewards will be lost. And they can be even not small. 
## Tools Used
VsCode
## Recommended Mitigation Steps
I believe that rewards should be claimed for all tokens, that are going to be sent to redeemers. These rewards should be used in order to back RToken.


## Assessed type

Error
FurnaceP1.setRatio will work incorrect after call when frozen 
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/Furnace.sol#L84-L91


# Vulnerability details

## Impact
`FurnaceP1.setRatio` will not update `lastPayout` when called in frozen state, which means that after component will be unfrozen, melting will be incorrect.
## Proof of Concept
`melt` function should burn some amount of tokens from `lastPayoutBal`. It depends of `lastPayout` and `ratio` variables. The more time has passed, the more tokens will be burnt.

When `setRatio` function is called, then `melt` function [is tried to be executed](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/Furnace.sol#L86), because new ratio is provided and it should not be used for previous time ranges.
In case if everything is ok, then `lastPayout` and `lastPayoutBal` will be updated, so it's safe to update `ratio` now.
But it's possible that `melt` function will revert in case if `notFrozen` modifier is not passed. As result `lastPayout` and `lastPayoutBal` will not be updated, but ratio will be. Because of that, when `Furnace` will be unfrozen, then melting rate can be much more, then it should be, because `lastPayout` wasn't updated.
## Tools Used
VsCode
## Recommended Mitigation Steps
In case of `catch` case, you can update `lastPayout` and `lastPayoutBal`.


## Assessed type

Error
Users who stake at the end of a freeze would get rewards as if they've staked before the freeze
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L225


# Vulnerability details



This one was reported [in the first contest](https://github.com/code-423n4/2023-01-reserve-findings/issues/148), it was mitigated but a [code change](https://github.com/reserve-protocol/protocol/commit/576061e70259be9fd21d3b0b9720a4976cfadbef) that was made since then brings it back again.


## Impact
Users who stake while frozen would get a share of the rewards for the period since the last call to `payoutRewards()`.

This means that in a case of a long freeze (e.g. a week or a few weeks) - users who stake right before the freeze ends would get a reward as if they've staked before the freeze started. This comes at the expense of the stakers who have staked before the freezing begun.

## Proof of Concept
While frozen the `_payoutRewards()` isn't called:
```solidity
    function stake(uint256 rsrAmount) public {
        require(rsrAmount > 0, "Cannot stake zero");

        if (!main.frozen()) _payoutRewards();
```

Not paying out the rewards meaning that the next time `_payoutRewards()` would be called the stakers would get a reward as if they've staked at the last time it was called.


## Recommended Mitigation Steps
Consider paying out rewards also while frozen


## Assessed type

Other
Lack of claimRewards when manageToken in RevenueTrader
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RevenueTrader.sol#L83-L104


# Vulnerability details

There is a dev comment in the Assert.sol:
```
DEPRECATED: claimRewards() will be removed from all assets and collateral plugins
```

The claimRewards is moved to the `TradingP1.claimRewards/claimRewardsSingle`. 

But when the `RevenueTraderP1` trade and distribute revenues by `manageToken`, it only calls the refresh function of the asserts:
```
if (erc20 != IERC20(address(rToken)) && tokenToBuy != IERC20(address(rToken))) {
    IAsset sell_ = assetRegistry.toAsset(erc20);
    IAsset buy_ = assetRegistry.toAsset(tokenToBuy);
    if (sell_.lastSave() != uint48(block.timestamp)) sell_.refresh();
    if (buy_.lastSave() != uint48(block.timestamp)) buy_.refresh();
}
```

The claimRewards is left out. 

## Impact
Loss a part of rewards.

## Tools Used
Manual review
## Recommended Mitigation Steps
Add claimRewardsSingle when refresh assert in the `manageToken`.



## Assessed type

Context
Oracle timeout at rebalance will result in a sell-off of all RSRs at 0 price
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/mixins/RecollateralizationLib.sol#L394-L413


# Vulnerability details

When creating the trade for rebalance, the `RecollateralizationLibP1.nextTradePair` uses `(uint192 low, uint192 high) = rsrAsset.price(); // {UoA/tok}` to get the rsr sell price. And the rsr assert is a pure Assert contract, which `price()` function will just return (0, FIX_MAX) if oracle is timeout:
```
function price() public view virtual returns (uint192, uint192) {
    try this.tryPrice() returns (uint192 low, uint192 high, uint192) {
        assert(low <= high);
        return (low, high);
    } catch (bytes memory errData) {
        ...
        return (0, FIX_MAX);
    }
}
```

The `trade.sellAmount` will be all the rsr in the `BackingManager` and `stRSR`:
```
uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus(
    rsrAsset.bal(address(ctx.stRSR))
);
trade.sellAmount = rsrAvailable;
```

It will be cut down to a normal amount fit for buying UoA amount in the `trade.prepareTradeToCoverDeficit` function.

But if the rsr oracle is timeout and returns a 0 low price. The trade req will be made by `trade.prepareTradeSell`, which will sell all the available rsr at 0 price.

Note that the SOUND colls won't be affected by the issue because the sell amount has already been cut down by basketsNeeded.

## Impact
Loss huge amount of rsr in the auction. When huge amounts of assets are auctioned off at zero, panic and insufficient liquidity make the outcome unpredictable.

## Proof of Concept
POC git diff test/Recollateralization.test.ts
```patch
diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts
index 86cd3e88..15639916 100644
--- a/test/Recollateralization.test.ts
+++ b/test/Recollateralization.test.ts
@@ -51,7 +51,7 @@ import {
 import snapshotGasCost from './utils/snapshotGasCost'
 import { expectTrade, getTrade, dutchBuyAmount } from './utils/trades'
 import { withinQuad } from './utils/matchers'
-import { expectRTokenPrice, setOraclePrice } from './utils/oracles'
+import { expectRTokenPrice, setInvalidOracleTimestamp, setOraclePrice } from './utils/oracles'
 import { useEnv } from '#/utils/env'
 import { mintCollaterals } from './utils/tokens'
 
@@ -797,6 +797,166 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => {
   })
 
   describe('Recollateralization', function () {
+    context('With simple Basket - Two stablecoins', function () {
+      let issueAmount: BigNumber
+      let stakeAmount: BigNumber
+
+      beforeEach(async function () {
+        // Issue some RTokens to user
+        issueAmount = bn('100e18')
+        stakeAmount = bn('10000e18')
+
+        // Setup new basket with token0 & token1
+        await basketHandler.connect(owner).setPrimeBasket([token0.address, token1.address], [fp('0.5'), fp('0.5')])
+        await basketHandler.connect(owner).refreshBasket()
+
+        // Provide approvals
+
sell reward rTokens at low price because of skiping furnace.melt
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RevenueTrader.sol#L100-L104


# Vulnerability details

## Impact
The reward rToken sent to RevenueTrader will be sold at a low price. RSR stakers will lose some of their profits.

## Proof of Concept
`RevenueTraderP1.manageToken` function is used to launch auctions for any erc20 tokens sent to it. For the RevenueTrader of the rsr stake, the `tokenToBuy` is rsr and the token to sell is reward rtoken.

There is the refresh code in the `manageToken` function:

```
} else if (assetRegistry.lastRefresh() != uint48(block.timestamp)) {
    // Refresh everything only if RToken is being traded
    assetRegistry.refresh();
    furnace.melt();
}
```

It refreshes only when the assetRegistry has not been refreshed in the same block.

So if the actor calls the `assetRegistry.refresh()` before calling `manageToken` function, the `furnace.melt()` won't been called. And the BU exchange rate of the RToken will be lower than actual value. So the sellPrice is also going to be smaller.

```
(uint192 sellPrice, ) = sell.price(); // {UoA/tok}

TradeInfo memory trade = TradeInfo({
    sell: sell,
    buy: buy,
    sellAmount: sell.bal(address(this)),
    buyAmount: 0,
    sellPrice: sellPrice,
    buyPrice: buyPrice
});
```

## Tools Used
Manual review
## Recommended Mitigation Steps
Refresh everything before sell rewards.


## Assessed type

Context
stake before unfreeze can take away most of rsr rewards in the freeze period 
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L222-L232


# Vulnerability details

If the system is frozen, the only allowed operation is `stRST.stake`. And the `_payoutRewards` is not called during freeze period:
```
if (!main.frozen()) _payoutRewards();

function payoutRewards() external {
    requireNotFrozen();
    _payoutRewards();
}
```
So the `payoutLastPaid` stays before the freeze period. But when the system is unfreezed, accumulated rewards will be released all at once because the block.timestamp leapt the whole freeze period.

## Impact
A front runner can stake huge proportion rsr before admin unfreezes the system. And the attacker can get most of rsr rewards in the next block. And he only takes the risk of the `unstakingDelay` period.


## Proof of Concept
Assumption: there are 2000 rsr stake in the stRSR, and there are 1000 rsr rewards in the `rsrRewardsAtLastPayout` with a 1 year half-life period. 

And at present, the LONG_FREEZER `freezeLong` system for 1 year(default).

After 1 year, at the unfreeze point, a front runner stake 2000 rsr into stRSR. And then the system is unfreeze. And in the next blcok,the front runner unstakes all the stRSR he has for `2250 rsr = 2000 principal + 1000 / 2 / 2 rsr rewards`. 

The only risk he took is `unstakingDelay`. The original rsr stakers took the risk of the whole freeze period + `unstakingDelay` but only got a part of rewards back.

## Tools Used
Manual review
## Recommended Mitigation Steps
payoutRewards before freeze and update payoutLastPaid before unfreeze.


## Assessed type

Access Control
cancelUnstake lack payoutRewards before mint shares
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L341-L380


# Vulnerability details


`cancelUnstake` will cancel the withdrawal request in the queue can mint shares as the current `stakeRate`. But it doesn't `payoutRewards` before `mintStakes`. Therefor it will mint stRsr as a lower rate, which means it will get more rsr.

## Impact
Withdrawers in the unstake queue can `cancelUnstake` without calling `payoutRewards` to get more rsr rewards that should not belong to them.

## Proof of Concept
POC test/ZZStRSR.test.ts git patch
```patch
diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts
index ecc31f68..b2809129 100644
--- a/test/ZZStRSR.test.ts
+++ b/test/ZZStRSR.test.ts
@@ -1333,6 +1333,46 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => {
       expect(await stRSR.exchangeRate()).to.be.gt(initialRate)
     })
 
+    it('cancelUnstake', async () => {
+      const amount: BigNumber = bn('10e18')
+
+      // Stake
+      await rsr.connect(addr1).approve(stRSR.address, amount)
+      await stRSR.connect(addr1).stake(amount)
+      await rsr.connect(addr2).approve(stRSR.address, amount)
+      await stRSR.connect(addr2).stake(amount)
+      await rsr.connect(addr3).approve(stRSR.address, amount)
+      await stRSR.connect(addr3).stake(amount)
+
+      const  initExchangeRate = await stRSR.exchangeRate();
+      console.log(initExchangeRate);
+
+      // Unstake addr2 & addr3 at same time (Although in different blocks, but timestamp only 1s)
+      await stRSR.connect(addr2).unstake(amount)
+      await stRSR.connect(addr3).unstake(amount)
+
+      // skip 1000 block PERIOD / 12000s
+      await setNextBlockTimestamp(Number(ONE_PERIOD.mul(1000).add(await getLatestBlockTimestamp())))
+
+      // Let's cancel the unstake in normal
+      await expect(stRSR.connect(addr2).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled')
+      let exchangeRate = await stRSR.exchangeRate();
+      expect(exchangeRate).to.equal(initExchangeRate)
+      
+      // addr3 cancelUnstake after payoutRewards
+      await stRSR.payoutRewards()
+      await expect(stRSR.connect(addr3).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled')
+
+      // Check balances addr2 & addr3
+      exchangeRate = await stRSR.exchangeRate();
+      expect(exchangeRate).to.be.gt(initExchangeRate)
+      const addr2NowAmount = exchangeRate.mul(await stRSR.balanceOf(addr2.address)).div(bn('1e18'));
+      console.log("addr2", addr2NowAmount.toString());
+      const addr3NowAmount = exchangeRate.mul(await stRSR.balanceOf(addr3.address)).div(bn('1e18'));
+      console.log("addr3",addr3NowAmount.toString());
+      expect(addr2NowAmount).to.gt(addr3NowAmount)
+    })
+
     it('Rewards should not be handed out when paused but staking should still work', async () => {
       await main.connect(owner).pauseTrading()
       await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp(
An oracle deprecation might lead the protocol to sell assets for a low price
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BackingManager.sol#L87-L96


# Vulnerability details



During a Dutch Auction, if a user places a bid, the trade is settled in the same transaction. As part of this process, the backing manager tries to call the `rebalance()` function again.
The call to `rebalance()` is wrapped in a try-catch block, if an error occurs and the error data is empty, the function will revert.

The assumption is that if the error data is empty that means it was due to an out-of-gas error, this assumption isn't always true as mentioned in a [previous issue](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) (that wasn't mitigated).
In the case of this issue, this can result in a case where users can't bid on an auction for some time, ending up selling an asset for a price lower than the market price. 



## Impact
Protocol's assets will be auctioned for a price lower than the market price

## Proof of Concept
Consider the following scenario:
* Chainlink announces that an oracle will get deprecated
* Governance passes a proposal to update the asset registry with a new oracle
* A re-balancing is required and executed with a Dutch Auction
* The oracle deprecation happens before the auction price reaches a reasonable value
* Any bid while the oracle is deprecated will revert
* Right before the auction ends the proposal to update the asset becomes available for execution (after the timelock delay has passed). Somebody executes it, bids, and enjoys the low price of the auction.

## Recommended Mitigation Steps
On top of checking that the error data is empty, compare the gas before and after to ensure this is an out-of-gas error.


## Assessed type

Error
Attacker can disable basket during un-registration, which can cause an unnecessary trade in some cases
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/AssetRegistry.sol#L89-L93
https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/AssetRegistry.sol#L106-L110


# Vulnerability details

At the mitigation contest there was an issue regarding the `basketHandler.quantity()` call at the unregistration process taking up all gas.
As a mitigation to that issue the devs set aside some gas and use the remaining to do that call.
This opens up to a new kind of attack, where a attacker can cause the call to revert by not supplying enough gas to it.


## Impact
This can cause the basket to get disabled, which would require a basket refresh.
After a basket refresh is done, an additional warmup period has to pass for some functionality to be available again (issuance, rebalancing and forwarding revenue).
In some cases this might trigger a basket switch that would require the protocol to rebalance via trading, trading can have some slippage which can cause a loss for the protocol.


## Proof of Concept

The `quantity()` function is being called with the amount of gas that `_reserveGas()` returns

If an attacker causes the gas to be just right above `GAS_TO_RESERVE` the function would be called with 1 unit of gas, causing it to revert:
```solidity
    function _reserveGas() private view returns (uint256) {
        uint256 gas = gasleft();
        require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely");
        return gas - GAS_TO_RESERVE;
    }
```


Regarding the unnecessary trade, consider the following scenario:
* The basket has USDC as the main asset and DAI as a backup token
* A proposal to replace the backup token with USDT was raised
* A proposal to unregister BUSD (which isn't part of the basket) was raised too
* USDC defaults and DAI kicks in as the backup token
* Both proposals are now ready to execute and the attacker executes the backup proposal first, then the unregister while disabling the basket using the bug in question
* Now, when the basket is refreshed DAI will be replaced with USDT, making the protocol to trade DAI for USDT

The refresh was unnecessary and therefore the trade too.


## Recommended Mitigation Steps

Reserve gas for the call as well:
```diff
    function _reserveGas() private view returns (uint256) {
        uint256 gas = gasleft();
-        require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely");
+        require(gas >= GAS_TO_RESERVE + MIN_GAS_FOR_EXECUTION, "not enough gas to unregister safely");
        return gas - GAS_TO_RESERVE;
    }
```

Disclosure: this issue was [mentioned in the comments](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435483929) to the issue in the mitigation contest, however since this wasn't noticed by the devs and isn't part of the submission I don't think this should be considered a known issue


## Ass
Custom redemption can be used to get more than RToken value, when an upwards depeg occurs
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# 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

Custom redemption allows to redeem RToken in exchange of a mix of previous baskets (as long as it's not more than the prorata share of the redeemer).
The assumption is that previous baskets aren't worth more than the target value of the basket.
However, a previous basket can contain a collateral that depegged upwards and is now actually worth more than its target. 

## Impact
Funds that are supposed to go revenue traders would be taken by an attacker redeeming RToken

## Proof of Concept
The [following code](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/plugins/assets/FiatCollateral.sol#L142-L148) shows that when a depeg occurs the collateral becomes IFFY (which means it'll be disabled after a certain delay):
```solidity
            // If the price is below the default-threshold price, default eventually
            // uint192(+/-) is the same as Fix.plus/minus
            if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) {
                markStatus(CollateralStatus.IFFY);
            } else {
                markStatus(CollateralStatus.SOUND);
            }
```

Consider the following scenario:
* Basket contains token X which is supposed to be pegged to c
* Token X depegs upwards and is now worth 2c
* After `delayUntilDefault` passes the basket gets disabled
* A basket refresh is executed (can be done by anybody) and token Y kicks in as the backup token
* Half of token X is now traded for the required Y tokens
* The other half should go to revenue traders (rsr trader and Furnace), but before anyone calls ‘forewardRevenue’ the attacker calls custom redemption with half from the current basket and half of the previous one
* The user would get 0.5X+0.5Y per each RToken which is worth 1.5c


## Recommended Mitigation Steps
When doing custom redemption check that the collateral used is sound or at least check that the price isn't higher then the peg price.


## Assessed type

Other
`cancelUnstake()` doesn't payout rewards first
Audit: 2023-06-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L341-L380


# Vulnerability details



The new `cancelUnstake()` function allows users to cancel their unstaking, by taking the user's drafts and minting it again.
However, since the `_payoutRewards()` isn't being called this means that the user would get rewards for the period between the last time `_payoutRewards()` was called and the time of cancellation.

## Impact
Users would get rewards they don't deserve, at the expense of other stakers.

## Proof of Concept
`cancelUnstake()` doesn't call `_payoutRewards()`, this means that the minting part would be done with the old staking rate.
Using an old staking rate means that the user would get a share of the rewards since the last time `_payoutRewards()` was called.



## Recommended Mitigation Steps
Call `_payoutRewards()` before the minting


## Assessed type

Other