xanoutas/solidity-security-findings huggingface dataset:
Running external rebalancing might be delayed even after a Dutch auction has already been settled
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

It will be unable to run external rebalancing in backing manager until `tradeEnd[DUTCH_AUCTION]` expires, even when a Dutch auction has already been settled.

## Proof of Concept

To prevent an external rebalancing in the same block of the settlement, the `BackingManager` has the following restriction:

```solidity
    function rebalance(TradeKind kind) external nonReentrant {
        ... ...
        require(
            _msgSender() == address(this) || tradeEnd[kind] < block.timestamp, 
            "already rebalancing"
        );
        ... ...
    }
```

a Dutch auction is considered settled as soon as any bidder places a bid, regardless of the remaining auction period.

Let's assume the following scenario.

1. There is a collateral default, so starts a dutch auction of 6 days (the maximum auction length is 7 days).
2. After 3 days are passed, the auction is settled by a bidder, and as there's no more collateral default, the next rebalancing is just returned without running a new auction.
3. In the next day, a new collateral default happens, so when trying to run rebalancing by external call, it is reverted by the restriction of `tradeEnd[DUTCH_AUCTION]` because 2 days are remaining.

Therefore, during the remaining auction period, calling the `rebalance()` function will be reverted by the above restriction.

## Tools Used

Manual Review

## Recommended Mitigation Steps

I would suggest updating `tradeEnd[DUTCH_AUCTION]` in the `settleTrade()` function:

```diff
    function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) {
        delete tokensOut[sell];
        trade = super.settleTrade(sell); // nonReentrant
+       if (trade.KIND() == TradeKind.BATCH_AUCTION && block.timestamp < tradeEnd[trade.KIND()])
+           tradeEnd[trade.KIND()] = block.timestamp;
        ... ...
    }
```



## Assessed type

Invalid Validation
Some tokens revert if approval amounts are > type(uint96).max
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L73
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Allowance.sol#L41


# Vulnerability details

## Impact
And they also change the allowance amount to type(uint96).max if the amount given is type(uint256).max

