xanoutas/solidity-security-findings huggingface dataset:
Type Mismatch in initiateAuction function: auctionedSellAmount and minBuyAmount should be uint256
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

`initiateAuction` function of the contract. The function takes in two parameters, `auctionedSellAmount` and `minBuyAmount`, which are intended to specify the amount of the auctioned token being sold and the minimum amount of the bidding token that can be used to purchase it, respectively. However, these parameters are declared as uint96, which is not a valid type in Solidity.

## Proof of Concept

As the `auctionedSellAmount` and `minBuyAmount` parameters are declared as uint96 in the `initiateAuction` function, it will cause an error in the compilation of the code and the contract will not be able to deploy. As a result, no one will be able to interact with the contract, hence the bug will not be exploited.

However, for the sake of an example, if the contract was compiled and deployed with this error, the exploit would be as follows:

* An attacker creates a malicious contract that calls the `initiateAuction` function with the `auctionedSellAmount` and `minBuyAmount` parameters passed as uint96

* Since uint96 is not a valid type in Solidity, the function will not work as intended, and incorrect values will be stored in the contract

* As a result, the auction will not be conducted correctly, and the attacker can take advantage of this by bidding on the auction with an incorrect amount of bidding tokens.

Code for a malicious contract that could "exploit" this vulnerability.

```
contract Attack {
    function attack(address gnosis) public {
        uint96 fakeAuctionedSellAmount = 100;
        uint96 fakeMinBuyAmount = 200;
        IGnosis gnosisContract = IGnosis(gnosis);
        gnosisContract.initiateAuction(
            address(0x0), address(0x0), 0, 0, 
            fakeAuctionedSellAmount, fakeMinBuyAmount, 0, 0, false, address(0x0), new bytes(0));
    }
}
```

This exploit would allow the attacker to bid on the auction with an incorrect amount of bidding tokens, potentially allowing them to purchase the auctioned token at an incorrect price.

## Tools Used

Manual review, Vs Code.

## Recommended Mitigation Steps

* Change the type of the `auctionedSellAmount` and `minBuyAmount` parameters from `uint96 to uint256` in the `initiateAuction` function signature. This will ensure that the correct type of data is being passed to the function, preventing the contract from storing incorrect values, and ensuring the correct functioning of the auction.

