xanoutas/solidity-security-findings huggingface dataset:
IDeployerRegistry Contract Lacks Authorization Anyone Can Make Changes
Audit: 2023-01-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/interfaces/IDeployerRegistry.sol#L15-L26


# Vulnerability details

## Impact
```
  function register(
        string calldata version,
        IDeployer deployer,
        bool makeLatest
    ) external;

    /// Unregister by version
    function unregister(string calldata version) external;

    /// @return The Deployer from the latest deployment
    function latestDeployment() external view returns (IDeployer);
}
```
This bug is a major security vulnerability because it allows anyone to call the `register`, `unregister`, and `latestDeployment` functions, which can potentially lead to unauthorized changes to the contract. Without proper access control or permission system in place, any malicious actor can call these functions and register, unregister, or access the latest deployment deployer contract, which can result in unexpected behavior, data loss, or even financial loss for the users of the contract.

A malicious actor could register a malicious version of the deployer contract, which could potentially be used to steal funds or perform other unauthorized actions. If a malicious actor unregisters a version that is currently in use, it could cause a malfunction in the contract, which could lead to financial loss. It could also allow malicious actors to access the latest deployment of the deployer contract, which could be used to steal funds or perform other unauthorized actions.

## Proof of Concept

This bug would involve creating a scenario where an unauthorized party can call the `register`, `unregister`, or `latestDeployment` functions of the contract. 
```
pragma solidity 0.8.9;

import "./IDeployer.sol";
import "truffle/Assert.sol";

interface IDeployerRegistry {
    event DeploymentUnregistered(string version, IDeployer deployer);
    event DeploymentRegistered(string version, IDeployer deployer);
    event LatestChanged(string version, IDeployer deployer);

    function register(
        string calldata version,
        IDeployer deployer,
        bool makeLatest
    ) external;

    function unregister(string calldata version) external;

    function latestDeployment() external view returns (IDeployer);
}

contract Attacker {
    IDeployerRegistry public registry;

    constructor(address _registry) public {
        registry = IDeployerRegistry(_registry);
    }

    function performAttack() public {
        // Create a malicious deployer contract
        address maliciousDeployer = new MaliciousDeployer();
        
        // Register the malicious deployer contract
        registry.register("1.0.0", maliciousDeployer, true);
        // Unregister a version that is currently in use
        registry.unregister("1.0.0");
        // Access the latest deployment
        address latestDeployment = registry.latestDeployment();

        // Assert that the malicious deployer was registered
        Assert.equal(maliciousDepl
delegateBySig() allows user to vote to address(0), which causes the user to permanently lose his vote and assets
Audit: 2023-01-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSRVotes.sol#L118-L135


# Vulnerability details

## Impact
In the StRSRVotes.sol contract, when the user votes with the delegate function, the delegatee will not be address(0).

## Proof of Concept
In the function ```delegateBySig()```, one can vote to address(0)
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSRVotes.sol#L118-L135
later whenever the hook ```_afterTokenTransfer()``` is used, the function ```_moveVotingPower()``` is called and it will revert and thus the person will lose
his votes.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider checking the delegatee address in the ```delegateBySig()``` function not to be address(0).
Integer overflows in RevenueTrader.sol
Audit: 2023-01-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RevenueTrader.sol#L37-#L65


# Vulnerability details

## Impact
Integer overflows exists in this project. This may lead to an integer overflow and allow an attacker to steal assets from the contract.

## Details
It seems that the `increaseAllowance` function is from the OpenZeppelin library, because of "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol". The contract contain SafeERC20Upgradeable, which in turn contains the increaseAllowance function.

This function does not check for the maximum allowed approve amount.  The risk of "not checking for the max allowed approve amount" is that may lead the integer overflow.

In the case, the contract does not check for the maximum allowed approve amount, an attacker could potentially increase the approve amount to a very large number, causing the value to overflow. Also, the attacker is able to cause an approve amount to overflow to a negative value, it may also allow the attacker to approve and transfer more tokens.

# Tools Used
VS code

## Recommended Mitigation Steps
Use safe math libraries or use the require statement.
Simple Example of require:
```
require(value <= 2**256 -1, "Too large value");
require(token.approve(spender, value), "failed");
```
abi.encodePacked Allows Hash Collision
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
From the solidity documentation: https://docs.soliditylang.org/en/v0.8.17/abi-spec.html?highlight=collisions#non-standard-packed-mode > If you use keccak256(abi.encodePacked(a, b)) and both a and b are dynamic types, it is easy to craft collisions in the hash value by moving parts of a into b and vice-versa. More specifically, abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c").
This issue exists in the StRSR.sol contract can results in hash collisions, make structHash can be bypassing

## Proof of Concept
As the solidity docs describe, two or more dynamic types are passed to abi.encodePacked. Moreover, these dynamic values are user-specified function arguments in external functions, meaning anyone can directly specify the value of these arguments when calling the function.  The structHash is supposed to protect functions to permit only function arguments that have been properly signed to be passed to the function logic, but because a collision can be created, the modifier can be bypassed for certain select inputs that result in the same encodePacked value.

## Tools Used
Manual review
## Recommended Mitigation Steps
Instead of writing functions to accept several arguments that are hashed inside the function, consider rewriting the function to take the hashed value as a function argument directly so that the hashing process happens off-chain. This approach would solve the issue and save gas.
Cross-chain replay attacks are possible with  `delegateBySig`
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSRVotes.sol#L118-L135


# Vulnerability details

If a user does a delegateBySig() using the wrong network, an attacker can replay the action on the correct chain, and steal the funds a-la the wintermute gnosis safe attack, where the attacker can create the same address that the user tried to, and steal the funds from there
https://mirror.xyz/0xbuidlerdao.eth/lOE5VN-BHI0olGOXe27F0auviIuoSlnou_9t3XRJseY


There is no chain.id in the data



```solidity
contracts/p1/StRSRVotes.sol:
  116  
  117:     function delegateBySig(
  118:         address delegatee,
  119:         uint256 nonce,
  120:         uint256 expiry,
  121:         uint8 v,
  122:         bytes32 r,
  123:         bytes32 s
  124:     ) public {
  125:         require(block.timestamp <= expiry, "ERC20Votes: signature expired");
  126:         address signer = ECDSAUpgradeable.recover(
  127:             _hashTypedDataV4(keccak256(abi.encode(_DELEGATE_TYPEHASH, delegatee, nonce, expiry))),
  128:             v,
  129:             r,
  130:             s
  131:         );
  132:         require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
  133:         _delegate(signer, delegatee);
  134:     }

