xanoutas/solidity-security-findings huggingface dataset:
Critical Bug in prepareTradeToCoverDeficit Function Causing Zero Sell Amount in TradeLib Contract
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/reserve-protocol/protocol/blob/4.0.0/contracts/p1/mixins/TradeLib.sol#L148


# Vulnerability details

## Impact
Based on the provided echidna logs there is an issue within the TradeLib contract the prepareTradeToCoverDeficit function. Specifically, the Exact Sell Amount and Request Sell Amount are coming out as 0, which implies that the calculations for determining the sell amount aren't producing valid results.

Potential Causes:
1. Trade Amount Calculations:
The exactSellAmount and slippedSellAmount calculations are producing non-zero values (2000288176520567114 and 2020493107596532439 respectively), but these aren't being reflected in the final sellAmount in the trade request, which is suspiciously 0.
2. Trade Size Constraints:
There might be an issue where the calculated slippedSellAmount is being incorrectly limited or discarded due to internal checks, such as maximum trade size limits or dust trade conditions.

## Proof of Concept
echidna test:
testPrepareTradeToCoverDeficit(uint192): failed!💥  
  Call sequence:
    TradeLibEchidnaTest3.testPrepareTradeToCoverDeficit(1000101419437632800)

Traces:
emit LogUint(name=«Exact Sell Amount Calculation», value=2000202838875265600) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:78)
emit LogUint(name=«Slipped Sell Amount Calculation», value=2020406907954813738) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:79)
call MockAsset2::maxTradeVolume()() (/share/p1/mixins/TradeLib.sol:185)
 └╴← (1000000000000000000000000)
call MockAsset2::maxTradeVolume()() (/share/p1/mixins/TradeLib.sol:185)
 └╴← (1000000000000000000000000)
call MockAsset2::erc20Decimals()() (/share/p1/mixins/TradeLib.sol:80)
 └╴← (18)
call MockAsset2::erc20Decimals()() (/share/p1/mixins/TradeLib.sol:81)
 └╴← (18)
emit LogUint(name=«Buy Amount», value=1000101419437632800) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:87)
emit LogUint(name=«Exact Sell Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:88)
emit LogUint(name=«Request Sell Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:89)
emit LogUint(name=«Request Min Buy Amount», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:90)
emit LogUint(name=«Sell Low Price», value=1000000000000000000) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:91)
emit LogUint(name=«Max Trade Slippage», value=10000000000000000) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:93)
emit LogUint(name=«Is Not Dust Trade», value=0) (/share/echidna/mixins/TradeLibEchidnaTest3.sol:94)

this is the echidna test that was used, 
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "./MockAsset2.sol";
import "../../libraries/Fixed.sol";
import "../../interfaces/IAsset.sol";
import "../../echidna/IERC20.sol";
import "../../p1/mixins/TradeLib.sol";
import "../../echidna/open/IERC20Metadata.sol";