```
function initiateAuction(
        IERC20 auctioningToken,
        IERC20 biddingToken,
        uint256 orderCancellationEndDate,
        uint256 auctionEndDate,
        uint256 auctionedSellAmount,
        uint256 minBuyAmount,
        uint256 minimumBiddingAmountPerOrder,
        uint256 minFundingThreshold,
        bool isAtomicClosureAllowed,
        address accessManagerContract,
        bytes memory access
DoS BrokerP1 with a Deflationary Sell Token
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If a bad actor do BrokerP1.openTrade() and GnosisTrade().settle() the trade immediately, all sell tokens would be returned to the GnosisTrade contract with `(sellBal == initBal)` at https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/trading/GnosisTrade.sol#L195

However, if the sell token happens to be a deflationary token, `sellBal < initBal` is true. Then, `broken.reportViolation()` would be called as `boughtAmt == 0`.

With that, the bad actor could easily launch DoS attacks on BrokerP1 though the governance can fix it later.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Whitelist the sell token or check/reject inflationary sell tokens
Front-run Initialize()
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Main.sol#L31


# Vulnerability details

## Impact
Due to not having access control in the initialize function. Anyone could front-run it getting the ownership of the contract.

## Proof of Concept

1 Reserve deploys contract (attacker can monitor that via bytecode matching or tracking the reserve address)
2 attacker calls initialize with a flashbot directly after deployment
3 done. Attack has ownership over the contract.


## Tools Used

manual

## Recommended Mitigation Steps

The steps should be to add an only Owner modifier to the initialize function:


     constructor(){
      require(address(rsr_) != address(0), "invalid RSR address");
        __Auth_init(shortFreeze_, longFreeze_);
        __ComponentRegistry_init(components);
        __UUPSUpgradeable_init();
        //here: owner = msg.sender;
        rsr = rsr_;
        emit MainInitialized();

       function init(
        Components memory components,
        IERC20 rsr_,
        uint48 shortFreeze_,
        uint48 longFreeze_
     ) public virtual initializer {
      //require(msg.sender == owner);
attacker can steal RToken holders funds by performing reentrancy attack during redeem() function token transfers
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L439-L514
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BackingManager.sol#L105-L150


# Vulnerability details

## Impact
Function `redeem()` redeems RToken for basket collateral and it updated `basketsNeeded` and transfers users basket ERC20 from BackingManager to user address. it loops through tokens and transfer them to caller and if one of tokens were ERC777 or any other 3rd party protocol token with hook, attacker can perform reentrancy attack during token transfers. Attacker can cause multiple impacts by choosing the reentrancy function:
1. attacker can call `redeem()` again and bypass "bounding each withdrawal by the prorata share when protocol is under-collateralized" because tokens balance of BackingManager is not updated yet.
2. attacker can call `BackingManager.manageTokens()` and because `basketsNeeded` gets decreased and basket tokens balances of BasketManager are not updated, code would detect those tokens as excess funds and would distribute them between RSR stakers and RToken holders and some of RToken deposits would get transferred to RSR holders as rewards.

## Proof of Concept
This is `redeem()` code:
```
    function redeem(uint256 amount) external notFrozen {
...............
...............
        (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote(baskets, FLOOR);

        uint256 erc20length = erc20s.length;

        uint192 prorate = uint192((FIX_ONE_256 * amount) / supply);

        // Bound each withdrawal by the prorata share, in case we're currently under-collateralized
        for (uint256 i = 0; i < erc20length; ++i) {
            uint256 bal = IERC20Upgradeable(erc20s[i]).balanceOf(address(backingManager));

            uint256 prorata = (prorate > 0)
                ? (prorate * bal) / FIX_ONE // {qTok} = D18{1} * {qTok} / D18
                : mulDiv256(bal, amount, supply); // {qTok} = {qTok} * {qRTok} / {qRTok}

            if (prorata < amounts[i]) amounts[i] = prorata;
        }

        basketsNeeded = basketsNeeded_ - baskets;
        emit BasketsNeededChanged(basketsNeeded_, basketsNeeded);

        // == Interactions ==
        _burn(redeemer, amount);

        bool allZero = true;
        for (uint256 i = 0; i < erc20length; ++i) {
            if (amounts[i] == 0) continue;
            if (allZero) allZero = false;

            IERC20Upgradeable(erc20s[i]).safeTransferFrom(
                address(backingManager),
                redeemer,
                amounts[i]
            );
        }

        if (allZero) revert("Empty redemption");
    }
```
As you can see code calculates withdrawal amount of each basket erc20 tokens by calling `basketHandler.quote()` and then bounds each withdrawal by the prorata share of token balance, in case protocol is under-collateralized. and then code updates `bas
Potential DoS due to external token transfer failure
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BackingManager.sol#L238-L242


# Vulnerability details

## Impact

When `handoutExcessAssets()`, if one of the token transfer fails, the whole excess assets handout would revert, causing DoS. `_manageTokens()` -> `handoutExcessAssets()` function will be inoperable until the token transfer resumes. 

However, some external factors could lead to transfer failure:
- some token has pause mode, such as USDC, USDT, WBTC, etc. When this mode is turned on, all transfer will revert.
- some token has blacklist, such as USDC, if the `rsrTrader` is added to the blacklist, the transfer will fail.

## Proof of Concept

The entire list of `erc20s` is iterated to transfer excess token. 
```solidity
File: contracts/p1/BackingManager.sol
154:     function handoutExcessAssets(IERC20[] calldata erc20s) private {

238:         for (uint256 i = 0; i < length; ++i) {
239:             IERC20Upgradeable erc20 = IERC20Upgradeable(address(erc20s[i]));
240:             if (toRToken[i] > 0) erc20.safeTransfer(address(rTokenTrader), toRToken[i]);
241:             if (toRSR[i] > 0) erc20.safeTransfer(address(rsrTrader), toRSR[i]);
242:         }
```

But the external dependency on token transfer success could DoS and making the `_manageTokens()` -> `handoutExcessAssets()` functionality fail to operate.


## Tools Used
Manual analysis.

## Recommended Mitigation Steps

Use `try/catch` to skip the erc20 with problem, then the `manageToken()` function will be more robust to external token special conditions.
Anybody can cancel a proposal
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/fa81006e97c84f41b43b0eefa395cc253b6e731b/contracts/plugins/governance/Governance.sol#L105


# Vulnerability details

## Impact
There is no authentication check regarding cancelling a proposal.

## Proof of Concept

The proposal can be made the number of votes (voting power) is larger than threshold.
```
function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public override(Governor, IGovernor) returns (uint256 proposalId) {
        // The super call checks that getVotes() >= proposalThreshold()
        return super.propose(targets, values, calldatas, description);
    }
```
https://github.com/reserve-protocol/protocol/blob/fa81006e97c84f41b43b0eefa395cc253b6e731b/contracts/plugins/governance/Governance.sol#L85
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/18691d1a6f56dd906c175072a97dc5ed93ab968c/contracts/governance/Governor.sol#L252

But, regarding the cancelling a proposal, there is no authentication check. So, in case an attacker notices that a proposal (which is not in favor of the attacker) is going to be executed soon, he can cancel it.
```
function cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) external {
        uint256 proposalId = _cancel(targets, values, calldatas, descriptionHash);
        require(!startedInSameEra(proposalId), "same era");
    }
```
https://github.com/reserve-protocol/protocol/blob/fa81006e97c84f41b43b0eefa395cc253b6e731b/contracts/plugins/governance/Governance.sol#L105
```
function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal virtual returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
        ProposalState status = state(proposalId);

        require(
            status != ProposalState.Canceled && status != ProposalState.Expired && status != ProposalState.Executed,
            "Governor: proposal not active"
        );
        _proposals[proposalId].canceled = true;

        emit ProposalCanceled(proposalId);

        return proposalId;
    }
```
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/18691d1a6f56dd906c175072a97dc5ed93ab968c/contracts/governance/Governor.sol#L372


## Tools Used

## Recommended Mitigation Steps
There should be a voting power threshold or required proposal canceler role on the function `cancel`.
`unregistered` asset reward could still be swept
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `sweepRewardsSingle()` function does not allow unregistered asset to be swept. But the other sweep function `sweepRewards()` does not perform this check. Unregistered can still be swept. The registration status check can not work as expected.



## Proof of Concept

The `sweepRewardsSingle()` function checks for the registration status of the erc20 asset, unregistered asset will revert.
```solidity
File: contracts/p1/mixins/RewardableLib.sol
90:     function sweepRewardsSingle(
91:         mapping(IERC20 => uint256) storage liabilities,
92:         IERC20 erc20,
93:         IAssetRegistry reg,
94:         IBackingManager bm
95:     ) external {
96:         require(reg.isRegistered(erc20), "erc20 unregistered");
```

But `sweepRewards()` does not check for registration status, unregistered asset can still be swept.
```solidity
57:     function sweepRewards(
58:         mapping(IERC20 => uint256) storage liabilities,
59:         IAssetRegistry reg,
60:         IBackingManager bm
61:     ) external {
62:         IERC20[] memory erc20s = reg.erc20s();
63:         uint256 erc20sLen = erc20s.length;
64:         uint256[] memory deltas = new uint256[](erc20sLen); // {qTok}
65: 
66:         // Calculate deltas
67:         for (uint256 i = 0; i < erc20sLen; ++i) {
68:             deltas[i] = erc20s[i].balanceOf(address(this)) - liabilities[erc20s[i]]; // {qTok}
69:         }
70: 
71:         // == Interactions ==
72:         // Sweep deltas
73:         for (uint256 i = 0; i < erc20sLen; ++i) {
74:             if (deltas[i] > 0) {
75:                 erc20s[i].safeTransfer(address(bm), deltas[i]);
76: 
77:                 // Verify nothing has gone wrong -- we should keep this assert, even in P1
78:                 assert(erc20s[i].balanceOf(address(this)) >= liabilities[erc20s[i]]);
79:             }
80:         }
81:     }
```



## Tools Used
Manual analysis.

## Recommended Mitigation Steps

Add the same check in `sweepRewards()`.

```diff
57:     function sweepRewards() external {

71:         // == Interactions ==
72:         // Sweep deltas
73:         for (uint256 i = 0; i < erc20sLen; ++i) {
-74:             if (deltas[i] > 0) {
+74:             if (deltas[i] > 0 && reg.isRegistered(erc20s[i])) {
75:                 erc20s[i].safeTransfer(address(bm), deltas[i]);
76: 
77:                 // Verify nothing has gone wrong -- we should keep this assert, even in P1
78:                 assert(erc20s[i].balanceOf(address(this)) >= liabilities[erc20s[i]]);
79:             }
80:         }
```
Centralization Risk
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/mixins/Auth.sol#L148


# Vulnerability details

## Impact
Owner can call `freezeForever` function causing a permanent freeze. This will prohibit any unstake and only operation allowed would be owner operations and staking. If owner does not unfreeze then User funds will get stuck

## Proof of Concept
1. Owner calls `freezeForever` function

```
function freezeForever() external onlyRole(OWNER) {
        freezeUntil(MAX_UNFREEZE_AT);
    }
```

2. This causes a permanent freeze

3. This prohibits all important user function like [unstake](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L257) (Observe the notPausedOrFrozen  check)

```
function unstake(uint256 stakeAmount) external notPausedOrFrozen {
...
}
```

4. Thus user cannot unstake till owner decides to unfreeze

## Recommended Mitigation Steps
If such centralization is required then atleast unstaking should not be part of freezing which will allow user to atleast claim back their fund when required
strsr-payout-formula is not consistent with different `_payoutRewards()` call sequences
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L87-L137
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L496-L530


# Vulnerability details

## Impact

Currently the payout formula assumes lump sum reward at the beginning, and derive the formula by repeating the payout N times. But in practice, the rewards do not arrive in lump sum, but comes as multiple individual amounts. As a result this formula will give different results if the `_payoutRewards()` function is called in different ways. Hence, the reward amount calculation is not accurate and inconsistent. 


## Proof of Concept

According to the payout formula comments:

>    // [strsr-payout-formula]:
>    //   The process we're modelling is:
>    //     N = number of whole rewardPeriods since last >_payoutRewards() call
>    //     rewards_0 = rsrRewards()
>    //     payout_{i+1} = rewards_i * payoutRatio
>    //     rewards_{i+1} = rewards_i - payout_{i+1}
>    //     payout = sum{payout_i for i in [1...N]}
>    //   thus:
>    //     rewards_N = rewards_0 - payout
>    //     rewards_{i+1} = rewards_i - rewards_i * payoutRatio = rewards_i * (1-payoutRatio)
>    //     rewards_N = rewards_0 * (1-payoutRatio) ^ N
>    //     payout = rewards_N - rewards_0 = rewards_0 * (1 - (1-payoutRatio)^N)

Here `rewardRatio` is `payoutRatio` in the formula. Effectively is paying out the `rewardRatio` for N times. 

The assumption is, the reward comes at period 0 in a lump sum. For each period, the payout is the `rewardRatio` of the reward amount available for this period. And the reward amount available need to subtract the payout for current period. Whatever left in the reward amount will be used for the next period. And then the $1 - (1-payoutRatio)^N$ is derived.

The implementation is:
```solidity
File: contracts/p1/StRSR.sol
509:             uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);

512:             payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
```

For example, assuming the `numPeriods` is 2, rewards of 1,000 will arrive for each period, `rewardRatio` being 0.2. So `rsrRewards()` is 1,000 at the beginning.
If call function `_payoutRewards()` at the end of these 2 periods, the final payout ratio will be $1 - (1 - 0.2)^2 = 0.36$, total payout would be 2,000 * 0.36 = 720.

However, if `_payoutRewards()` is called each time the reward arrives, the payout_1 would be 1,000 * 0.2 = 200, and 1,000 - 200 = 800 will remain in the reward available. Next time `rsrRewards()` will be 1,800, and payout_2 would be 1,800 * 0.2 = 360. Total payout is 200 + 360 = 560.

The difference of 720 and 560 come from the assumption that all rewards arrive at the beginning of all the periods. So the late arrived rewards will be paid out more, in the example above, the 2nd 1,000 reward only went through 1 reward period (20
`setRewardRatio()` breaks `payoutRatio` formula assumption
Audit: 2023-01-reserve-findings
Severity: MEDIUM

# Lines of code

https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L828-L832
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L496-L530


# Vulnerability details

## Impact

`payoutRatio` assume constant `rewardRatio` across all the reward periods, and derives the formula of $1 - (1-payoutRatio)^N$. However, if the setter function `setRewardRatio()` is called to change the `rewardRatio`, the constant `rewardRatio` assumption does not hold anymore, the modelling process of constant ratio need adjustment. As a result, the reward amount calculation is not accurate comparing with the expected amount. In some cases the users could receive more rewards than deserved, and sometimes the users could get less rewards.

The issue with the current formula is, when the `rewardRatio` changed, it is immediately applied to all the reward periods, including the periods in the past. Effectively applying future parameter value to past reward periods. So the reward calculation logic needs refactor.


## Proof of Concept

According to the payout formula comments:

>    // [strsr-payout-formula]:
>    //   The process we're modelling is:
>    //     N = number of whole rewardPeriods since last >_payoutRewards() call
>    //     rewards_0 = rsrRewards()
>    //     payout_{i+1} = rewards_i * payoutRatio
>    //     rewards_{i+1} = rewards_i - payout_{i+1}
>    //     payout = sum{payout_i for i in [1...N]}
>    //   thus:
>    //     rewards_N = rewards_0 - payout
>    //     rewards_{i+1} = rewards_i - rewards_i * payoutRatio = rewards_i * (1-payoutRatio)
>    //     rewards_N = rewards_0 * (1-payoutRatio) ^ N
>    //     payout = rewards_N - rewards_0 = rewards_0 * (1 - (1-payoutRatio)^N)

Here `rewardRatio` is `payoutRatio` in the formula. Effectively is paying out the `rewardRatio` for N times. 

The assumption is, the reward comes at period 0 in a lump sum. For each period, the payout is the `rewardRatio` of the reward amount available for this period. And the reward amount available need to subtract the payout for current period. Whatever left in the reward amount will be used for the next period. And then the $1 - (1-payoutRatio)^N$ is derived.

The implementation is:
```solidity
File: contracts/p1/StRSR.sol
509:             uint192 payoutRatio = FIX_ONE - FixLib.powu(FIX_ONE - rewardRatio, numPeriods);

512:             payout = (payoutRatio * rsrRewardsAtLastPayout) / FIX_ONE;
```

According to the formula, the `rewardRatio` needs to stay constant for this formula to hold. 

For example, assuming the `numPeriods` is 2, `rsrRewards()` is 10,000. And let the original `rewardRatio` being 0.2, and new `rewardRatio` being 0.3. If the `setRewardRatio()` call happens at the 2nd period. 
According to the payout formula, with the updated `rewardRatio`, the final payout ratio is $1 - (1 - 0.3)^2 = 0.51$, payout amount is 5,100.

But if looking