xanoutas/solidity-security-findings huggingface dataset:
attacker can make stakeRate to be 1 in the StRSR contract and users depositing tokens can lose funds because of the big rounding error
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L160-L188
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L496-L530
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L212-L237


# Vulnerability details

## Impact
code calculates amount of stake token and rsr token based on `stakeRate` and if `stakeRate` was near `1e18` then division error is small but attacker can cause `stakeRate` to be 1 and that can cause users to loss up to `1e18` token during stake and unstake.

## Proof of Concept
this is `init()` code:
```
    function init(
        IMain main_,
        string calldata name_,
        string calldata symbol_,
        uint48 unstakingDelay_,
        uint48 rewardPeriod_,
        uint192 rewardRatio_
    ) external initializer {
        require(bytes(name_).length > 0, "name empty");
        require(bytes(symbol_).length > 0, "symbol empty");
        __Component_init(main_);
        __EIP712_init(name_, "1");
        name = name_;
        symbol = symbol_;

        assetRegistry = main_.assetRegistry();
        backingManager = main_.backingManager();
        basketHandler = main_.basketHandler();
        rsr = IERC20(address(main_.rsr()));

        payoutLastPaid = uint48(block.timestamp);
        rsrRewardsAtLastPayout = main_.rsr().balanceOf(address(this));
        setUnstakingDelay(unstakingDelay_);
        setRewardPeriod(rewardPeriod_);
        setRewardRatio(rewardRatio_);

        beginEra();
        beginDraftEra();
    }
```
As you can see it sets the value of the `rsrRewardsAtLastPayout` as contract balance when contract is deployed.
This is `_payoutReward()` code:
```
    function _payoutRewards() internal {
        if (block.timestamp < payoutLastPaid + rewardPeriod) return;
        uint48 numPeriods = (uint48(block.timestamp) - payoutLastPaid) / rewardPeriod;

        uint192 initRate = exchangeRate();
        uint256 payout;

        // Do an actual payout if and only if stakers exist!
        if (totalStakes > 0) {
            // Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
            // Apply payout to RSR backing
            // payoutRatio: D18 = FIX_ONE: D18 - FixLib.powu(): D18
            // Both uses of uint192(-) are fine, as it's equivalent to FixLib.sub().
            uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);

            // payout: {qRSR} = D18{1} * {qRSR} / D18
            payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
            stakeRSR += payout;
        }

        payoutLastPaid += numPeriods * rewardPeriod;
        rsrRewardsAtLastPayout = rsrRewards();

        // stakeRate else case: D18{qStRSR/qRSR} = {qStRSR} * D18 / {qRSR}
        // downcast is safe: it's at most 1e38 * 1e18 = 1e56
        // untestable:
Missing require statement for sellAmount in init() function leading to potential loss of funds.
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/trading/GnosisTrade.sol#L81-L94


# Vulnerability details

## Impact

`init()` function is missing a `require` statement to check if the input variable `sellAmount_` is less than or equal to the contract's balance of sell tokens. This means that the contract is allowing the user to `initiate` an auction with a sell amount greater than the contract's balance, which could lead to unexpected behavior and loss of funds.

```
function init(
        IBroker broker_,
        address origin_,
        IGnosis gnosis_,
        uint48 endTime_,
        IERC20Metadata sell_,
        uint256 sellAmount_,
        IERC20Metadata buy_,
        uint192 minBuyAmount_,
        uint192 worstCasePrice_
    )
        public
        stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN)
        returns (bool)
    {
        require(endTime_ > now, "endTime must be in the future");
        require(sellAmount_ < 2**96, "sellAmount out of bounds");
        require(minBuyAmount_ < 2**96, "minBuyAmount out of bounds");
        // MISSING REQUIRE STATEMENT
    }
