xanoutas/solidity-security-findings huggingface dataset:
Revert on type(uint256).max on some tokens
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Vulnerability details
Some tokens are reverted when approve type(uint256).max. Eg : UNI, COMP . When interacting with such tokens, its needed to ensure that the amounts use for approve, transfer not to exceed the uint96 limit. 

## Proof of Concept

```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);
    }
```

## Impact
If such tokens(UNI, COMP) registerd as basked collateral on reserve protocol , transaction is reverted when calling the [grantRTokenAllowance](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/BackingManager.sol#L69) function.  


## Tools Used
Manual Review


## Recommended Mitigation Steps
It can be used approval amount as a function argument . So Approval amount can be changed accordingly

```solidity
 function grantRTokenAllowance(IERC20 erc20, uint256 amount) external notFrozen {
        require(assetRegistry.isRegistered(erc20), "erc20 unregistered");
        // == Interaction ==
        IERC20(address(erc20)).safeApprove(address(rToken), 0);
        IERC20(address(erc20)).safeApprove(address(rToken), amount);
    }
```


## Assessed type

DoS
Lack of Slippage Protection in `issueTo()` functions of the `RToken` contract
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L105-L155


# Vulnerability details

## Impact
The `issueTo()` function lacks the slippage control that allows the users to revert if the amount of the underlying tokens they deposits is bigger than the amount they expected. Therefore, Assets for the affected users may be lose.

## Proof of Concept
During issuance, the user deposits the underlying tokens into the `RToken` contract, and the `RToken` contract uses the `issueTo()` function to issue RToken to the user.
```solidity
    function issueTo(address recipient, uint256 amount) public notIssuancePausedOrFrozen {
        require(amount != 0, "Cannot issue zero");

        // == Refresh ==

110:    assetRegistry.refresh();
        ...
        uint256 supply = totalSupply();

        SNIP...

133:    uint192 amtBaskets = supply != 0
            ? basketsNeeded.muluDivu(amount, supply, CEIL)
            : _safeWrap(amount);
        emit Issuance(issuer, recipient, amount, amtBaskets);

        // Get quote from BasketHandler including issuance premium
139:    (address[] memory erc20s, uint256[] memory deposits) = basketHandler.quote(
            amtBaskets,
            true,
            CEIL
        );

        // == Interactions: Create RToken + transfer tokens to BackingManager ==
        _scaleUp(recipient, amtBaskets, supply);

        for (uint256 i = 0; i < erc20s.length; ++i) {
            IERC20Upgradeable(erc20s[i]).safeTransferFrom(
                issuer,
                address(backingManager),
                deposits[i]
            );
        }
    }
```
As you can see, slippage control is not implemented in the `issueTo()` function.

However, slippage can occur in the `issueTo()` function due to various reasons.

- Calculation of the  BU change based on `totalSupply()` and `basketsNeeded`

    The BU change is calculated as follows:

    > amtBaskets = basketsNeeded * amount / totalSupply()

    However, `totalSupply()` and `basketsNeeded` can increase or decrease at any time depending on the current on-chain conditions when the transaction is executed.

    - _scaleUp
    ```solidity
    function _scaleUp(
        address recipient,
        uint192 amtBaskets,
        uint256 totalSupply
    ) private {
        // take advantage of 18 decimals during casting
        uint256 amtRToken = totalSupply != 0
            ? amtBaskets.muluDivu(totalSupply, basketsNeeded) // {rTok} = {BU} * {qRTok} * {qRTok}
            : amtBaskets; // {rTok}
        emit BasketsNeededChanged(basketsNeeded, basketsNeeded + amtBaskets);
        basketsNeeded += amtBaskets;

        // Mint RToken to recipient
        _mint(recipient, amtRToken);
    }
    ```
    - _scaleDown
    ```solidity
    function _scaleDown(address account, uint256 amtRToken) private returns (uint192 amtBaskets) {
        // D18{BU} = D18{BU} * {qRTok} / {qRTok}
        amtBaskets = basketsNeeded.muluDivu(amtRToken, totalSupply()); // F
Potential Bypass of Era Consistency Check in Proposal Execution 
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/governance/Governance.sol#L136-L145


# Vulnerability details

## Impact
The current implementation performs the `startedInSameEra()` check after the `_execute` function. This sequence creates a vulnerability where the era consistency check could be bypassed if the `_execute` function contains operations that can prematurely terminate execution.  

This vulnerability could lead to proposals being executed in a different era than they were proposed, potentially causing:  

1. Execution of proposals under unexpected protocol conditions  
2. Manipulation of governance decisions  
3. Inconsistent protocol state  
4. Possible exploitation of outdated proposals  

## Proof of Concept
1. A proposal is created and approved in Era A.  
2. Before execution, the protocol transitions to Era B.  
3. The proposal execution is initiated.  
4. The `_execute` function encounters one of the following scenarios:  
   a. Gas exhaustion: A complex operation consumes all available gas, causing the transaction to fail before reaching the `startedInSameEra()` check.  
   b. Non-reverting opcode error: An assembly-level operation results in an error that doesn't trigger a revert (e.g., an invalid jump destination or stack underflow).  
5. Due to these scenarios, the execution terminates prematurely, bypassing the `startedInSameEra()` check.  
6. In a subsequent transaction, the proposal execution is retried. This time, it succeeds, but executes in Era B instead of Era A, potentially leading to unintended consequences.  

## Tools Used
Manual Review

## Recommended Mitigation Steps
Move the `startedInSameEra()` check to the beginning of the proposal execution process, before any actions in `_execute` are performed. For example:  ```solidity  
function executeProposal(uint256 proposalId) public {
    require(startedInSameEra(proposalId), "new era");  
    super._execute(proposalId, targets, values, calldatas, descriptionHash);
}
```  


## Assessed type

Governance
The net issuance or net redemption may exceeds the per hour limit.
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/libraries/Throttle.sol#L69-L77


# Vulnerability details

## Impact
When calculating the current available amount of RToken in `ThrottleLib.currentlyAvailable`, the last available amount (i.e. `throttle.lastAvailable` in L75) is also included, which may exceed the hourly limit in some cases.
```solidity
69:    function currentlyAvailable(Throttle storage throttle, uint256 limit)
70:        internal
71:        view
72:        returns (uint256 available)
73:    {
74:        uint48 delta = uint48(block.timestamp) - throttle.lastTimestamp; // {seconds}
75:@>      available = throttle.lastAvailable + (limit * delta) / ONE_HOUR;
76:        if (available > limit) available = limit;
77:    }
```
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/libraries/Throttle.sol#L69-L77

## Proof of Concept
We assume that the max hourly limit of net issuance or net redemption for an RToken is `3600 units`. At any time, the value of `throttle.lastAvailable` may range from 0 to 3600 units.
1. At `T - 300 seconds`, some user issues some RTokens, and the resulted `throttle.lastAvailable` is `200 units`.
2. At `T + 3000 seconds`, some user issues `3500 units` of RToken. The elapsed time since the last issuance is `3300 seconds`. So currently available amount is `throttle.lastAvailable + (limit * delta) / ONE_HOUR = 200 + (3600 * 3300 / ONE_HOUR) = 3500 units`, and the new `throttle.lastAvailable` is `0` after the issuance.
3. At `T + 3600 seconds`, someone issues `600 units` of RToken. The elapsed time since the last issuance is `600 seconds`. So currently available amount is `throttle.lastAvailable + (limit * delta) / ONE_HOUR = 0 + (3600 * 600 / ONE_HOUR) = 600 units`, and the new `throttle.lastAvailable` is `0` after the issuance.

We can see that within the hour from `T` to `T + 3600 seconds`, the total number of units issued is `3500 + 600 = 4100 units`, which exceeds the maximum limit of `3600 units` per hour.

## Tools Used
VS Code

## Recommended Mitigation Steps
Record `lastUsed` in `struct Throttle`, and subtract the `lastUsed` when calculating the currently available amount.
```solidity
    struct Throttle {
        // === Gov params ===
        Params params;
        // === Cache ===
        uint48 lastTimestamp; // {seconds}
        uint256 lastAvailable; // {qRTok}
+       uint256 lastUsed;
    }

    function useAvailable(
        Throttle storage throttle,
        uint256 supply,
        int256 amount
    ) internal {
        ...
        throttle.lastAvailable = available;
+       throttle.lastUsed = amount;
    }

    function currentlyAvailable(Throttle storage throttle, uint256 limit)
        internal
        view
        returns (uint256 available)
    {
        uint48 delta = uint48(block.timestamp) - throttle.lastTimestamp; // {seconds}
        available = throttle.lastAvailable + (limit * delta) / ONE_HOUR;
        if (available > limit) available = l
The supply throttle checking in issuance should be performed after the `_scaleUp`.
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L105-L155


# Vulnerability details

## Impact
The supply throttle check in issuance is performed before the `_scaleUp`, where `_mint` is called within `_scaleUp`. This is problematic:
1. The supply throttle check will fail if current total supply is 0. This occurs when it is the first issuance of the RToken, or when all RTokens have been redeemed before.
2. The current available amount in throttle check is less than it should. Since the throttle checking is used to limit the issuance per hour, the current issuance should be taken into account when checking the limit for this hour. However, the check only considers the total supply before this issuance.

## Proof of Concept
The `supply` used for throttle checking is the current total supply of RToken (i.e. `totalSupply()`, L118). It does not contain the amount of current issuance, as the minting of RToken is performed in `_scaleUp` (L496), which is called after the throttle checking.
```solidity
// Function: RToken.sol#issueTo()
105:    function issueTo(address recipient, uint256 amount) public notIssuancePausedOrFrozen {
            ...
116:        // Ensure basket is ready, SOUND and not in warmup period
117:        require(basketHandler.isReady(), "basket not ready");
118:@>      uint256 supply = totalSupply();
119:
120:        // Revert if issuance exceeds either supply throttle
121:@>      issuanceThrottle.useAvailable(supply, int256(amount)); // reverts on over-issuance
122:@>      redemptionThrottle.useAvailable(supply, -int256(amount)); // shouldn't revert
            ...
145:        // == Interactions: Create RToken + transfer tokens to BackingManager ==
146:@>      _scaleUp(recipient, amtBaskets, supply);
            ...
155:    }

// Function: RToken.sol#_scalUp()
483:    function _scaleUp(
484:        address recipient,
485:        uint192 amtBaskets,
486:        uint256 totalSupply
487:    ) private {
            ...
495:        // Mint RToken to recipient
496:        _mint(recipient, amtRToken);
497:    }
```
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L105-L155

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L483-L497


Total supply is then used to calculate the hourly limit of the supply throttle (L46 => L88), and the limit is then used to calculate the available amount to use (L49 => L75). If current total supply is 0, the available amount will be 0, and the supply throttle checking will fail (L58).
```solidity
// Function: Throttle.sol#useAvailable()
37:    function useAvailable(
38:        Throttle storage throttle,
39:        uint256 supply,
40:        int256 amount
41:    ) internal {
42:        // untestable: amtRate will always be > 0 due to previous validations
43:        if (throttle.params.amtRate == 0 && throttle.params.pctRate == 0) return;
44:
45:        // Calculate hourly limit
46:@>      uint256 limit
If the payout criteria are not met, `payoutLastPaid` should not be updated.
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L594-L614


# Vulnerability details

## Impact
In function `_payoutRewards`, if the payout criteria are not met, `payoutLastPaid` should not be updated. However, the `payoutLastPaid` is actually updated even if the payout criteria are not met, which leads subsequent payouts to be calculated incorrectly.

## Proof of Concept
In function `_payoutRewards`, the payout is done only if enough RSR (i.e. >= FIX_ONE) is staked. If not, the payout will not happen. The `payoutLastPaid` records the last time when rewards are paid out. Thus if the payout does not happen, `payoutLastPaid` should not be updated. 

However, in the function `_payoutRewards`, the `payoutLastPaid` is always updated regardless of whether a payout happens or not. This is problematic if the `totalStakes` of the previous stakes are less than FIX_ONE. When the `totalStakes` after a new stake is greater than FIX_ONE, a payout will happen. At this time, the `payoutLastPaid` will be smaller than it should be. This leads to the `payoutRatio` and `payout` being smaller than they should be. 

The `payoutRatio` is calculated as `(1 - (1-rewardRatio)^numPeriods)`. As `payoutLastPaid` becomes larger, `numPeriods` becomes smaller, `(1-rewardRatio)^numPeriods` becomes larger, and therefore `(1 - (1-rewardRatio)^numPeriods)` becomes smaller.
```solidity
// Function: StRSR.sol#_payoutRewards()

594:        if (block.timestamp < payoutLastPaid + 1) return;
595:@>      uint48 numPeriods = uint48(block.timestamp) - payoutLastPaid;
596:
597:        uint192 initRate = exchangeRate();
598:        uint256 payout;
599:
600:        // Do an actual payout if and only if enough RSR is staked!
601:        if (totalStakes >= FIX_ONE) {
602:            // Paying out the ratio r, N times, equals paying out the ratio (1 - (1-r)^N) 1 time.
603:            // Apply payout to RSR backing
604:            // payoutRatio: D18 = FIX_ONE: D18 - FixLib.powu(): D18
605:            // Both uses of uint192(-) are fine, as it's equivalent to FixLib.sub().
606:@>          uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);
607:
608:            // payout: {qRSR} = D18{1} * {qRSR} / D18
609:            payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
610:            stakeRSR += payout;
611:        }
612:
613:@>      payoutLastPaid += numPeriods;
614:        rsrRewardsAtLastPayout = rsrRewards();
```
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L594-L614

## Tools Used
VS Code

## Recommended Mitigation Steps
Update `payoutLastPaid` only if `totalStakes >= FIX_ONE`.
```solidity
// Function: StRSR.sol#_payoutRewards()

        if (block.timestamp < payoutLastPaid + 1) return;
        uint48 numPeriods = uint48(block.timestamp) - payoutLastPaid;

        uint192 initRate = exchangeRate();
        uint256 payout;

        // Do an actual payout if and only if enough RSR
rsr can be unregistered from AssetRegistry to prevent seizure of stRSR holdings
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


# Vulnerability details

## Impact
rsr held by stRSR contract can no longer be seized to cover rToken backing shortfall

## Proof of Concept
**Although stRSR governance is outlined as trusted in the readme I intend to show why this situation invalidate the underlying assumptions for this trust**

First an explanation of the issue. A basic feature of the system is that when there is a shortfall in the backing for an rToken, rsr held by the rToken specific stRSR holders is seized to cover it. This process is carried out in RecollateralizationLibP1#prepareRecollateralizationTrade via BackingManager#rebalance.

[BackingManager.sol#L148-L171](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L148-L171)

        (TradingContext memory ctx, Registry memory reg) = tradingContext(basketsHeld);
        (
            bool doTrade,
            TradeRequest memory req,
            TradePrices memory prices
        ) = RecollateralizationLibP1.prepareRecollateralizationTrade(ctx, reg);

[BackingManager.sol#L273-L300](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BackingManager.sol#L273-L300)

    function tradingContext(BasketRange memory basketsHeld)
        public
        view
        returns (TradingContext memory ctx, Registry memory reg)
    {
        reg = assetRegistry.getRegistry();

        ...
    }

Above we see that reg (which contains the list of ERC20 tokens) is pulled directly from the assetRegistry and passed to the RecollateralizationLib.

[RecollateralizationLib.sol#L274-L376](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L274-L376)

    function nextTradePair(
        TradingContext memory ctx,
        Registry memory reg,
        BasketRange memory range
    ) private view returns (TradeInfo memory trade) {
        // assert(tradesOpen == 0); // guaranteed by BackingManager.rebalance()

        MaxSurplusDeficit memory maxes;
        maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status

        uint256 rsrIndex = reg.erc20s.length; // invalid index, to-start

        for (uint256 i = 0; i < reg.erc20s.length; ++i) {
            ...
        }

        if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) {
            (uint192 low, uint192 high) = reg.assets[rsrIndex].price(); <- Calls will revert if rsr isn't present in the ERC20 array from asset registry

            if (
                high != 0 &&
                TradeLib.isEnoughToSell(
                    reg.assets[rsrIndex],
                    ctx.bals[rsrIndex],
                    low,
                    ctx.minTradeVolume
                )
            ) {
Using endtime for order cancelation deadline of gnosis auction could lead to bait and switch bid tactics
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Allowing users to cancel orders last moment can lead to bait and switch bids designed to obtain assets below market value

## Proof of Concept

[GnosisTrade.sol#L84-L157](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/trading/GnosisTrade.sol#L84-L157)

    function init(
        IBroker broker_,
        address origin_,
        IGnosis gnosis_,
        uint48 batchAuctionLength,
        TradeRequest calldata req
    ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) {

        ...

        broker = broker_;
        origin = origin_;
        gnosis = gnosis_;
        endTime = uint48(block.timestamp) + batchAuctionLength;

        ...

        auctionId = gnosis.initiateAuction(
            sell,
            buy,
            endTime, <- @audit orderCancellationEndDate
            endTime, <- @audit auctionEndDate
            _sellAmount,
            minBuyAmount,
            minBuyAmtPerOrder,
            0,
            false,
            address(0),
            new bytes(0)
        );
    }

Above we see that when creating an auction, both the auction end and order cancellation end are set to the same value. 

[EasyAuction.sol#L152-L227](https://github.com/gnosis/ido-contracts/blob/e5ec2e696c2e1b68fe79a7ddc792b231d60fdf8c/contracts/EasyAuction.sol#L152-L227)

    function initiateAuction(
        IERC20 _auctioningToken,
        IERC20 _biddingToken,
        uint256 orderCancellationEndDate,
        uint256 auctionEndDate,
        uint96 _auctionedSellAmount,
        uint96 _minBuyAmount,
        uint256 minimumBiddingAmountPerOrder,
        uint256 minFundingThreshold,
        bool isAtomicClosureAllowed,
        address accessManagerContract,
        bytes memory accessManagerContractData
    ) public returns (uint256) {

        ...

        auctionData[auctionCounter] = AuctionData(
            _auctioningToken,
            _biddingToken,
            orderCancellationEndDate,
            auctionEndDate,
            IterableOrderedOrderSet.encodeOrder(
                userId,
                _minBuyAmount,
                _auctionedSellAmount
            ),
            minimumBiddingAmountPerOrder,
            0,
            IterableOrderedOrderSet.QUEUE_START,
            bytes32(0),
            0,
            false,
            isAtomicClosureAllowed,
            feeNumerator,
            minFundingThreshold
        );

        ...
    }

We see from the gnosis auction contract that these unmodified time values are used directly for the auction. The result is that orders can be canceled up the the block before it is finalized. This opens up the ability to carry out a bait and switch MEV tactic on the auction.

1. Bid entire auction amount with price 10% higher than m