xanoutas/solidity-security-findings huggingface dataset:
Unchecked return price > 0 oracle
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/OracleLib.sol#L14-L30


# Vulnerability details

## Impact

In the function  price, there is no check that the return price that chainlink sends is >0. 


      (uint80 roundId, int256 p, , uint256 updateTime, uint80 answeredInRound) = chainlinkFeed
            .latestRoundData();

        if (updateTime == 0 || answeredInRound < roundId) {
            revert StalePrice();
        }
        // Downcast is safe: uint256(-) reverts on underflow; block.timestamp assumed < 2^48
        uint48 secondsSince = uint48(block.timestamp - updateTime);
        if (secondsSince > timeout) revert StalePrice();

Even if it is not likely, Chainlink could return a wrong price will concur in loss of customers or protocol funds due to not checking the validity of the price.

   
## Proof of Concept

1 Chainlink has some type of error while sending data
2 You receive a < 0 price or 0 in your contract
3 Depending on what the price function is being used for, more or less funds will be at risk

## Tools Used
manual

## Recommended Mitigation Steps

add:


           if (p < 0)
            revert Errors.NegativePrice(token, address(feed[token]));



Also as good practices:

Checking the returned answer is not 0.
Verify result is within an allowed margin of freshness by checking updatedAt.
Verify answer is indeed for the last known round.
Redemptions during undercollateralization can be hot-swapped to steal all funds
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/fdd9f81fe58953d758dbea62beed169a74523de1/contracts/p1/RToken.sol#L420


# Vulnerability details

## Impact
During recollateralization/a switch basket/when the protocol collateral isn't sound, a user can have almost their entire redemption transaction hot swapped for nothing. 

For example, trying to redeem 1M collateral for 1M rTokens could have the user end up with 0 collateral and 0 rTokens, just by calling the `redeem` function at the wrong time. 

Example:
- User A issues 1M rToken for 1M tokenA
- Evil user sees tokenA is about to become disabled, and that User A sent a normally innocuous redeem tx for too much underlying collateral in the mempool

- Evil user orders transactions so they and RSR/Rtoken holders can steal user A's funds

- They first buy a ton of tokenA and send it to the backing Manager
- They call `manageTokens` which flash issues a ton of new Rtoken due to the inflated tokenA balance, increasing the totalSupply
- The increase in total supply allows the normal redemption cap to be drastically lifted
- They then let the disabling of tokenA process, and calls refreshBasket where a backup token (tokenB) kicks in
- We are now undercollateralized, and evil user sends tokenB dust to the backingmanager

- FINALLY: the original redemption TX is ordered, and due to the inflated RToken supply, the battery discharge amount is also inflated, allowing the redemption to go through. Due to the new collateral in place, they redeem ALL their Rtoken (1M) for dust of tokenB!! The protocol has essentially honeypotted them!!