```
This missing `require` statement is the vulnerability that allows the smart contract to proceed with the auction even when it does not have enough balance to complete the transaction, this could lead to unexpected behavior and potential loss of funds.


## Proof of Concept

To exploit this vulnerability we'll create a malicious contract that calls the `init()` function of the vulnerable contract, passing in a `sell` amount greater than the contract's balance of sell tokens.

Let's assume the contract's balance of sell tokens is 50, and the malicious contract calls the `init()` function with a sell amount of 100 tokens.

```
// Malicious contract
contract Attacker {
    GnosisTrade public victim;

    function attack() public {
        victim.init(..., 100, ...);
    }
}
```
As the `init()` function does not have a require statement to check if the input variable `sellAmount_` is less than or equal to the contract's balance of sell tokens, the contract would proceed with the auction and attempt to sell 100 tokens, even though it only has 50 tokens available. This would result in the user losing 50 tokens because the contract does not have enough balance to complete the transaction.

Additionally, this vulnerability could also be `exploited by an attacker to drain` the contract's balance by initiating multiple auctions with large sell amounts.


## Tools Used

Manual audit, Vs Code

## Recommended Mitigation Steps

The `init()` function should be updated to include a `require` statement that checks if the input variable `sellAmount_` is less than or equal to the contract's balance of sell tokens. This will ensure that the contract only proceeds with the auction if it has enough balance to complete the transaction.

```
function init(
        IBroker broker_,
        address origin_,
Fee on transfer based collateral is not considered.
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
It has following impacts.
1. `backingManager` would be transferred with lesser than `deposits`
2. Recorded `liabilities` will be more
3. collateral deposited into the `RToken` contract would be lesser than `deposits`

## Proof of Concept
Any one can mint RToken by depositing backet of valid collaterals. The issue here is with collateral that would charge upon transferring.

Some ERC20 token implementations have a fee that is charged on each token transfer. This means that the transferred amount isn't exactly what the receiver will get. A call to IERC20(token).transfer(recipient, 100) with a fee-on-transfer (FOT) of 5% will entitle the recipient with 95 tokens.

The main issue then lies in that the protocol will pull funds from arbitrary ERC20 token implementation using transferFrom, assuming that the receiving amount matches the amount sent as the parameter to the transfer call, which is not always the case.

Fee on Transfer
Some tokens take a transfer fee (e.g. STA, PAXG), some do not currently charge a fee but may do so in the future (e.g. USDT, USDC).

The STA transfer fee was used to drain $500k from several balancer pools (more details).

https://github.com/d-xo/weird-erc20#:~:text=Fee%20on%20Transfer,%3A%20TransferFee.sol

In RToken contract, through `issue` function , time-delayed issuance of RToken is done for basket collateral.

based on the `RToken` is going to be minted and the `totalSupply()`, the amtBaskets is calculated as shown below,

        uint192 amtBaskets = uint192(
            totalSupply() > 0 ? mulDiv256(basketsNeeded, amtRToken, totalSupply()) : amtRToken
        );

the amount is going to be deposited is calculated from `basketHandler.quote`

        (address[] memory erc20s, uint256[] memory deposits) = basketHandler.quote(
            amtBaskets,
            CEIL
        );

For the `amtBaskets`, the deposit amount will be decided in Baskehandler.sol

    function quote(uint192 amount, RoundingMode rounding)
        external
        view
        returns (address[] memory erc20s, uint256[] memory quantities)
    {
        uint256 length = basket.erc20s.length;
        erc20s = new address[](length);
        quantities = new uint256[](length);


        for (uint256 i = 0; i < length; ++i) {
            erc20s[i] = address(basket.erc20s[i]);


            // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok}
            quantities[i] = quantity(basket.erc20s[i]).mul(amount, rounding).shiftl_toUint(
                int8(IERC20Metadata(address(basket.erc20s[i])).decimals()),
                rounding
            );
        }
    }

After deciding the deposit amount from above process, it will be transferred to `backingManager` . For fee on trasfer token, the actual amount is transferred is lesser than the deposit value.

            for (uint256 i = 0;
Missing Function Definition in tryPrice() leading to contract failure.
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/RTokenAsset.sol#L44-L55


# Vulnerability details

## Impact
`basketRange()` in the contract. The function `tryPrice()` calls the function `basketRange()` from the library RecollateralizationLibP1, which is not defined in the contract. As a result, when `tryPrice()` is executed, it will cause the contract to revert and throw an error, preventing it from continuing execution. This means that any functionality that relies on `tryPrice()` will also fail to execute.

## Proof of Concept
```
pragma solidity 0.8.9;

import "./RTokenAsset.sol";

contract Attacker {
    RTokenAsset public rTokenAsset;

    constructor(address rTokenAssetAddress) {
        rTokenAsset = RTokenAsset(rTokenAssetAddress);
    }

    function exploit() public {
        // Call the tryPrice function in the RTokenAsset contract
        rTokenAsset.tryPrice();
    }
}
```

The code above creates a contract called "Attacker" that takes the address of an `RTokenAsset` contract as an input to its constructor. The exploit function calls the `tryPrice()` function of the `RTokenAsset` contract. Because the `tryPrice()` function in the `RTokenAsset` contract calls the `basketRange()` function, which is not defined, calling `exploit()` will cause the `RTokenAsset` contract to revert and fail, preventing it from functioning correctly.

To exploit this vulnerability, the attacker could deploy this contract and call its `exploit()` function, causing the `RTokenAsset` contract to fail. This will cause a significant impact on the usability and functionality of the contract, making it unusable for its intended purpose.

## Tools Used

Manual audit, Vs Code

## Recommended Mitigation Steps

* Define the function `basketRange()` in the contract. 

```
function basketRange() internal view returns (RecollateralizationLibP1.BasketRange memory) {
    //implement the function here
}
```
This will ensure that the function is defined and can be called by `tryPrice()` without causing the contract to revert.

* Import the correct library that defines the function.

```
import "path/to/RecollateralizationLibP1.sol";
```
This will allow the contract to access the `basketRange()` function defined in the library.

* Remove the call to `basketRange()` function from `tryPrice()`. If the function is not needed in the `tryPrice()` function and it is causing the contract to revert, it can be removed.
Baited by redemption during undercollateralization (no issuance, just transfer)
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
This is similar to the "high" vulnerability I submitted, but also shows a similar exploit can be done if a user isn't a whale, and isn't issuing anything. 

A user can send a redeem TX and an evil actor can make it so they get almost nothing back during recollateralization. This requires ordering transactions, or just getting very unlucky with the order of your transaction. 

## Proof of Concept
- UserA is looking to redeem their rToken for tokenA (the max the battery will allow, let's say 100k)
- A basket refresh is about to be triggered
- Evil user wants the protocol to steal UserA's funds
- UserA sends redeem TX to the mempool, but Evil user move transactions around before it hits

- Evil user calls refreshbasket in same block as original collateral (tokenA) is disabled, kicking in backupconfig (tokenB)
- Protocol is now undercollateralized but collateral is sound (tokenB is good)
- Evil sends 1tokenB to backingManager to UserA's redeem has something to redeem
- UserA's redemption tx lands, and redeems 100k rTokens for a fraction of tokenB! 

UserA redeems and has nothing to show for it!
Evil user only had to buy 1 tokenB (or even less) to steal 100k of their rToken

## Tools Used

Hardhat

## Recommended Mitigation Steps

Disallow redemptions/issuance during undercollateralization

## Proof of Code 

To run:
1. git clone https://github.com/reserve-protocol/protocol.git
2. cd protocol
3. Copy paste the below code to a file like `/tmp/changes.patch`
4. git apply /tmp/changes.patch
5. yarn
6. yarn hardhat test test/submission-test.test.ts

```
diff --git a/contracts/ShitAsset.sol b/contracts/ShitAsset.sol
new file mode 100644
index 00000000..5420e914
--- /dev/null
+++ b/contracts/ShitAsset.sol
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: BlueOak-1.0.0
+pragma solidity 0.8.9;
+
+import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+import "./plugins/assets/OracleLib.sol";
+import "hardhat/console.sol";
+
+contract ShitAsset {
+    using FixLib for uint192;
+    using OracleLib for AggregatorV3Interface;
+
+    // AggregatorV3Interface public immutable chainlinkFeed; // {UoA/tok}
+
+    IERC20Metadata private s_erc20;
+    bool revertErc20 = false;
+
+    CollateralStatus myStatus;
+
+    uint8 public immutable erc20Decimals;
+
+    uint192 public immutable maxTradeVolume; // {UoA}
+
+    uint48 public immutable oracleTimeout; // {s} Seconds that an oracle value is considered valid
+
+    // uint192 public immutable oracleError; // {1} The max % deviation allowed by the oracle
+
+    // === Lot price ===
+
+    uint48 public immutable priceTimeout; // {s} The period over which `savedHighPrice` decays to 0
+
+    uint192 public savedLowPrice; // {UoA/tok} The low price of the
RToken issuers are not protected from slippage
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users can get loss from the slippage due to various potential reasons.

## Proof of Concept
Users can issue new RTokens by depositing the necessary collaterals using the function `issue()`.
The function `issue()` has two parameters specifying the `recipient` and `amtRToken`.
Note that it does not provide a parameter to specify the maximum spending of the actual collateral tokens.

```solidity
RToken.sol
186:     function issue(address recipient, uint256 amtRToken)
187:         public
188:         notPausedOrFrozen
189:         returns (uint256)
190:     {
191:         require(amtRToken > 0, "Cannot issue zero");
192:
193:         // == Refresh ==
194:         assetRegistry.refresh();
195:
196:         address issuer = _msgSender(); // OK to save: it can't be changed in reentrant runs
197:
198:         uint48 basketNonce = basketHandler.nonce();
199:         IssueQueue storage queue = issueQueues[recipient];
200:
201:         // Refund issuances against old baskets
202:         if (queue.basketNonce > 0 && queue.basketNonce != basketNonce) {
203:             // == Interaction ==
204:             // This violates simple CEI, so we have to renew any potential transient state!
205:             refundSpan(recipient, queue.left, queue.right);
206:
207:             // Refresh collateral after interaction
208:             assetRegistry.refresh();
209:
210:             // Refresh local values after potential reentrant changes to contract state.
211:             basketNonce = basketHandler.nonce();
212:             queue = issueQueues[recipient];
213:         }
214:
215:         // == Checks-effects block ==
216:         CollateralStatus status = basketHandler.status();
217:         require(status == CollateralStatus.SOUND, "basket unsound");
218:
219:         furnace.melt();
220:
221:         // AT THIS POINT:
222:         //   all contract invariants hold
223:         //   furnace melting is up-to-date
224:         //   asset states are up-to-date
225:         //   queue.basketNonce = basketHandler.nonce()
226:
227:         // Compute the whole issuance span. We want to accumulate the issuance:
228:         // iss = {when: vestingEnd' - vestingEnd, amtRToken, amtBaskets, deposits}
229:
230:         // amtBaskets: the BU change to be recorded by this issuance
231:         // D18{BU} = D18{BU} * {qRTok} / {qRTok}
232:         // Downcast is safe because an actual quantity of qBUs fits in uint192
233:         uint192 amtBaskets = uint192(
234:             totalSupply() > 0 ? mulDiv256(basketsNeeded, amtRToken, totalSupply()) : amtRToken
235:         );
236:
237:         (address[] memory erc20s, uint256[] memory deposits) = basketHandler.quote(
238:             amtBaskets,
239:             CEIL
240:         );
241:
242:         // Add amtRToken's worth of issuance delay to allVestAt
243
Small amounts of funds can be stolen during recollateralization
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
This is similar to the "high" vulnerability I submitted but shows a similar exploit can be done if a user isn't a whale, stealing a smaller amount of funds. This is potentially a "high" risk depending on how easy you think execution is. I think it's probably "high" but I'm being conservative. 

A user can send a redeem TX and an evil actor can make it so they get almost nothing back during recollateralization. This requires ordering transactions, or just getting very unlucky with the order of your transaction. 

IE: You hit an innocuous `redeem` function, and a malicious actor spends almost no resources to steal funds not greater than the redemption battery allowance.

Difficulty: High/Med. They can build a bot the just waits for this scenario, which wouldn't be too uncommon. They could also spread FUD to tell people to redeem near a recollateralization time.
Impact: High. You  can steal a "small" amount of funds (up to the battery redemption rate), but depending on the rToken supply this can be very high (potentially tens or hundreds of thousands of dollars).

## Proof of Concept
- UserA is looking to redeem their rToken for tokenA (the max the battery will allow, let's say 100k)
- A basket refresh is about to be triggered
- Evil user wants the protocol to steal UserA's funds
- UserA sends redeem TX to the mempool, but Evil user move transactions around before it hits

- Evil user calls refreshbasket in same block as original collateral (tokenA) is disabled, kicking in backupconfig (tokenB)
- Protocol is now undercollateralized but collateral is sound (tokenB is good)
- Evil user issues a 1 RToken for 1 tokenB, now the protocol has 1 tokenB for redeemers
- UserA's redemption tx lands, and redeems 100k rTokens for a fraction of tokenB! 

UserA redeems and has nothing to show for it!
Evil user only had to buy 1 tokenB (or even less) to steal 100k of their rToken

## Tools Used

Hardhat

## Recommended Mitigation Steps

Disallow issuance/redemptions during undercollateralization

## Proof of Code
To run:
1. git clone https://github.com/reserve-protocol/protocol.git
2. cd protocol
3. Copy paste the below code to a file like `/tmp/changes.patch`
4. git apply /tmp/changes.patch
5. yarn
6. yarn hardhat test test/submission-test.test.ts

```
diff --git a/contracts/ShitAsset.sol b/contracts/ShitAsset.sol
new file mode 100644
index 00000000..5420e914
--- /dev/null
+++ b/contracts/ShitAsset.sol
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: BlueOak-1.0.0
+pragma solidity 0.8.9;
+
+import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
+import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+import "./plugins/assets/OracleLib.sol";
+import "hardhat/console.sol";
+
+contract ShitAsset {
+    using FixLib for uint192;
+    using OracleLib for Agg
Wrong check isContract for Permit
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/libraries/Permit.sol#L17


# Vulnerability details

## Impact
In function: requireSignature. The validation to check whether the msg.sender is a smart contract is not enough in this case.
Anyone could bypass that check by calling that function from the constructor of a newly deployed contract. Therefore not the right validation of the signature will be done

## Proof of Concept

Attacker contract:
      
       //previous declarations

        constructor(Reserve _target) {
        Reserve= _target;
        //isContract fails and returns false. So we can avoid the requirements
        isContract = Address.isContract(address(this));
        addr = address(this);
        // This will work
        Reserve .requireSignature(...params...);
    
}


## Tools Used

manual

## Recommended Mitigation Steps

add to the isContract also a check that 

       require(msg.sender == tx.origin); //this will solve the issue
Deletion on mapping containing a structure
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BasketHandler.sol#L219


# Vulnerability details

## Impact
A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. 

## Proof of Concept

```
BasketHandler.sol:
219: delete config.targetAmts[config.erc20s[i]];
220: delete config.targetNames[config.erc20s[i]];
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use a lock mechanism instead of a deletion to disable structure containing a mapping.