xanoutas/solidity-security-findings huggingface dataset:
Malicious users can DoS honest user redemptions utilizing throttles and flashloans
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
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L183
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L121-L122
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L199-L200


# Vulnerability details

## Description

RTokens have supply throttles, which should restrict the system to organic behavior; one is used for net issuance, and the other for net redemption. The throttles recharge over one hour with the parameters set by governance. This influences the number of RTokens that can be issued and redeemed per hour and can give the RTokens some protection. However, this protection also allows malicious users to prevent honest users from redeeming their tokens by leaving the throttles empty.

## Proof of concept

When a user wants to [issue](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L105) new RTokens, they can only issue as many RTokens as the `issuanceThrottle` allows. Same when they want to [redeem](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L183), they can only redeem as much as the `redemptionThrottle` allows. On [issuance](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L121-L122), the `issuanceThrottle` is decreased by the amount of issued token and the `redemptionThrottle` is increased by the amount of issued token, while on the [redeem](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/RToken.sol#L199-L200) it is the other way around. Since there is no restriction on how often users can issue and redeem RTokens, a malicious user can DoS honest users by emptying the throttles with flashloans.

Consider the following scenario:
1. Alice wants to redeem her tokens after some time, so she submits a redeem tx.
2. Bob, a malicious user, sees the tx and quickly sends a transaction, which
    1. Loans the necessary amount of tokens for an issuance via flashloan
    2. Issues the whole available amount of RToken
    3. Redeems all the RTokens (this will increase the issuance throttle and decrease the redemption throttle)
    4. Return the flashloan

Now, the throttle is at zero, and Alice is unable to redeem her RTokens. Note that the griefer can front-run all redeem transactions this way.

## Impact and likelihood

The attacker gains nothing from this attack, lowering the likelihood. The cost of such an attack would only be gas, meaning it is less likely on Ethereum mainnet but very likely on L2s. Considering this, the likelihood should be set at **MEDIUM**. The attack would prevent honest users from redeeming their tokens. Since this is not a permanent DoS, the funds would not be lost, the attack would need to be maintained, and if something like that were to happen, the governance could act and change the parameters of throttle, the
The default Governor Anastasius is unable to call `resetStakes`
Audit: 2024-07-reserve-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description

The `StRSR` contract contains a function [`resetStakes`](https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/p1/StRSR.sol#L490), which is used to reset the staking of a specific RToken system, when the stake rate becomes unsafe. When this happens, the `era` of the staking contract is incremented, which practically resets the `StRSR` token. Since this is a sensitive action, only governance can call this function.

The Reserve team provides a default and recommended `Governance` contract with `TimelockController`, which handles the proposal creation and execution. The contract also ensures that proposals created in a past era can not be queued or executed in the current era since the voting conditions can differ between eras. However due to this check, it is impossible for the `Governance` contract to call `resetStakes`, since the function would increment the era, and the following check whether the proposal was proposed in the same era would not hold and revert the transaction.

## Proof of concept

The following test added to [ZZStRSR.tests.ts](https://github.com/code-423n4/2024-07-reserve/blob/main/test/ZZStRSR.test.ts) supports the issue:

```typescript
it.only("Impossible to reset stakes with Governor Anastasius", async () => {
  // Setup governance
  const ONE_DAY = 86400;
  const VOTING_DELAY = ONE_DAY;
  const VOTING_PERIOD = ONE_DAY * 3;
  const MIN_DELAY = ONE_DAY * 7;

  let GovernorFactory = await ethers.getContractFactory("Governance");
  let stRSRVotes = await ethers.getContractAt("StRSRP1Votes", stRSR.address);
  let TimelockFactory = await ethers.getContractFactory("TimelockController");
  let timelock = <TimelockController>(
    await TimelockFactory.deploy(MIN_DELAY, [], [], owner.address)
  );

  let governor = await GovernorFactory.deploy(
    stRSRVotes.address,
    timelock.address,
    VOTING_DELAY,
    VOTING_PERIOD, // voting period
    0, // threshold set to 0 just to showcase the issue
    0 // quorum percentage set to 0 just to showcase the issue
  );

  /////////////////////////////////////////
  ///                                   ///
  /// First step: update timelock roles ///
  ///                                   ///
  /////////////////////////////////////////

  const proposerRole = await timelock.PROPOSER_ROLE();
  const executorRole = await timelock.EXECUTOR_ROLE();
  const cancellerRole = await timelock.CANCELLER_ROLE();
  const adminRole = await timelock.TIMELOCK_ADMIN_ROLE();

  await timelock.grantRole(proposerRole, governor.address);
  await timelock.grantRole(executorRole, governor.address);
  await timelock.grantRole(cancellerRole, governor.address);
  await timelock.revokeRole(adminRole, owner.address);

  // Then we will update the owner to a new decentralized Governor Anastasius
  await main.connect(owner).grantRole(OWNER, governor.address);
  a
Reentrancy attack on fee transferring
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L168
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L257
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L578
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L581
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L600
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L631
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L168


# Vulnerability details

# Vulnerability details

## Description

There is `_execute` function in the `Exchange` smart contract. The function matches two orders, ensuring the validity of the match, **transfers the order fees**, etc.

When transferring fees, the contract just makes a call to the recipient's address, creating a possibility for a reentrancy attack.

On the other hand, there is a `bulkExecute` function. It makes a bunch of delegate calls to the `_execute` function. At the start of a function, the `_execute` is delegate called in the loop, and at the end, all remaining ether is returned to the caller.

Now let's put these together. Bob invites Alice to sign a couple of very profitable for her orders. The receipient of the fees of all orders is just an EOA account according to the on-chain data at the moment of signing the order. Alice sends a transaction by calling `bulkExecute` with ether which is needed to execute a list of signed orders to the mempool. Bob deploys the smart contract to the address of the fee recipient, frontrunning the Alices transaction. The Alices transaction starts execution. The first order executes correctly, and when the ethers should be transferred the fee recipient contract is called, it reenters the `Exchange` in a `bulkExecute` function and withdraws the whole ether balance to its address. Now the execution of Alices `bulkExecute` flow continues and all other orders fails due to zero contract balance. Bob steals Alices' funds.

### Attack scenario

1. Honest user sends a `bulkExecute` transaction with 10 buy orders (all orders use ETH as a token, for example, 1 ETH for each of the orders)
2. When executing **the first order from the list** the following bunch of calls happen:
- _execute
- _executeFundsTransfer
- _transferFees
- _transferTo
- payable(feeRecipient).call{value: ...}("")
3. In the last call of this sequence fee recipient receives access control and makes a reentrancy call to the `Exchange` contract. The attacker calls the `bulkExecute` function with empty input array `executions` and nonzero value (for example just 1 Wei), so the only `_returnDust` function will be processed -- actually it will send all the funds from the contract to the attacker.
4. Control flow is returned to the `Exchange` contract and execution of **the first order from the list** successfull
`Exchange.sol` has payable functions but no way of withdrawing
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L154-L162
https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L168-L173
https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L234-L239


# Vulnerability details

# `Exchange.sol` has payable functions but no way of withdrawing
## Impact
Functions are payable:
- `IExchange.execute`
- `Exchange.execute`
- `Exchange.bulkExecute`
- `Exchange._execute`
- `UUPSUpgradeable.upgradeToAndCall`

But there is no way of withdrawing, so funds can be lost
## PoC
https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L154-L162
https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L168-L173
https://github.com/code-423n4/2022-11-non-fungible/blob/8eaeb007b81d3c89ecf6a5dc2a67eecbe94cefaf/contracts/Exchange.sol#L234-L239

But there is no way of withdraw the ether
## Mitigation
Remove payable or add a withdraw method
`_returnDust` may sweep more eth than it is entitled to
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L219


# Vulnerability details

## Impact
`_returnDust` instead of returning the rightful remainingETH amount, it returns selfbalance(). If eth gets into the contract somehow, it can be swept by anyone, who passes in some eth through bulkExecute, even with empty `executions` parameter.

## Proof of Concept
```
                let callStatus := call(
                    gas(),
                    caller(),
                    selfbalance(),
                    0,
                    0,
                    0,
                    0
                )
```

## Tools Used

## Recommended Mitigation Steps
Change it to
```
                let callStatus := call(
                    gas(),
                    caller(),
                    _remainingETH,
                    0,
                    0,
                    0,
                    0
                )
```
Also if leftover ETH is a concert, add an onwerOnly function for the owner to sweep remaining ETH.
The setupExecution is reentrancy attack vulnerable
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L40-L46
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L168-L210


# Vulnerability details

## Impact
The setupExecution can be re-entered by calling bulkExecute inside an `_execution`.

Because the global state remainingETH and isInternal are modified (reset to 0 and flase) after the latter reentrant call, the previous call is affected:
1. all subsequent executions will fail, because isInternal is reset to false
2. the origin tx sender will lose ETH, because remainingETH is reset to 0

At the same time, the ETH lost is taken away by [_returnDust()](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L209) in the re-entered calling if it carries msg.value greater than 0.

## Proof of Concept

Use a custom contract which will re-enter Exchange by calling `bulkExecute`.

The logic of the custom contract can be invoked by the execution of a malicious constructed order in
[Exchange.sol#L631](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L631)
or [Exchange.sol#L663](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L663)
or [Exchange.sol#L665](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L665).

## Tools Used
n/a

## Recommended Mitigation Steps

Adding `require(!isInternal)` to setupExecution will avoid this problem:

```
modifier setupExecution() {
    require(!isInternal);    // added
    remainingETH = msg.value;
    isInternal = true;
    _;
    remainingETH = 0;
    isInternal = false;
}
```
Attacker can spoof remainingETH and double-spend their input ETH to Exchange
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212-L227


# Vulnerability details

## Description

remainingETH is an important state variable in Exchange.sol, which keeps track of how many ETH have yet to be used as payment from the current msg.value.
The setupExecution modifier sets the value before and after execution:
```
modifier setupExecution() {
    remainingETH = msg.value;
    isInternal = true;
    _;
    remainingETH = 0;
    isInternal = false;
}
```

Payments are deducted from remainingETH:
```
function _executeFundsTransfer(
    address seller,
    address buyer,
    address paymentToken,
    Fee[] calldata fees,
    uint256 price
) internal {
    if (msg.sender == buyer && paymentToken == address(0)) {
        require(remainingETH >= price);
        remainingETH -= price;
    }
    /* Take fee. */
    uint256 receiveAmount = _transferFees(fees, paymentToken, buyer, price);
    /* Transfer remainder to seller. */
    _transferTo(paymentToken, buyer, seller, receiveAmount);
}
```

Another important note is that \_returnDust() is executed in the end of execute() / bulkExecute() to send back to msg.sender the remaining ETH:
```
function _returnDust() private {
    uint256 _remainingETH = remainingETH;
    assembly {
        if gt(_remainingETH, 0) {
            let callStatus := call(
                gas(),
                caller(),
                selfbalance(),
                0,
                0,
                0,
                0
            )
        }
    }
}
```

The issue created here is that \_returnDust passes execution to caller *without* updating remainingETH. Caller may then immediately enter \_execute() and use the remainingETH sum as if it is still remaining. 
Interestly this is possible despite \_execute being guarded by reentrancyGuard and internalCall. reentrancyGuard is bypassed because in the outer call we are in bulkExecute(), not \_execute. internalCall is bypassed because while we are in the outer bulkExecute(), the setupExecution() modifier guarantees that isInternal is set to true.
Therefore, in the latter \_executeFundsTransfer remainingETH will be old instead of zero, and the require will succeed.

## Impact

Attacker can spoof remainingETH and double-spend their input ETH to Exchange

## Proof of Concept

The call chain would look like:

- bulkExecute()
	- \_execute()
		- \_executeFundsTransfer()
	- \_returnDust()
		- caller.call()
			- \_execute()
				- \_executeFundsTransfer()


## Tools Used

Manual audit

## Recommended Mitigation Steps

Use the Checks / Effects / Interactions pattern:
Before calling user in \_returnDust(), update remainingETH to 0.
A malicious user can steal all the excess balance on the pool by calling the execute function with higher amount than the selling amount
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L219
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212


# Vulnerability details

## Impact
When an order is matched, the Buyer has the option to pay in either ETH, WETH or via the Pool contract. The Exchange smart contract implements a function [`_returnDust`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212) which returns the extra ETH to the user, if (s)he overpays.
The function is implemented in inline assembly(YUL) and takes advantage of the `call OPCODE`.
The result of the `call` function call isn't checked whether it's true or not.
So, if a smart contracts calls the [`execute`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L154) function, pays in ETH and forgets to implement a fallback function, the execute function succeedsand the remaining ETH are left in the contract and not returned to the user.

The `_returnDust` function assumes that not ETH are stuck in the contract. As this is not the case, the whole `selfbalance`(`address(this).balance)`) is transferred to the malicious user, instead of the `remainingETH`.

## Proof of Concept
The  [`_returnDust`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212) function:
```
function _returnDust() private {
        uint256 _remainingETH = remainingETH;
        assembly {
            if gt(_remainingETH, 0) {
                let callStatus := call(
                    gas(),
                    caller(),
                    selfbalance(),
                    0,
                    0,
                    0,
                    0
                )
            }
        }
    }
```

- We assume that some ETH are stuck in the contract, most possible due to a smart contract that's interacting with `Exchange.sol`, but fails to implement a fallback function
- Let's say the smart contract has 2 ETHstuck, the selling price of the order is 1 ETH and the user calls the `execute` function with 1,5 ETH
- The malicious user calls the `execute` function, with more ETH as the selling price in order to trigger the call OPCODE in the `_returnDust` function
- As `_remainingETH=0.5ETH > 0` the smart contract transfers to the user address(this).balance, which is 1.5 instead of the `remainingETH` which is 0.5
- So, the malicious can steal the excess ETH that are left in the contract

## Tools Used
Manual Code Review

## Recommended Mitigation Steps
It is recommended to check the returned value of the call opcode, `callStatus` whether its true or not and to replace the `selfbalance()` with `_remainingETH ` in order to transfer to the user the remaining balance instead of the whole ETH bala
Reentrancy attack can be used to externally call the `_execute` function in the Exchange contract
Audit: 2022-11-non-fungible-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L216
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L238


# Vulnerability details

# Reentrancy attack can be used to externally call the `_execute` function in the Exchange contract

The `_execute` function present in the Exchange contract is intended to be called only internally using `delegatecall` by the `execute` and `bulkExecute` functions. This is achieved by keeping track of the `isInternal` variable and using the `internalCall` modifier. However, it is possible to execute a reentrancy attack and call the `_execute` function directly from the outside.

## Impact

The `_returnDust()` function returns control to the caller by sending ETH and can be used to reenter the contract and call the `_execute` function. 

Note that, when `_returnDust()` is called, we are in the middle of the `execute` or `bulkExecute` call. This means that the value for the `isInternal` variable is still true. This allows us to bypass the `internalCall` modifier and access the `_execute` function externally, as shown in the following PoC.

Although such an attack is possible, I couldn't find any other side effect that could lead to stolen funds or NFTs. I'm assigning this issue a medium severity, feel free to discuss this, but at least I wanted to report the issue, that may eventually lead to a higher impact.

## PoC

Here Alice wants to sell an NFT for 1 ETH. Using the ReentrancyBuyer contract, we first send a modified payload that we know will fail on purpose to the `bulkExecute` function, which will return control to the calling contract when the `_returnDust()` function is executed. While this happens, we get the callback in the `receive` function and execute a **direct** call to the `_execute` function.

```
it('can bypass internal check and call _execute directly', async function() {
  const reentrancyContract = (await simpleDeploy('ReentrancyBuyer', [exchange.address])) as any;
  const price = ethers.utils.parseEther("1");
  
  sell = generateOrder(alice, {
    side: Side.Sell,
    tokenId,
    paymentToken: ethers.constants.AddressZero,
    price,
    fees: [],
  });
  
  buy = generateOrder(reentrancyContract, {
    side: Side.Buy,
    tokenId,
    price,
    paymentToken: ethers.constants.AddressZero,
    fees: [],
  });
  
  sellInput = await sell.pack();
  buyInput = await buy.packNoSigs();
  const executions = [{ sell: sellInput, buy: buyInput }];

  await waitForTx(
    reentrancyContract.startReentracy(executions, { value: price })
  );
  
  // The reentrancy call to _execute worked since we bought the NFT
  expect(await mockERC721.balanceOf(reentrancyContract.address)).to.eq(1);
});
```

The ReentrancyBuyer contract:

```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "../Exchange.sol";
import "../lib/OrderStructs.sol";

contract Re