```


### Recommended Mitigation Steps
Include the chain.id in keccak256
OracleLib: Price can be negative
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps
Inadequate Maximum Orders Value in Determining Minimum Buy Amount Per Order
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `MAX_ORDERS` constant is defined as a `uint96`, which has a maximum `value of 2^96-1`. This means that the maximum number of orders that the contract is able to handle is `2^96-1`. However, if the number of orders exceeds this maximum value, the calculation for the minimum buy amount per order will be incorrect and could lead to confusion and errors in the trading process.

## Proof of Concept

It could be exploited by a malicious actor submitting an order with a high number of orders, exceeding the maximum value of `2^96-1`. This could cause confusion in the trading process and potentially lead to errors such as the minimum buy amount per order being calculated incorrectly.

```
contract GnosisTrade {
    uint96 public constant MAX_ORDERS = 1e5;

    function submitOrder(uint96 numberOfOrders) public {
        require(numberOfOrders <= MAX_ORDERS, "Number of orders exceeds maximum value of MAX_ORDERS");
        // code to submit order
    }
}

// Malicious actor submits an order with a high number of orders
contract Attacker {
    GnosisTrade gnosisTrade;
    constructor(address _gnosisTrade) public {
        gnosisTrade = GnosisTrade(_gnosisTrade);
    }

    function exploit() public {
        gnosisTrade.submitOrder(2**96);
    }
}
```

The above exploit function would cause a `require` statement to fail in the `GnosisTrade` contract, as the number of orders exceeds the maximum value of `MAX_ORDERS`.

## Tools Used

Manual audit.

## Recommended Mitigation Steps

Update the `MAX_ORDERS` constant to a lower value that is more appropriate for the expected number of orders in the trading process. 
Additionally, it would be a good practice to add a comment explaining the reasoning behind the value chosen for MAX_ORDERS to make it clear for other developers.

Another solution would be to use `SafeMath` library to prevent integers overflow in calculations.

```
import "@openzeppelin/contracts/math/SafeMath.sol";

contract GnosisTrade {
    using SafeMath for uint96;

    uint96 public constant MAX_ORDERS = 1e5;
    ...
    function init(...) {
        ...
        require(req.sellAmount.mul(worstCasePrice).div(FEE_DENOMINATOR).div(MAX_ORDERS) <= sell.balanceOf(address(this)), "Insufficient balance of sell tokens");
        ...
    }
}
```
Battery discharge mechanism doesn't work correctly for first redemption
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/libraries/RedemptionBattery.sol#L59-L70


# Vulnerability details

## Impact
The `RTokenP1` contract implements a throttling mechanism using the `RedemptionBatteryLib` library. The library models a "battery" which "recharges" linearly block by block, over roughly 1 hour.

RToken.sol
```solidity
    function redeem(uint256 amount) external notFrozen {
        // ...

        uint256 supply = totalSupply();

        // ...
        battery.discharge(supply, amount); // reverts on over-redemption

        // ...
    }