## Proof of Concept
We provide the proof of code in [proof of code](#proof-of-code). 

1. MEV

This relies on a validator being malicious with for-profit motives. It would be pretty easy for them to setup a bot looking for this exact scenario though and just staying dormant till the time is right. If they get to order the transactions, they can make a fat profit from the victim. 

2. Backing manager can flash issue RToken

If the backingManger has too many excess assets, it will flash issue as [many RTokens as](https://github.com/reserve-protocol/protocol/blob/fdd9f81fe58953d758dbea62beed169a74523de1/contracts/p1/BackingManager.sol#L201) possible to even the collateral to RTokens.

```
function handoutExcessAssets(IERC20[] calldata erc20s) private {
.
.
if (held.gt(needed)) {
.
.
rToken.mint(address(this), uint256(rTok));
```

3. Increasing the supply increases the redemption and issuance block cap

The RedemptionBattery's currentCharge function is [dependent on the total supply of RTokens](https://github.com/reserve-protocol/protocol/blob/fdd9f81fe58953d758dbea62beed169a74523de1/contracts/libraries/RedemptionBattery.sol#L59). So if the total supply is raised, you can redeem way more than you should be able to.

```
uint256 amtPerHour = (supply * battery.scalingRedemptionRate) / FIX_ONE_256;
```

(This also is true for issuance.)

4. Anyone can call [refreshB
Real Furnace and StRSR addresses can be added to the Distributor
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L161-L177


# Vulnerability details

## Impact

RToken may be distributed to StRSR and frozen.
RSR may be distributed to Furnace and frozen.

## Proof of Concept

Both Furnace and StRSR are added to the destinations of Distributor in [DistributorP1#init()](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L41):
```
function init(IMain main_, RevenueShare calldata dist) external initializer {
    // omitted
    _setDistribution(FURNACE, RevenueShare(dist.rTokenDist, 0));
    _setDistribution(ST_RSR, RevenueShare(0, dist.rsrDist));
}
```
They are both added with a mapping address:
```
address public constant FURNACE = address(1);
address public constant ST_RSR = address(2);
```

At the same time, in order to prevent funds from being trapped by distributing RToken to StRSR or RSR to Furnace, their configuration is specifically checked in [DistributorP1#_setDistribution()](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L163-L164):
```
function _setDistribution(address dest, RevenueShare memory share) internal {
    require(dest != address(0), "dest cannot be zero");
    if (dest == FURNACE) require(share.rsrDist == 0, "Furnace must get 0% of RSR");
    if (dest == ST_RSR) require(share.rTokenDist == 0, "StRSR must get 0% of RToken");
    // omitted
}
```

However, if an actual Furnace address or StRSR address is used when calling [DistributorP1#setDistribution()](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L61), it will be added to destinations as if it is a regular address.
Then, these two special contracts will be distributed twice in [DistributorP1#distribute](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L87)
And since `_setDistribution` didn't check the share parameter when they are added as regular address:
* RToken may be distributed to StRSR and frozen.
* RSR may be distributed to Furnace and frozen.

## Tools Used

Manual

## Recommended Mitigation Steps

We should treat the real addresses of Furnace and StRSR as their mapping addresses in [DistributorP1#setDistribution()](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L61):
```
diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol
index 53322973..28a05d67 100644
--- a/contracts/p1/Distributor.sol
+++ b/contracts/p1/Distributor.sol
@@ -160,6 +160,12 @@ contract DistributorP1 is ComponentP1, IDistributor {
     //   distribution' = distribution.set(dest, share)
     function _setDistribution(address dest, RevenueShare memory share) internal {
         require(dest != address(0), "
ERC20Permit should include chainId and address to avoid replay attacks
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/d224c14c398d2727d39d133aa7511e1e6b161833/contracts/p0/StRSR.sol#L498
https://github.com/reserve-protocol/protocol/blob/d224c14c398d2727d39d133aa7511e1e6b161833/contracts/p1/StRSRVotes.sol#L128


# Vulnerability details

## Impact
This ensures a signature is only used for our given token contract address on the correct chain id. The chain id was introduced to exactly identify a network after the Ethereum Classic fork which continued to use a network id of 1.

Include the chainId to avoid replay/ cross-chain attacks on different networks. Avoiding loss of funds / unable to transfer StRSR tokens

Code: [https://github.com/reserve-protocol/protocol/blob/d224c14c398d2727d39d133aa7511e1e6b161833/contracts/p1/StRSR.sol#L778](https://github.com/reserve-protocol/protocol/blob/d224c14c398d2727d39d133aa7511e1e6b161833/contracts/p1/StRSR.sol#L778)

Reference: [https://eips.ethereum.org/EIPS/eip-1344#rationale](https://eips.ethereum.org/EIPS/eip-1344#rationale)

## Recommended Mitigation Steps
Include chainId and address
A transfer-on-fee token or a deflationary/rebasing token, causing the received amount to be less than the accounted amount. For instance, a deflationary tokens might charge a certain fee for every safetransfer() or safetransferFrom().
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L236


# Vulnerability details

## Impact

ALice calls stake(amount = 100 deflationary Tokens). Because the token has a fee upon transfer, StRSR receives only 99 tokens and staked that amount to mint but user thought that receives 100 tokens . But reality token received for stRSR only 99 .

## Proof of Concept
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L236
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L336

## Tools Used
Manual view

## Recommended Mitigation Steps
Maintain before and after balance before token transfer/receiver.
Not-well defined behaviors of  setPeriod() and setRatio()
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Furnace.sol#L88-L101


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
When ``setPeriod()`` and ``setRatio()`` are called, the two parameters, ``period`` and ``ratio`` will change the behavior of ``melt()`` function retroactively since the last recorded ``lastPayout``. Meanwhile, a user can front-run these two functions and call ``melt()`` (anybody can call) and melt RTokens and bring ``lastPayout`` up to date, making ``period`` and ``ratio`` only effective prospectively. As a result, the consequence of ``setPeriod()`` and ``setRatio()`` is unpredictable, the new parameters might take effect for the past or for the future. 


## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
The problem lies in two facts: 1) when the new parameters of ``setPeriod()`` and ``setRatio()`` are set, they will take effect retroactively since ``lastPayout``; 2) melt() can be called by anyone and thus used to front-run ``setPeriod()`` and ``setRatio()``.  Depending on whether front-running happen or not, the behavior of the system could be one way or the other: new parameters will take effect for the past or for the future. 

## Tools Used
Remix

## Recommended Mitigation Steps
We can define the behavior of the system to be that new parameters only take effect for the future. All we need to do is to call ``_melt()`` first before setting the new parameters. 
```
function setPeriod(uint48 period_) public governance {
        _melt();
        require(period_ > 0 && period_ <= MAX_PERIOD, "invalid period");
        emit PeriodSet(period, period_);
        period = period_;
    }

    /// Ratio setting
    /// @custom:governance
    function setRatio(uint192 ratio_) public governance {
        _melt();
        require(ratio_ <= MAX_RATIO, "invalid ratio");
        // The ratio can safely be set to 0
        emit RatioSet(ratio, ratio_);
        ratio = ratio_;
    }

  function melt() external notPausedOrFrozen {
        _melt();
    }

 function _melt() internal {
        if (uint48(block.timestamp) < uint64(lastPayout) + period) return;

        // # of whole periods that have passed since lastPayout
        uint48 numPeriods = uint48((block.timestamp) - lastPayout) / period;

        // Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
        uint192 payoutRatio = FIX_ONE.minus(FIX_ONE.minus(ratio).powu(numPeriods));

        uint256 amount = payoutRatio.mulu_toUint(lastPayoutBal);

        lastPayout += numPeriods * period;
        lastPayoutBal = rToken.balanceOf(address(this)) - amount;
        if (amount > 0) rToken.melt(amount);
    }
```
[Medium - 1] Too few rewards paid over periods in Furnace and StRSR
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Furnace.sol#L77-L79
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L509-L512
https://github.com/reserve-protocol/protocol/blob/946d9b101dd77275c6cbfe0bfe9457927bd221a9/contracts/p1/StRSR.sol#L490-L493


# Vulnerability details

## Impact

For two instances in the codebase ([`Furnace`](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Furnace.sol#L77-L79) and [`StRSR`](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L509-L512)), the composed rewards calculation seems to be wrong.
How the rewards are working in these two snippets is that we are first measuring how much `period` or `rewardPeriod` occured since the last payout and calculating in only **one** step the rewards that should be distributed over these periods. In other words, it is composing the ratio over periods.

## Proof of Concept

Taken from the [comments](https://github.com/reserve-protocol/protocol/blob/946d9b101dd77275c6cbfe0bfe9457927bd221a9/contracts/p1/StRSR.sol#L490-L493), we can write the formula of the next rewards payout as:

```
with n = (i+1) > 0, n is the number of periods
rewards{0} = rsrRewards()
payout{i+1} = rewards{i} * payoutRatio
rewards{i+1} = rewards{i} - payout{i+1}
rewards{i+1} = rewards{i} * (1 - payoutRatio)
```

Generalization:
$$u_{i+1} = u_{i} * (1 - r)$$

It's a geometric mean whose growth rate is `(1 - r)`.

Claculation of the sum:

![](https://user-images.githubusercontent.com/51274081/213756676-cbbcf22b-3237-433d-a6e3-9577b6d75474.png)

You can play with the graph [here](https://www.desmos.com/calculator/1fnpsnf8nt).

For a practical example, let's say that our `rsrRewardsAtLastPayout` is 5, with a `rewardRatio` of 0.9
If we had to calculate our compounded rewards, from the formula given by the comments above, we could calculate manually for the first elements. Let's take the sum for n = 3:

$$S = u_{2} + u_{1} + u_{0}$$
$$u_{2} = u_{1} * (1-0.9)$$
$$u_{1} = u_{0} * (1-0.9)$$
$$u_{0} = rsrRewardsAtLastPayout$$

So,

$$S = u_{0} * (1-0.9) * (1-0.9) + u_{0} * (1-0.9) + u_{0}$$

For the values given above, that's

$$S = 5 * 0.1² + 5 * 0.1 + 5$$
$$S = 5.55$$

If we do the same calculation with the sum formula

![](https://user-images.githubusercontent.com/51274081/213756989-03bb092b-a0a3-447a-a0e8-83035559be7b.png)
$$S' = 4.995$$

## Tools Used

Manual inspection

## Recommended Mitigation Steps

Rather than dividing by 1 (1e18 from the Fixed library), divide it by the `ratio`.

```solidity
// Furnace.sol
// Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
uint192 payoutRatio = FIX_ONE.minus(FIX_ONE.minus(ratio).powu(numPeriods));

uint256 amount = payoutRatio * lastPayoutBal / ratio;
```

```solidity
// StRSR.sol
uint1
TradeLib - prepareTradeSell function does not handle zero division
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/mixins/TradeLib.sol#L38


# Vulnerability details

## Impact
This vulnerability can cause the function to return incorrect results or throw an exception when called with a trade whose buyPrice is zero. This could lead to unexpected behavior and/or errors in the smart contract using the TradeLib library.

## Proof of Concept
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/mixins/TradeLib.sol

## Tools Used
Manual.

## Recommended Mitigation Steps
The vulnerability can be fixed by adding a check for zero division in the prepareTradeSell function before the calculation of b, and returning an appropriate error or default value.

ex.: 
```
function prepareTradeSell(TradeInfo memory trade, TradingRules memory rules)
    internal
    view
    returns (bool notDust, TradeRequest memory req)
{
    // checked for in RevenueTrader / CollateralizatlionLib
    assert(trade.buyPrice > 0 && trade.buyPrice < FIX_MAX && trade.sellPrice < FIX_MAX);

    // Check for zero division
    if (trade.buyPrice == 0) {
        return (false, req);
    }

    (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice();

    // Don't sell dust
    if (!isEnoughToSell(trade.sell, trade.sellAmount, lotLow, rules.minTradeVolume)) {
        return (false, req);
    }

    // Cap sell amount
    uint192 maxSell = maxTradeSize(trade.sell, lotHigh); // {sellTok}
    uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok}

    // Calculate equivalent buyAmount within [0, FIX_MAX]
    // {buyTok} = {sellTok} * {1} * {UoA/sellTok} / {UoA/buyTok}
    uint192 b = safeMulDivCeil(
        ITrading(address(this)),
        s.mul(FIX_ONE.minus(rules.maxTradeSlippage)),
        trade.sellPrice, // {UoA/sellTok}
        trade.buyPrice // {UoA/buyTok}
    );

    // {*tok} => {q*Tok}
    req.sellAmount = s.shiftl_toUint(int8(trade.sell.erc20Decimals()), FLOOR);
    req.minBuyAmount = b.shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL);
    req.sell = trade.sell;
    req.buy = trade.buy;

    return (true, req);
}
```
Undefined 'erc20' variable in refresh() function and use of undefined 'shiftl_toFix' function in CTokenFiatCollateral contract
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/CTokenFiatCollateral.sol#L37
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/CTokenFiatCollateral.sol#L47


# Vulnerability details

## Impact
```
vulnerability in the use of the "erc20" variable in the refresh()
```
vulnerability in the use of the "shiftl_toFix" function occurs on the following line of code.

```
return shiftl_toFix(rate, shiftLeft);
```

`CTokenFiatCollateral` contract uses the "erc20" variable in the `refresh()` function, but it's not defined within the scope of the contract. It appears to be inherited from the `FiatCollateral` contract, but it's not clear if that contract properly defines and initializes the variable. This means that when the `refresh()` function is called, the "erc20" variable may not have a value, which could cause the function to throw an error or produce unexpected results.

## Proof of Concept

Exploiting the first bug involves creating an instance of the `CTokenFiatCollateral` contract, and then calling the `refresh()` function on it.

```
contract Exploit {
    CTokenFiatCollateral cTokenFiatCollateral;

    constructor() public {
        cTokenFiatCollateral = new CTokenFiatCollateral();
    }
}
```
Call the `refresh()` function on the contract instance.

```
function exploit() public {
    cTokenFiatCollateral.refresh();
}
```
After calling the `refresh()` function, I Observe that the function throws an error with unexpected results because the `erc20` variable is not defined within the scope of the contract.

## Tools Used

Manual audit, Remix, Vs Code

## Recommended Mitigation Steps

In the FiatCollateral contract, define the "erc20" variable as a storage variable and initialize it in the constructor.

```
contract FiatCollateral {
    ICToken public erc20;
    constructor(CollateralConfig memory config) {
        erc20 = ICToken(config.erc20);
    }
}
```

In the CTokenFiatCollateral contract constructor, pass the "erc20" variable from the FiatCollateral contract.

```
contract CTokenFiatCollateral is FiatCollateral {
    constructor(CollateralConfig memory config, IComptroller comptroller_) FiatCollateral(config) {
        erc20 = super.erc20;
        // other constructor code
    }
}
```

Re-compile and re-deploy the contract on the Ethereum network after making these changes.

Thoroughly test the contract with various inputs and scenarios to ensure it behaves as intended.
Malicious uses can prevente other users from issuing new RTokens by continuously issue and cancel RToken
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L186
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L406-L418


# Vulnerability details

## Impact

The RToken issuance will not work, i.e users will not be able to mint new RTokens.

## Proof of Concept

The issuing time of RToken is determined by [RToken#allVestAt](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L72) and issue amount.
Every issuance will increase the allVestAt, which will make later issuances to have a bigger issuing time.
Therefore, a malicious user can rapidly increase the allVestAt by issuing large issues, which makes all subsequent users have to wait a long time to issue. 

Originally, this wouldn't be a big deal, because it requires the malicious user to pledge all the assets to the protocol.
But, RToken also has a [RToken#cancel()](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L406) function which allows a user to cancel his pending issuances in the issueQueues at any time without any cost.

Using this, the malicious user can extend the issuance period for all subsequent users quickly and costless by iteratively calling `issue()` and `cancel()`.
Also because it is costless for calling `cancel()`, flashloan can be used to improve the efficiency of the attack.

## Tools Used

Manual

## Recommended Mitigation Steps

We can set a wait time for the cancel operation, that larger issuance will need wait more time to cancel.