contract TradeLibEchidnaTest3 {
    using FixLib for uint192;

    // Mock Assets
    MockAsset2 public sellAsset;
    MockAsset2
Reentrancy in RToken may lead to complete loss of funds
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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#L397-L414


# Vulnerability details

## Impact

There’s a reentrancy vulnerability inside the `RToken` contract. Currently, deployed USD and ETH pools seem safe. Any RTokens having a collateral allowing external calls (e.g. on-transfer hooks) inside the collateral's `transferFrom` are vulnerable. It is possible to re-enter an `RToken` during `issue` before collaterals are transferred and tamper the state in an attacker's favor. The attacker can leverage that vulnerability to drain all funds as soon as such a collateral is added to a basket, in any quantity.

The key prerequisite of the attack is to have in a basket a collateral allowing external calls (e.g. on-transfer hooks) inside the collateral's `transferFrom`. It may seem a bit of a stretch, however, consider the following:

* There are a lot of ERC20-compatible behaviours that allow some form of control leak from `transfer`s as competently described [here](https://medium.com/immunefi/the-ultimate-guide-to-reentrancy-19526f105ac) in the "What can trigger reentrancy?" section.
* Such a risk is [well known and require mitigation](https://blog.openzeppelin.com/exploiting-uniswap-from-reentrancy-to-actual-profit) (whatever your solution on this report, please consider adding guards!). 
* There were several significant hacks caused exactly by such collaterals, e.g.: https://www.coindesk.com/tech/2022/04/01/ola-finance-says-attackers-stole-47m-in-re-entrancy-exploit/
* Some **existing collaterals in existing pools** may inadvertently and without any prior notice exhibit such a behavior, mainly due to upgradeability. Compound, Lido, Aave tokens as examples.

As soon as such a collateral is added to a basket, all the protocol funds may be drained. At the time of writing this bug report no funds are at risk to the knowledge of the author. However, as discussed above the funds may fall under the risk of complete draining any minute now even without any actions on the Protocol/Governance side.


## Proof of Concept
The key step of the exploitation involves the `issue` and `setBasketsNeeded` functions. The functions are not protected by the `nonReentrant` modifier, and despite the fact that `issueTo` adheres to the RCEI pattern, there is still a way to re-enter the protocol in an incosystent state.
Have a look at this snippet of `issueTo`:

```solidity
        // == 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]
            );
        }
```

Just after all the protocol math is done and the `basketsNeeded` adjustment inside the `_scaleUp` is done and
Delegation to the `zero address` can lead to permanent loss of the user's voting power
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L166-L183
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L230-L254
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L211-L218


# Vulnerability details

## Summary

Do Note that this vulnerability was highly inspired by this recent finding -
- https://mirror.xyz/verbsteam.eth/TP917T6vm6gXuVAxbQ34ZCn7dNiHabu3UW-ninwalVc.

The `StRSRVotes` contract contains a similar vulnerability to the one described in the Nouns NFT scenario. The issue lies in the `_moveVotingPower` function, which is called during token transfers and delegations.

We have identified a critical vulnerability in the `StRSRP1Votes` contract. When `delegateBySig` is called, potential delegation to the zero address can lead to permanent loss of the user's voting power.

Users are no longer able to participate in the voting decisions When this function is used to delegate to address zero, the delegator wallet loses all its voting power and can no longer transfer their votes out of that wallet.


## Vulnerability Details

The` _moveVotingPower` function allows delegation to address(0), which can lead to permanent loss of voting power. 

The issue occurs when an `StRSR` owner delegates their voting power to an EOA. If the delegate, whether intentionally or unintentionally, uses the `delegateBySig` function to delegate to the zero address, one unit of voting power is permanently burned. This results in the associated losing its voting power and becoming non-transferrable. The transfer attempt will revert due to an underflow error when the contract attempts to reduce the sender's already zero voting power. When delegating to `address(0)`, voting power is subtracted from the source but not added to any destination, effectively burning it.

The `_afterTokenTransfer` function calls `_moveVotingPower`, potentially causing issues during transfers if a user's voting power has been compromised. The `stakeAndDelegate` function allows users to stake and delegate in one transaction, potentially exacerbating the issue if misused.

- Who's at risk? `StRSR` token holders delegating to EOA accounts.
- What's the damage? `StRSR` tokens can become non-transferrable with lost voting power.
- How likely is it to happen? Low probability if delegates are trusted not to transfer to a zero address, but mistakes do happen. And it can also be perfomed by a malicious actor.


## Impact:

Voting Power Loss: The affected user loses its voting power permanently, reducing its utility in governance processes.



## Proof of Concept:

1.  Setup:
        Alice owns `StRSR` 1.
        Bob owns `StRSR` 2.

2.  Delegation:
        Alice delegates her voting power to Bob (Alice votes: 0; Bob votes: 2).

3.  Faulty Delegation:
        Bob uses `dele
delegateBySig() does not validate the delegatee address, malicious user can lock other user's NFT(funds)
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L162
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L166


# Vulnerability details

## Impact
function delegateBySig doesn't cover case when delegatee = address(0), malicious users can lock other user's NFT(funds).

The delegateBySig() function does not validate the delegatee address.

when call delegateBySig(delegatee=address(0)), the delegatee is still self, but user checkpoints.votes will be reduced.
it will cause the NFT(funds) of the user being delegate to be locked, can no set other delegatee or NFT transfer

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L166C1-L183C6
```
    function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public {
        require(block.timestamp <= expiry, "signature expired");
        address signer = ECDSAUpgradeable.recover(
            _hashTypedDataV4(keccak256(abi.encode(_DELEGATE_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        require(nonce == _useDelegationNonce(signer), "invalid nonce");
        _delegate(signer, delegatee);
    }
```


### POC:

step1 - Alice has 1 NFT, Bob has 1 NFT, and Bob's delegatee is Alice, then Alice's Checkpoints.votes=2

step2 - Alice transfers its own NFT to Carol, then Alice's Checkpoints.votes=1

step3 - Alice call delegateBySig(address(0)), after Alice's Checkpoints.votes=0

step4 - Now Bob cannot modify the delegatee, nor can he transfer NFTs(funds). In fact, they are locked


What will happen will be as follows:

If Bob wants to transfer NFT or set a new delegatee, it will call _moveVotingPower()

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L230C1-L243C14
```
    function _moveVotingPower(
        address src,
        address dst,
        uint256 amount
    ) private {
        if (src != dst && amount != 0) {
            if (src != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
                    _checkpoints[era][src],
                    _subtract,
                    amount
                );
                emit DelegateVotesChanged(src, oldWeight, newWeight);
            }
```

Here also because src = Alice, and the Alice's Checkpoints.votes=0, _moveVotingPower() reverts with an underflow error due to subtracting amount from 0.


The same thing, i.e. locking of NFTs(funds), can happen to the user himself as well:

If user A votes to address 0 in the delegateBySig function, _delegates[A] will be address 0.

https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSRVotes.sol#L220C1-L228C6
```
    function _delegate(address delegator, address delegatee) internal {
        address currentDelegate = delegates(delegator);
        uint256 delegatorBala
Attacker can manipulate auction outcome by exploiting rounding in clearingPrice calculation (`GnosisTrade::settle`)
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

The `GnosisTrade` contract facilitates trades using the Gnosis EasyAuction mechanism. The [`settle()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/trading/GnosisTrade.sol#L175-L221) is responsible for finalizing the auction, transferring tokens, and verifying if the auction's clearing price meets the expected worst-case price. However, there is a vulnerability in the way the clearing price is calculated and compared to the worst-case price, potentially allowing an attacker to manipulate the auction outcome by exploiting rounding errors.

The [`settle()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/trading/GnosisTrade.sol#L175-L221) includes the following logic to calculate the clearing price:

```solidity
File: GnosisTrade.sol
212:             // Gnosis rounds defensively in the buy token; we should not consider it a violation
213:             uint256 adjustedSoldAmt = Math.max(soldAmt, 1);
214:             uint256 adjustedBuyAmt = boughtAmt + 1;
215: 
216:             // D27{buyTok/sellTok}
217:             uint192 clearingPrice = shiftl_toFix(adjustedBuyAmt, 9).divu(adjustedSoldAmt, FLOOR);
218:             if (clearingPrice.lt(worstCasePrice)) broker.reportViolation();

```

The issue arises from two main factors:
1. The use of `Math.max(soldAmt, 1)` to ensure a non-zero denominator.
2. The addition of 1 to `boughtAmt` to account for Gnosis' defensive rounding.

These adjustments, combined with the `FLOOR` rounding in the `divu()` function, can lead to scenarios where an attacker can manipulate the auction to barely avoid triggering a violation report, even when the actual clearing price is below the worst-case price.

For example, if the `worstCasePrice` is set to 100 and an attacker manages to settle the auction with `soldAmt = 100` and `boughtAmt = 9900`, the actual price would be 99. However, due to the adjustments and rounding, the calculated `clearingPrice` would be 100, equal to the `worstCasePrice`, thus avoiding a violation report.

## Impact

Attackers can exploit the rounding mechanism to settle auctions at prices slightly below the worst-case price without triggering violation reports. 

Legitimate participants may receive fewer tokens than expected based on the intended worst-case price protection, resulting in direct financial losses. 


## Proof of Concept

1. Alice initiates an auction with a `worstCasePrice` of 100 (assuming appropriate scaling).
2. The auction proceeds, and Bob (the attacker) manipulates the final bid to result in:
   - `soldAmt = 100`
   - `boughtAmt = 9900` (which would normally result in a price of 99, below the worst-case price)
3. In the `settle()` function
BackingManager can seize more RSR than intended due to calculation error (`StRSRP1::seizeRSR()`)
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

The `StRSRP1` contract contains a critical vulnerability in its [`seizeRSR()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L424-L479) that allows the `BackingManager` to seize more RSR than intended. This function is designed to allow the `BackingManager` to seize a specified amount of RSR from the contract, adjusting the `stakeRSR` and `draftRSR` balances accordingly. However, the current implementation uses the total RSR balance of the contract (`rsrBalance`) in its calculations, which can be manipulated to skew the results.

The core issue lies in the calculation of `stakeRSRToTake` and `draftRSRToTake`. These values are computed using the following formulas:

```solidity
File: StRSR.sol
441:         uint256 stakeRSRToTake = (stakeRSR * rsrAmount + (rsrBalance - 1)) / rsrBalance;
456:         uint256 draftRSRToTake = (draftRSR * rsrAmount + (rsrBalance - 1)) / rsrBalance;
```

The problem arises because `rsrBalance` includes the amount of RSR that is being seized (`rsrAmount`). This means that if an attacker were to transfer additional RSR to the contract immediately before calling `seizeRSR()`, they could artificially inflate `rsrBalance`, leading to an incorrect calculation of the amounts to be seized.

For example, if the contract initially has 1000 RSR, and the `BackingManager` attempts to seize 100 RSR, the calculation would be based on a total balance of 1000 RSR. However, if an attacker transfers an additional 9000 RSR to the contract just before the seizure, the calculation would now be based on a total balance of 10,000 RSR, significantly altering the proportion of RSR seized from each pool.

## Impact

It allows the `BackingManager` to seize more RSR than intended, which can lead to significant financial loss and instability in the system. An attacker could manipulate the `rsrBalance` by transferring RSR to the contract just before calling `seizeRSR()`, leading to incorrect calculations and potentially allowing the `BackingManager` to seize more RSR than intended.

## Proof of Concept

1. Initially, the contract has 1000 RSR total, with 500 in `stakeRSR` and 500 in `draftRSR`.
2. The `BackingManager` prepares to seize 100 RSR by calling `seizeRSR(100)`.
3. Just before the transaction is processed, an attacker transfers 9000 RSR to the contract.
4. The `seizeRSR()` function calculates `stakeRSRToTake` as:
   `(500 * 100 + 9999) / 10000 = 55` (rounded up)
5. Similarly, `draftRSRToTake` is calculated as 55.
6. The total seized amount is 110 RSR, which is more than the intended 100 RSR.
7. The remaining balances are skewed: `stakeRSR` = 445, `draftRSR` = 445, instead of the expected 450 each.

## Tools Used

Manual review

## Recommended Mitigation Steps

To mitigate this vulne
Malicious users can extract excess RSR due to incorrect rate calculations (`StRSRP1::unstake` and `StRSRP1::withdraw`)
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L259-L281
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L640-L664
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L304-L342


# Vulnerability details

## Description
 
The `StRSRP1` contract implements a staking mechanism for RSR tokens, allowing users to stake, unstake, and withdraw their tokens. The contract uses two key rates, [`stakeRate`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L66) and [`draftRate`](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L88), to manage the relationship between staked RSR, StRSR tokens, and drafts (unstaking requests). However, these rates are not properly updated during the unstaking process, leading to a critical vulnerability.

The [`unstake()` function ](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L259-L281)is responsible for initiating the unstaking process:

```solidity
File: StRSR.sol
259:     function unstake(uint256 stakeAmount) external {

    // ... (checks and effects)
274:         uint256 newStakeRSR = (FIX_ONE_256 * totalStakes + (stakeRate - 1)) / stakeRate;
275:         uint256 rsrAmount = stakeRSR - newStakeRSR;
276:         stakeRSR = newStakeRSR;

279:         (uint256 index, uint64 availableAt) = pushDraft(account, rsrAmount);
    // ...
}
```

The [`pushDraft()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L640-L664) then creates a draft for the unstaked amount:

```solidity
function pushDraft(address account, uint256 rsrAmount) internal returns (uint256 index, uint64 availableAt) {
    draftRSR += rsrAmount;
    uint256 newTotalDrafts = (draftRate * draftRSR) / FIX_ONE;
    uint256 draftAmount = newTotalDrafts - totalDrafts;
    totalDrafts = newTotalDrafts;
    // ... (create draft entry)
}
```

Finally, the [`withdraw()` function](https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L304-L342) allows users to claim their unstaked RSR:

```solidity
function withdraw(address account, uint256 endId) external {
    // ... (checks and effects)
    uint256 newTotalDrafts = totalDrafts - draftAmount;
    uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate;
    uint256 rsrAmount = draftRSR - newDraftRSR;
    // ... (transfer RSR)
}
```

It issue is that neither `stakeRate` nor `draftRate` is updated during the unstaking process. This leads to a mismatch between the amount of RSR moved from stakes to drafts and the amount that can be withdrawn, potentially al