```
RedemptionBatteryLib.sol
```solidity
    function discharge(
        Battery storage battery,
        uint256 supply,
        uint256 amount
    ) internal {
        if (battery.redemptionRateFloor == 0 && battery.scalingRedemptionRate == 0) return;

        // {qRTok}
        uint256 charge = currentCharge(battery, supply);

        // A nice error message so people aren't confused why redemption failed
        require(amount <= charge, "redemption battery insufficient");

        // Update battery
        battery.lastBlock = uint48(block.number);
        battery.lastCharge = charge - amount;
    }

    /// @param supply {qRTok} Total RToken supply before the burn step
    /// @return charge {qRTok} The current total charge as an amount of RToken
    function currentCharge(Battery storage battery, uint256 supply)
        internal
        view
        returns (uint256 charge)
    {
        // {qRTok/hour} = {qRTok} * D18{1/hour} / D18
        uint256 amtPerHour = (supply * battery.scalingRedemptionRate) / FIX_ONE_256;

        if (battery.redemptionRateFloor > amtPerHour) amtPerHour = battery.redemptionRateFloor;

        // {blocks}
        uint48 blocks = uint48(block.number) - battery.lastBlock; 

        // {qRTok} = {qRTok} + {qRTok/hour} * {blocks} / {blocks/hour}
        charge = battery.lastCharge + (amtPerHour * blocks) / BLOCKS_PER_HOUR;

        uint256 maxCharge = amtPerHour > supply ? supply : amtPerHour;
        if (charge > maxCharge) charge = maxCharge;
    }
```
The linear redemption limit is calculated in the `currentCharge` function. This function calculates the delta blocks by `uint48 blocks = uint48(block.number) - battery.lastBlock;`.

The bug here is that the `lastBlock` value is never initialized by the `RTokenP1` contract so its value defaults to `0`. This results in incorrect delta blocks value as the delta blocks comes out to be an incorrectly large value
```
        blocks = current block number - 0 = current block number
```

Due do this issue, the `currentCharge` value comes out to be way larger than the actual intended value for the first RToken redemption. The `maxCharge` cap at the end of `currentCharge` function caps the result to the current total supply of RToken. 

The issue results in an instant first RToken redemption for the full `totalSupply` of the RToken. The battery dischar
ISCOLLATERAL FUNCTION IS ALWAYS RETURNING FALSE
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/ae03d523ac980bc44d53e87400a64a6ff02f745f/contracts/plugins/assets/Asset.sol#L156-L158


# Vulnerability details

## Impact
`isCollateral()` in Asset.sol is a critical function updating the caller if the asset is an instance of ICollateral or not. However, it is noted that this function is always returning `false`, making all associated require statements to revert with this condition embedded.

## Proof of Concept
The function below returns `false` when externally called:

[Asset.sol#L155-L158](https://github.com/reserve-protocol/protocol/blob/ae03d523ac980bc44d53e87400a64a6ff02f745f/contracts/plugins/assets/Asset.sol#L155-L158)

```
    /// @return If the asset is an instance of ICollateral or not
    function isCollateral() external pure virtual returns (bool) {
        return false;
    }
```
Consequently, all functions depending on this check is going to be denied:

[BasketHandler.sol#L230](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BasketHandler.sol#L230)
[BasketHandler.sol#L265](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BasketHandler.sol#L265)

```
            require(assetRegistry.toAsset(erc20s[i]).isCollateral(), "token is not collateral");
```
[AssetRegistry.sol#L112](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/AssetRegistry.sol#L112)

```
        require(assets[erc20].isCollateral(), "erc20 is not collateral");