![ERC20 token behaviors in scope](https://github-production-user-asset-6210df.s3.amazonaws.com/92864243/358510854-2a9cb8f1-507a-4ae8-a6c6-8e32b4a00100.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240816%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240816T075315Z&X-Amz-Expires=300&X-Amz-Signature=88cf7eedbbc60afaa7bda8beaf92a9b45eeeb803691e0976bd95df399310a808&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=0)

## Proof of Concept 

[ERC20 token behaviors in scope](https://github.com/code-423n4/2024-07-reserve?tab=readme-ov-file#erc20-token-behaviors-in-scope) shows that tokens that [Revert on Large Approvals & Transfers](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#revert-on-large-approvals--transfers) are in scope. 
Some tokens (e.g. UNI, COMP) revert if the value passed to approve or transfer is larger than uint96.

[BackingManagerP1::grantRTokenAllowance](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L73)
```solidity
    function grantRTokenAllowance(IERC20 erc20) external notFrozen {
        require(assetRegistry.isRegistered(erc20), "erc20 unregistered");
        // == Interaction ==
        IERC20(address(erc20)).safeApprove(address(rToken), 0);
 @@   IERC20(address(erc20)).safeApprove(address(rToken), type(uint256).max);
 }
```

[AllowanceLib::safeApproveFallbackToMax](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Allowance.sol#L41)

```solidity
        function safeApproveFallbackToMax(
        address tokenAddress,
        address spender,
        uint256 value
 ) internal {
 IERC20ApproveOnly token = IERC20ApproveOnly(tokenAddress);

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

        // 3. Fall-back to setting a maximum allowance
        if (!success) {
 @@      token.approve(spender, type(uint256).max);
            // untestable:
            //    allowance should always be max value if token behaves correctly
            require(token.allowance(address(this), spender) >= value, "allowance missing");
 }
 }
```

## Tools Used
Vs code

## Recommended Mitigation Steps
Acknowledge that amount passed over type(uint96).max will only result in reverting and the system expects to use the amount in uint256. Don't allow UNI and COMP tokens to a backing token of rToken.



## Assessed type

ERC20
dutchAuctionLength & batchAuctionLength are allowed set to zero.
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L221
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L199


# Vulnerability details

## Vulnerability details
[dutchAuctionLength](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L221) and [batchAuctionLength](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L199) are allowed set to zero in broker contract. So governance set `dutchAuctionLength` and  `batchAuctionLength` to zero , two auction models can not be initiated since its revert the transaction. So protocol is going to be in insolvant condition.

## Proof of Concept
```solidity

   function setDutchAuctionLength(uint48 newAuctionLength) public governance {
        require(
            newAuctionLength == 0 ||
                (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH),
            "invalid dutchAuctionLength"
        );
        emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength);
        dutchAuctionLength = newAuctionLength;
    }


```

Governance able to set `dutchAuctionLength` to zero. If its zero then `openTrade` function initiating a  `newDutchAuction` there its reveted due to [this](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L277) reqiure statement.

```solidity

 function newDutchAuction(
        TradeRequest memory req,
        TradePrices memory prices,
        ITrading caller
    ) private returns (ITrade) {

      ...
      require(dutchAuctionLength != 0, "dutch auctions not enabled");
      ...
}
```

Same thing happend with [batch auction](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L245C9-L245C72). 

```solidity
 function setBatchAuctionLength(uint48 newAuctionLength) public governance {
        require(
            newAuctionLength == 0 ||
                (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH),
            "invalid batchAuctionLength"
        );
        emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength);
        batchAuctionLength = newAuctionLength;
    }
```

```solidity
function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) {
      
    ...
        require(batchAuctionLength != 0, "batch auctions not enabled");
    ...
    }
```

## Impact
In case of governance set `dutchAuctionLength` and `batchAuctionLength` are set to zero , Protocol is not able to run the two auction models so eventually prpotocol is going to insolvant state.

## Tools Used

Manual Review

## Recommended Mitigation Steps
Remove `newAuctionLength == 0` from [setDutchAuctionLength](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L221) function and
[setBatchAuctionLength](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/Broker.sol#L199) 



## As
Volatile exchange rates cause slippage loss to users during issuance and rebalancing the collateral
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L409-L413
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L483-L496
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L105


# Vulnerability details

## Impact
During rebalancing when trade settlement, the exchange rate between total supply of rToken and backing needed will be volatile and user can incur slippage loss and there's no validation to check the minimum `rToken` minted.

While issuance maybe paused during these times, but there will still be user's issue transaction losing to the slippage, because the first rebalancing can even happen before owner pausing issuance.
Rebalancing can happen if the rToken is not frozen during trade settlement or when `tradeEnd < block.timestamp`.

## Proof of Concept 

A user approves the token amounts and gets minted `x` rTokens. But the amount of rTokens minted depends on `basketsNeeded` and `totalSupply` an the input parameter `amount`.  
The `basketsNeeded` is volatile irrespective or independent of `totalSupply` in the following flow of rebalancing the backing manager. 
[BackingManagerP1::settleTrade](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L85) >> [BackingManagerP1::rebalance](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L107) >> [BackingManagerP1::compromiseBasketsNeeded](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L306) >> [rToken.setBasketsNeeded](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L409-L413)

So, there are flows where exchange rate between total supply of rToken and backing needed, it can be allowed to move between `low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE`, 1e9 < rate < 1e27

And if this sudden slippage change of over > 0.5% is a concern to the user who is trying to mint at the current exchange rate. And to protect his slippage, there should be a minimum rToken minted validation during these rebalancing times.

[rToken.setBasketsNeeded](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L409-L413)
```solidity
        uint256 low = (FIX_ONE_256 * basketsNeeded_) / supply; // D18{BU/rTok}
        uint256 high = (FIX_ONE_256 * basketsNeeded_ + (supply - 1)) / supply; // D18{BU/rTok}

        // here we take advantage of an implicit upcast from uint192 exchange rates
        require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range");
```

[rToken::issueTo](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e
No check for sequencer uptime will lead dutch auctions executing at worst prices
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Dutch auctions allow the protocol to sell assets at a time based decreasing price, which means means the longer the auction the lower the price. 
This kind of model presents some risk if the sequencer on L2 chains fails to work properly after the auction has been fired.

## Impact

The impact is financial, dutch auctions will be less profitable for the protocol.

## Proof of Concept
Given that the price during dutch auctions is based on how much time has passed since [the start.](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/trading/DutchTrade.sol#L204)

```solidity
   uint192 price = _price(uint48(block.timestamp)); 
```
A network outage can be quite damaging to any actors providing liquidity to the system.

Consider the following scenario.

*A 30 min long dutch auction start
The network experiences an outage, causing the sequencer to go offline.
45 min later the network is back
A fast actor bids on the dutch auction sitting now at the worst price
Auction settles.*

## Tools Used

Manuel reviews

## Recommended Mitigation Steps

With the use of [Chainlinks sequencer uptime feeds](https://docs.chain.link/data-feeds/l2-sequencer-feeds), negate auctions that have been taking place while an outage.


## Assessed type

Other
Rounding Errors in distribute Function Cause Undistributed Tokens and Potential Fund Loss
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability Details
The `distribute` function is responsible for distributing RSR or RToken to various destinations based on predefined shares. It's a critical function in the revenue distribution mechanism of the system.

The bug lies in the calculation and distribution of tokens. The function calculates `tokensPerShare` by dividing the total amount by the total shares, but it uses integer division, which can lead to rounding errors. These errors accumulate and result in undistributed tokens.

## Code Snippet
```solidity
uint256 tokensPerShare;
uint256 totalShares;
{
    RevenueTotals memory revTotals = totals();
    totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal;
    if (totalShares != 0) tokensPerShare = amount / totalShares;
    require(tokensPerShare != 0, "nothing to distribute");
}

// ... later in the function:
uint256 transferAmt = tokensPerShare * numberOfShares;
```

## Impact
1. Loss of Funds: Due to rounding errors, a portion of tokens may remain undistributed. These tokens effectively become locked in the contract.
2. Unfair Distribution: Destinations with smaller shares are more likely to be adversely affected by rounding errors, leading to an unfair distribution over time.
3. Inconsistency with Total Amount: The sum of distributed tokens may be less than the intended distribution amount.

## Scenario
Suppose there are 100 tokens to distribute among 3 destinations with shares of 50%, 30%, and 20%.
- `tokensPerShare` = 100 / 100 = 1
- Destination 1 receives: 1 * 50 = 50 tokens
- Destination 2 receives: 1 * 30 = 30 tokens
- Destination 3 receives: 1 * 20 = 20 tokens
Total distributed: 100 tokens

Now, if there are 101 tokens to distribute:
- `tokensPerShare` = 101 / 100 = 1 (due to integer division)
- Destination 1 receives: 1 * 50 = 50 tokens
- Destination 2 receives: 1 * 30 = 30 tokens
- Destination 3 receives: 1 * 20 = 20 tokens
Total distributed: 100 tokens, with 1 token remaining undistributed

## Fix
To address this issue, implement a two-step distribution process:

1. Distribute using integer division as currently implemented.
2. Calculate the remaining undistributed tokens and distribute them to destinations in order of their share size until all tokens are distributed.

Here's a code snippet illustrating the fix:

```solidity
uint256 distributedAmount = 0;
for (uint256 i = 0; i < numTransfers; ++i) {
    IERC20Upgradeable(address(erc20)).safeTransferFrom(
        caller,
        transfers[i].addrTo,
        transfers[i].amount
    );
    distributedAmount += transfers[i].amount;
}

// Distribute remaining tokens
uint256 remainingTokens = amount - distributedAmount;
for (uint256 i = 0; i < numTransfers && remainingTokens > 0; ++i) {
    uint256 additionalAmount = Math.min(1, remainingTokens);
    IERC20Upgradeable(address(erc20)).safeTr
`PermitLib#requireSignature()`would fail even for valid signatures from counterfactual wallets
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/libraries/Permit.sol#L5


# Vulnerability details


## Proof of Concept
Take a look at https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/libraries/Permit.sol#L5

```solidity
import "@openzeppelin/contracts-upgradeable/utils/cryptography/SignatureCheckerUpgradeable.sol";
```


See also https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/libraries/Permit.sol#L10-L23

```solidity
    function requireSignature(
        address owner,
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view {
        if (AddressUpgradeable.isContract(owner)) {
            require(
                IERC1271Upgradeable(owner).isValidSignature(hash, abi.encodePacked(r, s, v)) ==
                    0x1626ba7e,
                "ERC1271: Unauthorized"
            );
        } else {
        
        
        ..snip
        
        }
```


Evidently, [EIP 1271](https://eips.ethereum.org/EIPS/eip-1271) is being implemented in protocol, albeit via the `SignatureCheckerUpgradeable` inherited from openzeppelin which allows contracts to sign messages and works great in tandem with EIP 4337 (account abstraction).

For ERC-4337, the account is not deployed until the first UserOp is mined, previous to this, the account exists "counterfactually" — it has an address even though it's not really deployed. Usually, this is great since we can use the counterfactual address to receive assets without deploying the account first.

Now, not being able to sign messages from counterfactual contracts/accounts has always been a limitation of [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) since one can't call the `_isValidSignature()` function on them.

Here is the current method of validation: https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/libraries/Permit.sol#L18-L22

```solidity
        if (AddressUpgradeable.isContract(owner)) {
            require(
                IERC1271Upgradeable(owner).isValidSignature(hash, abi.encodePacked(r, s, v)) ==
                    0x1626ba7e,
                "ERC1271: Unauthorized"
            );
```

Which calls the below via `SignatureChecker`

```solidity

    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
        (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature);
        return
            (error == ECDSA.RecoverError.NoError && recovered == signer) ||
            isValidERC1271SignatureNow(signer, hash, signature);
    }

    function isValidERC1271SignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal view returns (bool) {
        (bool success, bytes memory result) = signer.staticcall(
Not enough gas might still pass on Arbitrum breaking the logic
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L15-L18
https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L259-L262


# Vulnerability details


## Proof of Concept

First see https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L15-L18

```solidity
    uint256 public constant GAS_FOR_BH_QTY = 100_000; // enough to call bh.quantity

    uint256 public constant GAS_FOR_DISABLE_BASKET = 900_000; // enough to disable basket on n=128


```

This constants have been set and are expected to be used in some checks to ensure enough gas still exists for some calls

That is the requirement here: https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L253-L265

```solidity
function _reserveGas() private view returns (uint256) {
  uint256 gas = gasleft();
  // Call to quantity() restricts gas that is passed along to 63 / 64 of gasleft().
  // Therefore gasleft() must be greater than 64 * GAS_FOR_BH_QTY / 63
  // GAS_FOR_DISABLE_BASKET is a buffer which can be considerably lower without
  // security implications.
  require(
    gas > (64 * GAS_FOR_BH_QTY) / 63 + GAS_FOR_DISABLE_BASKET,
    'not enough gas to unregister safely'
  );
  return GAS_FOR_BH_QTY;
}

```

Now keep in mind that the deployment of this protocol is to be done to multiple EVM chains, per the [readme](https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/README.md#L140-L141)

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

Issue now is that, this logic would not work as expected _Arbitrum_ , which is because _Arbitrum_ has a totally different gas model than Ethereum.

Differences are summarized in the following article: https://docs.arbitrum.io/devs-how-tos/how-to-estimate-gas.

As an example even a simple approve transaction requires more than 100_000 gas on Arbitrum: https://arbiscan.io/tx/0x04f97a39c6d03029518d1953226b343555ea585d2064a24a767155a548b0665e.

In our case here, this would then lead to scenarios where even if the [check](https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L259-L262) passes the transaction could still fail in any of the exerpts below:

- https://github.com/code-423n4/2024-07-reserve/blob/825e5a98a8c94a7b4458b3b359a507edb9e662ba/contracts/p1/AssetRegistry.sol#L90-L100

```solidity
function swapRegistered(IAsset asset) external governance returns (bool swapped) {
  require(_erc20s.contains(address(asset.erc20())), 'no ERC20 collision');
  //@audit
  try basketHandler.quantity{ gas: _reserveGas() }(asset.erc20()) returns (uint192 quantity) {
    if (quantity != 0) basketHandler.disableBasket(); // not an interaction
  } catch {