xanoutas/solidity-security-findings huggingface dataset:
Lack of sequencer uptime checks can lead to dutch auctions executing at bad prices or failing
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/plugins/trading/DutchTrade.sol#L199-L205


# Vulnerability details


## Proof of Concept

First, per the readMe, protocol is to be deployed on multiple chains, optimistic L2s included, see https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/README.md#L140

```markdown
| Chains the protocol will be deployed on | Ethereum,Arbitrum,Base,Optimism |
```

The protocol implements Dutch auction trades via [DutchTrade.sol](https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/plugins/trading/DutchTrade.sol). The auction mechanism is designed with a specific price decay structure over time: _see the [docs](https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/plugins/trading/DutchTrade.sol#L27-L31) for a short map on how these auctions should work._

```solidity
// A dutch auction in 4 parts:
//   1.  0% -  20%: Geometric decay from 1000x the bestPrice to ~1.5x the bestPrice
//   2. 20% -  45%: Linear decay from ~1.5x the bestPrice to the bestPrice
//   3. 45% -  95%: Linear decay from the bestPrice to the worstPrice
//   4. 95% - 100%: Constant at the worstPrice
```

However, the contract does not include any checks for sequencer uptime. This can be problematic because network outages and reorgs happen quite often with optimistic L2s.

Now to place bids we have https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/plugins/trading/DutchTrade.sol#L199-L205

```solidity
    function bid() external returns (uint256 amountIn) {
        require(bidder == address(0), "bid already received");
        assert(status == TradeStatus.OPEN);

        // {buyTok/sellTok}
        uint192 price = _price(uint48(block.timestamp)); // enforces auction ongoing //@audit
..snip
    }

```

Take a look at the [`_price()` function](https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/plugins/trading/DutchTrade.sol#L314-L360), which calculates the auction price based on the current timestamp:

```solidity
function _price(uint48 timestamp) private view returns (uint192) {
  uint48 _startTime = startTime; // {s} gas savings
  uint48 _endTime = endTime; // {s} gas savings
  require(timestamp >= _startTime, 'auction not started');
  require(timestamp <= _endTime, 'auction over');

  uint192 progression = divuu(timestamp - _startTime, _endTime - _startTime); // @audit

  // ...snip
}

```

Now, if there's a sequencer downtime, which we should agree the likelihood to be substantial considering the various chains protocol is to deploy to the timestamp used for price calculation could be significantly off, leading to incorrect pricing.

## Impact

Auctions could execute at unfavorable prices if there's a significant delay between when a bid is
Precision loss when calculating the transfer amount inside the distribute function of the Distributor contract
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Less transfer amounts are calculated in the P1 distributor due to the precision loss

## Description 
Solidity rounds down the result of an integer division, and because of that, it is always recommended to multiply before dividing to avoid that precision loss. In the case of a prior division over multiplication, the final result may face serious precision loss as the first answer would face truncated precision and then multiplied to another integer.

The problem arises in the distributor's distribution part. After fetching the total shares, the next step is to calculate the `tokensPerShare` which is then used to compute the transfer amounts and sending them to the allowed recipients. 

If we look deeply at the function `distribute()` we can see the transfer amounts calculation procedure is presented as:

```Solidity
    function distribute(IERC20 erc20, uint256 amount) external {

    ...

            totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal;
            if (totalShares != 0) tokensPerShare = amount / totalShares;
            require(tokensPerShare != 0, "nothing to distribute");
        }

    ...

            if (numberOfShares == 0) continue;
            uint256 transferAmt = tokensPerShare * numberOfShares;

    ...

    }
```
we can see that inside this pattern, there is a hidden division before a multiplication that makes round down the whole expression. This is bad as the precision loss can be significant, which leads the distributor to send less `transferAmt` than actual.

At the Proof of Concept part, we can check this behavior precisely.

## Proof of Concept
You can run this code to see the difference between the results:

```Solidity
    function test_precissionLoss() public {

        uint256 amount = 4.512317e19; // This number represents the `amounts`
        uint256 totalShares = 6.525132e18; // This number represents the total shares
        uint256 numberOfShares = 2.14235e18;
        uint256 tokensPerShare = amount / totalShares;

        uint256 transferAmount1 = tokensPerShare * numberOfShares;
        uint256 transferAmount2 = amount * numberOfShares / totalShares;
        
        console.log("Current Implementation   ", transferAmount1);
        console.log("Accurate Implementation  ", transferAmount2);
        assertNotEq(transferAmount1, transferAmount2);
    }
```

The result would be:

```  
     Current Implementation    12854100000000000000
     Accurate Implementation   14814968225853515300
```

This test shows that for a sample real data, the relative loss is around ~15.2%. Thus, we can see that the actual implementation produces less transfer amount than the precise method.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Consider modifying the transfer amount calculation to prevent such precision loss and prioritize the multiplicatio
Potential Risk that Deprecated Assets can be Used as Valid Ones
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L148-L154
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L159-L167
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L126-L129
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L134-L138


# Vulnerability details

## Impact

Deprecated assets by `AssetPluginRegistry` can be retrieved as normal ones in backing manager and revenue traders via the `AssetRegistry::getRegistry()` and `AssetRegistry::erc20s()` functions, and may cause a potential danger to the protocol.

## Proof of Concept

`AssetPluginRegistry` contract provides additional asset management functionality including deprecation.

But the deprecation of an asset is checked only when it is being registered via the `_registerIgnoringCollisions()` function, and once regstered, deprecation is not considered when calling asset registry functions, so deprecated assets can be provided as valid ones to the backing manager, revenue trader, etc.

And more dangerously, deprecated assets can be parts of prime baskets when a governer tries or force to set them.

## Tools Used

Manual Review

## Recommended Mitigation Steps

I suggest updating `AssetRegistry::getRegistry()` function by adding validation checks like the below:

```solidity
    function getRegistry() public view returns (Registry memory reg) {
        uint256 length = _erc20s.length();
        reg.erc20s = new IERC20[](length);
        reg.assets = new IAsset[](length);
        for (uint256 i = 0; i < length; ++i) {
            if (address(assetPluginRegistry) != address(0)) {
                if (assetPluginRegistry.isValidAsset(
                        keccak256(abi.encodePacked(this.version())),
                        address(assets[IERC20(_erc20s.at(i))])
                    )) {
                    reg.erc20s[i] = IERC20(_erc20s.at(i));
                    reg.assets[i] = assets[IERC20(_erc20s.at(i))];
                }
            }
        }
    }
```

And also do this to the `erc20s()`, `toAsset()`, `toColl()` functions.






## Assessed type

Invalid Validation
Admin can't set throttle amount as zero.
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/tree/main/contracts/p1/RToken.sol#L452
https://github.com/code-423n4/2024-07-reserve/tree/main/contracts/p1/RToken.sol#L463


# Vulnerability details

## Impact
Admin can't set throttle amount as zero to solely rely on the issuance throttle rate.
The same problem also exists in `RToken.sol#setRedemptionThrottleParams`.

## Proof of Concept
`RToken.sol#setIssuanceThrottleParams` function is the following.
```solidity
    function setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
452:    require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small");
        require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big");
        require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big");
        issuanceThrottle.useAvailable(totalSupply(), 0);

        emit IssuanceThrottleSet(issuanceThrottle.params, params);
        issuanceThrottle.params = params;
    }
```
Since `constant MIN_THROTTLE_RATE_AMT = 1e18 > 0` in `L452`, `params.amtRate` can't be zero.
However the [docs](https://reserve.org/protocol/rtokens/#advanced-rtoken-parameters) stated that:
```
Must be at least 1 whole RToken. Can be set to 0 to solely rely on the issuance throttle rate.
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Modify `RToken.sol#setIssuanceThrottleParams` function as follows.
```solidity
    function setIssuanceThrottleParams(ThrottleLib.Params calldata params) public governance {
--      require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small");
++      require(params.amtRate == 0 || params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small");
        require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big");
        require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big");
        issuanceThrottle.useAvailable(totalSupply(), 0);

        emit IssuanceThrottleSet(issuanceThrottle.params, params);
        issuanceThrottle.params = params;
    }
```



## Assessed type

Error
Unintended Token Transfer Can Disrupt Distribution Process (`RevenueTraderP1::returnTokens`)
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L76-L93


# Vulnerability details

## Description

The `RevenueTraderP1` contract is designed to manage and distribute revenue by converting all asset balances to a single target asset (`tokenToBuy`) and distributing it through the `Distributor`. The [`returnTokens` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L76-L93) is intended to return registered ERC20 tokens to the `BackingManager` if the distribution for `tokenToBuy` is zero. However, the function does not prevent `tokenToBuy` from being included in the tokens being returned, which can disrupt the intended distribution process.

The [`returnTokens` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L76-L93) checks if the total distribution for `tokenToBuy` is zero using `revTotals`. If the total distribution is zero, it proceeds to return the tokens in the `erc20s` array to the `BackingManager`. The function ensures that only registered ERC20 tokens are returned but does not explicitly exclude `tokenToBuy` from being returned.

This logical flaw allows `tokenToBuy` to be transferred back to the `BackingManager`, even if it has a non-zero balance intended for future distribution. This could lead to a situation where funds meant for distribution are incorrectly sent back to the `BackingManager`, disrupting the intended token flow and distribution process.

Relevant code:
```solidity
File: RevenueTrader.sol
76:     function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen {
77:         RevenueTotals memory revTotals = distributor.totals();
78:         if (tokenToBuy == rsr) {
79:             require(revTotals.rsrTotal == 0, "rsrTotal > 0");
80:         } else if (address(tokenToBuy) == address(rToken)) {
81:             require(revTotals.rTokenTotal == 0, "rTokenTotal > 0");
82:         } else {
83:             // untestable: tokenToBuy is always the RSR or RToken
84:             revert("invalid tokenToBuy");
85:         }
86: 
87:         // Return ERC20s to the BackingManager
88:         uint256 len = erc20s.length;
89:         for (uint256 i = 0; i < len; ++i) {
90:             require(assetRegistry.isRegistered(erc20s[i]), "unregistered erc20");
91:             erc20s[i].safeTransfer(address(backingManager), erc20s[i].balanceOf(address(this)));
92:         }
93:     }

```

## Impact
This can disrupt the intended distribution process, leading to incorrect token management. If `tokenToBuy` is included in the `erc20s` array, it will be returned to the `BackingManager`, even if it has a non-zero balance intended for distribution. This could lead to an unintended transfer of funds, potentially causing a loss of tokens meant for distribution and disrupting the overall
Silent Failure in `settleTrade` Can Lead to Incomplete Operations and Stuck Funds (`RevenueTraderP1::settleTrade`)
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L54-L65


# Vulnerability details

## Description 

The `RevenueTraderP1` contract is designed to settle trades and distribute the bought tokens. The [`settleTrade` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L54-L65) calls `super.settleTrade(sell)` to settle a trade and then attempts to distribute the bought tokens using `distributeTokenToBuy`. The error handling for `distributeTokenToBuy` is implemented using a `try-catch` block, which only reverts if the error data is empty, typically indicating an out-of-gas (OOG) error. This approach can lead to silent failures for any other type of error, resulting in incomplete operations and potential for stuck funds.

The [`settleTrade` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RevenueTrader.sol#L54-L65) is intended to perform two main tasks: settle a trade and distribute the bought tokens. If the distribution fails and the error is ignored, the function will return successfully, misleading users and other parts of the system into thinking that both tasks were completed. This can lead to a situation where the contract's state is inconsistent with the expected state, specifically resulting in tokens being stuck in the contract without being distributed.

The relevant part of the code is as follows:

```solidity
File: RevenueTrader.sol
54:     function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) {
55:         trade = super.settleTrade(sell); // nonReentrant
56: 
57:         // solhint-disable-next-line no-empty-blocks
58:         try this.distributeTokenToBuy() {} catch (bytes memory errData) {
59:             // untested:
60:             //     OOG pattern tested in other contracts, cost to test here is high
61:             // see: docs/solidity-style.md#Catching-Empty-Data
62:             if (errData.length == 0) revert(); // solhint-disable-line reason-string
63:         }
64:         // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely
65:     }

```

The issue arises because the `try-catch` block catches all errors and only reverts on OOG errors, ignoring other types of errors. This can lead to incomplete operations where the trade is settled, but the distribution fails without any indication. If the distribution fails consistently for reasons other than OOG, the bought tokens could accumulate in the contract without being distributed, effectively becoming stuck.

## Impact
The impact of this finding is significant because it can lead to a situation where the contract's state is inconsistent with the expected state. Specifically, it can result in tokens being stuck in the contract, which can affect the overall functionality and
Attacker can force basket disabling during asset unregistration (`AssetRegistryP1::unregister`)
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L108-L121
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L112
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L113
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L253-L264


# Vulnerability details

## Description

The `AssetRegistryP1` contract manages the registration and unregistration of assets in the Reserve Protocol. The [`unregister()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L108-L121) is responsible for removing an asset from the registry. During this process, it calls [`basketHandler.quantity()`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L112) with a reserved amount of gas to check the quantity of the asset in the basket. If this call fails or returns a non-zero quantity, the basket is disabled by calling [`basketHandler.disableBasket()`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L113).

The issue lies in the [`_reserveGas()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/AssetRegistry.sol#L253-L264), which is supposed to ensure sufficient gas for the `basketHandler.quantity()` call:

```solidity
function _reserveGas() private view returns (uint256) {
    uint256 gas = gasleft();
    require(
        gas > (64 * GAS_FOR_BH_QTY) / 63 + GAS_FOR_DISABLE_BASKET,
        "not enough gas to unregister safely"
    );
    return GAS_FOR_BH_QTY;
}
```

This function does not account for the minimum gas needed for the actual execution of `basketHandler.quantity()`. An attacker can exploit this by providing just enough gas to pass the requirement check but not enough for the actual execution, causing the call to fail and the basket to be disabled unnecessarily.

The issue is particularly concerning in the `unregister()` function:

```solidity
function unregister(IAsset asset) external governance {
    // ... (previous checks)
    try basketHandler.quantity{ gas: _reserveGas() }(asset.erc20()) returns (uint192 quantity) {
        if (quantity != 0) basketHandler.disableBasket();
    } catch {
        basketHandler.disableBasket();
    }
    // ... (asset unregistration logic)
}
```

An attacker can craft a transaction with precisely calculated gas to trigger the basket disabling, potentially disrupting protocol operations and causing unnecessary trades.

## Impact

An attacker can manipulate the gas provided to the `basketHandler.quantity()` call, causing it to revert and
`BasketHandler::price` returns the wrong lower end of the price estimate
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Fixed.sol#L503
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L429-L431


# Vulnerability details

## Impact
And the users/Integrators using [BasketHandler::price](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L429-L431) to get the `price of a BU` will read the wrong  lower end of the price estimate of teh rToken.

## Proof of Concept 
[Fixed::safeMul](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Fixed.sol#L503) should return FIX_MAX if eitehr a r b == FIX_MAX. And it does, but when b == 0, a will be FIX_MAX says the comment. And if b == 0 the previous line itself will return 0.

So the code logic to return FIX_MAX if a == FIX_MAX, b == 0  will never happen. Instead flip the lines, so that the first validation of a,b is done with FIX_MAX and not 0.

[BasketHandler::price](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L429-L431) has a flow where lowP == 0, highP == FIX_MAX and it calls `low256 += qty.safeMul(lowP, FLOOR);`. So here a == qty, b = lowP. And in a case where qty = FIX_MAX, lowP = 0, the safeMul should return `FIX_MAX` but instead it returns 0. So, the [BasketHandler::price](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L429-L431) calcualtion of final lowP is malicious.

[Fixed::safeMul](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Fixed.sol#L503)
```solidity
    function safeMul(
        uint192 a,
        uint192 b,
 RoundingMode rounding
 ) internal pure returns (uint192) {
        // untestable:
        //      a will never = 0 here because of the check in _price()
        if (a == 0 || b == 0) return 0;
        // untestable:
 @@   //      a = FIX_MAX iff b = 0 
        if (a == FIX_MAX || b == FIX_MAX) return FIX_MAX;

----------------------

 }
```

[BasketHandler::price](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L429-L431)

```solidity
    function price(bool applyIssuancePremium) public view returns (uint192 low, uint192 high) {
        uint256 low256;
        uint256 high256;

        uint256 len = basket.erc20s.length;
        for (uint256 i = 0; i < len; ++i) {
            try assetRegistry.toColl(basket.erc20s[i]) returns (ICollateral coll) {
                uint192 qty = _quantity(basket.erc20s[i], coll, CEIL);
                if (qty == 0) continue;

 @@          (uint192 lowP, uint192 highP) = coll.price();

 @@          low256 += qty.safeMul(lowP, FLOOR);
```

## Tools Used
Vs cod