```
## Recommended Mitigation Steps
It is recommended soft coding the return bool of `isCollateral()` to appropriately reflect the actual status of each specific asset instance of ICollateral.
[NAZ-M4] Missing `isRegistered` Check in `sweepRewards()`
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In the `sweepRewardsSingle()` function there is a check to see if the ERC20 token to be swept is registered or not. While in the `sweepRewards()` function that sweeps multiple ERC20 tokens instead of just one, this check is missing. With that in mind, `sweepRewards()` can be used to bypass and sweep unregistered ERC20 tokens.

## Proof of Concept
If an ERC20 token were to be unregistered for whatever reason, `sweepRewards()` can be used to bypass this and break the logic of it not being allowed to be swept.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider adding the same check used in `sweepRewardsSingle()` to check if the ERC20 token is registered or not.
stRSR.seizeRSR() should check whether the basket of collateral is defaulted
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Backing Manager can seize RSR anytime and not within reason. RSR stakers will lose their funds.

## Proof of Concept

Protocol states that staked RSR can be seized in the case of a collateral default, in a process that is entirely mechanistic based on on-chain price-feeds, and does not depend on any governance votes or human choices. However, the current iteration of seize() in stRSR.sol does not check whether there is a collateral default or not. The backing manager can seize the tokens whenever.

```
    function seizeRSR(uint256 rsrAmount) external notPausedOrFrozen {
        require(_msgSender() == address(backingManager), "not backing manager");
        require(rsrAmount > 0, "Amount cannot be zero");
        uint192 initRate = exchangeRate();


        uint256 rsrBalance = rsr.balanceOf(address(this));
        require(rsrAmount <= rsrBalance, "Cannot seize more RSR than we hold");


        uint256 seizedRSR;
        uint256 rewards = rsrRewards();


        // Remove RSR from stakeRSR
        uint256 stakeRSRToTake = (stakeRSR * rsrAmount + (rsrBalance - 1)) / rsrBalance;
        stakeRSR -= stakeRSRToTake;
        seizedRSR = stakeRSRToTake;


        // update stakeRate, possibly beginning a new stake era
        if (stakeRSR > 0) {
            // Downcast is safe: totalStakes is 1e38 at most so expression maximum value is 1e56
            stakeRate = uint192((FIX_ONE_256 * totalStakes + (stakeRSR - 1)) / stakeRSR);
        }
        if (stakeRSR == 0 || stakeRate > MAX_STAKE_RATE) {
            seizedRSR += stakeRSR;
            beginEra();
        }


        // Remove RSR from draftRSR
        uint256 draftRSRToTake = (draftRSR * rsrAmount + (rsrBalance - 1)) / rsrBalance;
        draftRSR -= draftRSRToTake;
        seizedRSR += draftRSRToTake;


        // update draftRate, possibly beginning a new draft era
        if (draftRSR > 0) {
            // Downcast is safe: totalDrafts is 1e38 at most so expression maximum value is 1e56
            draftRate = uint192((FIX_ONE_256 * totalDrafts + (draftRSR - 1)) / draftRSR);
        }


        if (draftRSR == 0 || draftRate > MAX_DRAFT_RATE) {
            seizedRSR += draftRSR;
            beginDraftEra();
        }


        // Remove RSR from yet-unpaid rewards (implicitly)
        seizedRSR += (rewards * rsrAmount + (rsrBalance - 1)) / rsrBalance;


        // Transfer RSR to caller
        emit ExchangeRateSet(initRate, exchangeRate());
        IERC20Upgradeable(address(rsr)).safeTransfer(_msgSender(), seizedRSR);
    }
```

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

## Tools Used

Manual Review, VS Code.

## Recommended Mitigation Steps

Make sure the seize function checks whether the collateral is defaulted first. The withdraw
A staker might drain the stRST contract slowly
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
The ``stake()`` function relies on the ``_payoutRewards()`` function to calculate ``stakeRate``, which has a round-up error. Then, ``stateRate`` is also used to calculate ``stakeAmount``, which also has a round-up error. As a result, stakers might drain the contract  ``stRST`` slowly. 

## 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 ``stake()`` function relies on the ``_payoutRewards()`` function to calculate ``stakeRate`` as follows:
```
 stakeRate = (stakeRSR == 0 || totalStakes == 0)
            ? FIX_ONE
            : uint192((totalStakes * FIX_ONE_256 + (stakeRSR - 1)) / stakeRSR);
```
This results in a round-up error. Then it is used to calculate ``stakeAmount`` as follows:
```
        uint256 newStakeRSR = stakeRSR + rsrAmount;
        // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18
        uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE;
        uint256 stakeAmount = newTotalStakes - totalStakes;
```
As a result, ``stakeAmount`` has a round-up error also. Accumulatively, stakes might drain the ``StRSR`` contract in the long run. 

## Tools Used
Remix


## Recommended Mitigation Steps
When we can calculate ``stakeAmout`` we need to do a round-down as follows:
```
stakeRate = (stakeRSR == 0 || totalStakes == 0)
            ? FIX_ONE
            : uint192((totalStakes * FIX_ONE_256) / stakeRSR);
 uint256 stakeAmount = (stakeRate * rstAmount) / FIX_ONE;
uint256 newTotalStakes =  totalStakes + stakeAmount;

```