xanoutas/solidity-security-findings huggingface dataset:
Order listing time can be arbitrarily set by an attacker to modify matching role
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

# Order listing time can be arbitrarily set by an attacker to modify matching role

## Impact

The condition `sell.listingTime <= buy.listingTime` present in the function `_canMatchOrders` can be easily forged by an attacker after seeing an existing order to determine which branch of the if condition to take. 

This isn't particularly relevant to the available matching strategies, since both (721 and 1155) are commutative in the implementations of `canMatchMakerAsk` and `canMatchMakerBid` (and also their arguments can be switched without affecting the result). But this may become relevant in the future when more strategies are added, which is to be expected. The design of the `IMatchingPolicy` interface also indicates the possibility of non symmetric implementations of `canMatchMakerAsk` and `canMatchMakerBid`.

For example, a seller can create an order with listing time `t_sell` and later an attacker can create an order with listing `t_buy` so that `t_buy < t_sell`, switching his role from taker to maker.

The effect here is that the attacker can benefit from deciding which policy to use (since the policy is instantiated from the maker side) or deciding between the role maker/taker by simply setting the listing time.

## Recommendation

Unsure what could be used to determine who is the maker, in particular in this scenario where orders are not registered on-chain. I guess the options are 1) take this into account while designing matching policies acknowledging this may happen or 2) change the design of matching policies so that it doesn't matter which side is the maker/taker.
Pool contract uses proxies but doesn't implement the initializer function
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Pool.sol#L13
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Pool.sol#L15


# Vulnerability details

# Pool contract uses proxies but doesn't implement the initializer function

The Pool contract uses proxies (OpenZeppelin UUPSUpgradeable) but doesn't include an initializer function to properly initialize the contract.

## Impact

The Pool contract inherits from `OwnableUpgradeable` to handle access protection for the contract but fails to initialize it, because the contract doesn't even have an initializer.

This will cause the internal `_owner` storage variable to remain uninitialized, its value will be empty/zero, and will leave the contract without owner. 

This is concerning in particular because the function that authorizes an upgrade `_authorizeUpgrade` is owner protected using the `onlyOwner` modifier, which will always be false since the owner is not initialized (owner is `address(0)`) and will prevent the contract from being upgraded.

## PoC

The following test uses the hardhat plugin `@openzeppelin/hardhat-upgrades` to simplify the code. 

Note: `PoolV2` here is just the same code as the `Pool` contract with an added function, its contents aren't relevant to the test.

```
const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");

describe.only("Pool", function () {
  it("deploys proxy", async function() {
    const Pool = await ethers.getContractFactory("Pool");
    const PoolV2 = await ethers.getContractFactory("PoolV2");

    const pool = await upgrades.deployProxy(Pool, []);
    await pool.deployed();

    // pool has no owner
    expect(await pool.owner()).to.eq(ethers.constants.AddressZero);

    // pool can't be upgraded
    await expect(
      upgrades.upgradeProxy(pool.address, PoolV2)
    ).to.be.revertedWith("Ownable: caller is not the owner");
  });
});
```

## Recommendation

Add a proper initializer to the contract that calls the `OwnableUpgradeable` initializer:

```
function initialize() external initializer {
    __Ownable_init();
}
```

Also, don't forget to lock initializers in the implementation contract:

```
constructor() {
    _disableInitializers();
}
```
ETH payments may fail to be refunded and will be lost in the Exchange contract
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

# ETH payments may fail to be refunded and will be lost in the Exchange contract

The function present in the Exchange contract that is in charge of refunding any remaining ETH after a single/bulk exchange doesn't check the return value of the ETH transfer operation and may fail while the overall process succeeds.

## 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. The issue is that this function doesn't check if the low level call succeeded, the result of the call is stored in the variable `callStatus` but no further is done using this value.

If the caller is a contract, there are various reasons why this call may fail. The calling contract may fail to implement the receive/fallback callbacks, have an error during execution of the callback, or simply run out of gas. Any of these scenarios may cause the low level call to fail, the ETH won't be refunded and will be lost in the Exchange contract.

## PoC

This test reproduces the issue using a simple contract that initiates the purchase for demonstration purposes.

```
it('fails to refund eth', async function() {
  const buyerContract = (await simpleDeploy('NftBuyer', [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(buyerContract, {
    side: Side.Buy,
    tokenId,
    price,
    paymentToken: ethers.constants.AddressZero,
    fees: [],
  });

  sellInput = await sell.pack();
  buyInput = await buy.packNoSigs();

  // buyer sends more eth than needed (double)
  await waitForTx(
    buyerContract.buyNft(sellInput, buyInput, { value: price.mul(2) })
  );
  
  // we bought the nft
  expect(await mockERC721.balanceOf(buyerContract.address)).to.eq(1);
  
  // ETH not refunded to buyer contract
  expect(await hre.ethers.provider.getBalance(buyerContract.address)).to.eq(0);
  
  // ETH is in exchange
  expect(await hre.ethers.provider.getBalance(exchange.address)).to.eq(price);
});
```

NftBuyer 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 NftBuyer is IERC721Receiver {
    Exchange private immutable exchange;

    constructor(Exchange _exchange) {
        exchange = _exchange;
    }

    function buyNft(Input calldata sell, Input calldata buy) external payable {
        exchange.execute{value: msg.
Extra ETH can be stuck in the contract
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# 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.

## Proof of Concept
- UserA creates a smart contract and transfers funds to it, but forgets to create a `fallback` function
- UserA matches an order and calls the `execute` function of the `Exchange.sol` with an `amountA` higher than the sell amount and with `address(0)` as the [`paymentToken`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/lib/OrderStructs.sol#L20) meaning that (s)he wants to pay in ETH.
- After the checks the [`remainingETH`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L574) are `remainingETH=startingETH-sellAmount` and need to  be transferred to the userA via the [`_returnDust`](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Exchange.sol#L212) function 
- The low level call fails, as no fallback function is implemented but the execute function succeeds and the remaining ETH are stuck 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.
A simple `_returnDust` function is given bellow:
```
function _returnDust() private {
    (bool success,) = payable(msg.sender).call{value: remainingETH}("");
    require(success, "ETH transfer failed");
}
```
The pool contract is unable to upgrade
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The Pool (proxy) contract will never be able to upgrade successfully

## Proof of Concept

The Pool contract is defined as `OwnableUpgradeable` and `UUPSUpgradeable`, and it should be upgradeable just like the Exchange contract.

`UUPSUpgradeable` will invoke `_authorizeUpgrade()` in `upgradeTo()` and `upgradeToAndCall()`, and the Pool contract overrides [_authorizeUpgrade](https://github.com/code-423n4/2022-11-non-fungible/blob/323b7cbf607425dd81da96c0777c8b12e800305d/contracts/Pool.sol#L15):

```
function _authorizeUpgrade(address) internal override onlyOwner {}
```

This means that only the owner can perform the upgrade.

But, according to the existing implementation, the owner of the Pool proxy will always be the ZERO address, because it is not initialized.
Therefore, only the ZERO address can call `upgradeTo()` or `upgradeToAndCall()` successfully, and this is impossible.

## Tools Used
VS Code

## Recommended Mitigation Steps

Add an `initialize` function to call the `__Ownable_init()`.

The diff:
```
diff --git a/contracts/Pool.sol b/contracts/Pool.sol
index 3722082..ff664b5 100644
--- a/contracts/Pool.sol
+++ b/contracts/Pool.sol
@@ -22,6 +22,14 @@ contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable {

     event Transfer(address indexed from, address indexed to, uint256 amount);

+       constructor() {
+               _disableInitializers();
+       }
+
+       function initialize() external initializer {
+               __Ownable_init();
+       }
+
```
Pool Not Initializable Nor Upgradeable
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `Pool` contract is not initializable and therefore not upgradeable as its owner is never set meaning it always defaults to `address(0)`. Note that the core functionality of the `Pool` contract works as expected, it simply is not upgradeable.

## Issue Description
Looking at the `Pool` contract it inherits from OpenZeppelin's `OwnableUpgradeable`& `UUPSUpgradeable` mixins implementing the `_authorizeUpgrade` method, implying it should be upgradeable. However it has no initialization method calling the `__Ownable_init` and `__UUPSUpgradeable_init` initializers, meaning it can't be upgraded as it doesn't and can't have an owner. 

## Tools Used
Manual review.

## Recommended Mitigation Steps
If upgradeability is not necessary the `_authorizeUpgrade` method should be removed along with the imports and inheritance from the `UUPS` and `Ownable` upgradeable contracts. If upgradeability is desired an appropriate initialization method should be added.
Seller is unable to call bulkExecute or execute 
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If a seller calls `execute` or `bulkExecute` when the `currency` is set to `ETH` the transaction will fail. The seller would have to supply the `ETH` themselves effectively buying the NFT on the buyers behalf. 

## Proof of Concept
Seller calls `execute` 
Transaction fails because seller did not provide `ETH`.

## Recommended Mitigation Steps
When currency is ETH only the buyer should be able to execute

OR

if msg.sender != buyer, currency has to be set to either POOL or WETH
Insufficient validation on Exchange::_execute
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

`execute` only check if `sell.order.side == Side.Sell`. Thus, two sell orders as input is valid. Currently, we delay this check on call to `MatchingPolicy`. In the scenario that `MatchingPolicy` implemented incorrectly, anyone can match two sell order and if the `buyer` (which can be one of the seller) had also approved `WETH` to `executionDelegate` or had deposit ETH in `POOL`.

## Proof of Concept

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

```solidity
    function _execute(Input calldata sell, Input calldata buy)
        public
        payable
        reentrancyGuard
        internalCall
    {
        require(sell.order.side == Side.Sell);
```

## Recommended Mitigation Steps

Add check on buy order too

```solidity
    function _execute(Input calldata sell, Input calldata buy)
        public
        payable
        reentrancyGuard
        internalCall
    {
        require(sell.order.side == Side.Sell);
        require(buy.order.side == Side.buy);
```
Unknown variable with rights to move user funds
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In Pool.sol is a private constant variable that is not nearer declared and should not be defined here.
```bash
File: contracts/Pool.sol
19:     address private constant SWAP = 0x707531c9999AaeF9232C8FEfBA31FBa4cB78d84a;
```

This address has access to the 
```bash
File: contracts/Pool.sol
58:     function transferFrom(address from, address to, uint256 amount)
```
and can transfer any user funds and drain the contract.

## Proof of Concept
As defined here:
```
Pool.sol (51 sloc)
The pool allows user to predeposit ETH so that it can be used when a seller takes their bid. It uses an ERC1967 proxy pattern and only the exchange contract is permitted to make transfers.
```
only the Exchange.sol should have access to transfer funds.

Pool.sol can be deployed with any address for SWAP. At the moment it's the same value as EXCHANGE but if changed can result in draining all user funds.

## Recommended Mitigation Steps
Remove SWAP constant and change condition in transferFrom to only accept request if coming from EXCHANGE or define and clear up what the SWAP address is and from where it is coming.
it is possible that a user's ETH is trapped inside the contract.
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The current implementation of _returnDust() does not check assembly call's return value. So even if the call does not succeed, the whole execute() transaction will succeed. In this way, a user's ETH may be stuck in the contract if _returnDust() fails to send back ETH and will be claimed by another user later on.

## Proof of Concept
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol#L161

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

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

## Tools Used

## Recommended Mitigation Steps

Modify current implementation of _returnDust() to check assembly call's return value and change the codes to as follows.

function _returnDust() private {
        bool callStatus = true;

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

       require(callStatus, "Return dust failed");
    }
_returnDust function havent check the caller have receive function or not, which will lead to some eth locked in protocol and take by other users.
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
_returnDust function havent check the status after using calls to send the remaining ETH back to caller, which will lead to some ETH locked in protocol when the msg.sender is a contract that do not have receive function.

furthermore, _returnDust function is not using remainingETH as the amount it send to caller, but using selfbalance as the amount, which means if someone's ETH locked in protocol as what I described above, other user can use this function to "steal" the locked ETH in the protocol

## Proof of Concept

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

## Tools Used

## Recommended Mitigation Steps
add a status check in _ReturnDust function, record those failed ETH call allow admin or user to retrieve those locked ETH, using remainingETH as the amount not using selfbalance in order to avoid potential ETH stolen.
Dust amount may not be refunded
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If caller calling the `execute` function is a contract then it is possible that contract is not refunded the excess ETH passed. This happens since the return value for call is not validated

## Proof of Concept
1. User calls the `execute` function which post execution calls `_returnDust` function

```
function execute(Input calldata sell, Input calldata buy)
        external
        payable
        whenOpen
        setupExecution
    {
        _execute(sell, buy);
        _returnDust();
    }
```

2. As we can see the output of call `callStatus` is not validated and even if the call fails then also _returnDust will pass (User wont get any amount)

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

## Recommended Mitigation Steps
In _returnDust function check that callStatus is true
Pool.sol initialize() is missing
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Pool.sol is Upgradeable but not method "#initialize()", can't init owner,  owner still address(0), unable to Upgradeable

## Proof of Concept
```
contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable {
...

/***@audit not method initialize() ****


    function _authorizeUpgrade(address) internal override onlyOwner {} /****@audit upgrade need onlyOwner,but still address(0) ***/
```

## Tools Used

## Recommended Mitigation Steps
add initialize() , deploy need call 

```
contract Pool is IPool, OwnableUpgradeable, UUPSUpgradeable {

+   function initialize() external initializer {
+                __Ownable_init();
+   }
```
```
_returnDust can lock ETH in the contract due to lack of success check
Audit: 2022-11-non-fungible-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
After sending ETh through ```call```, the callStatus is not verified. If call does not succeed and there is gas remain, the operation won't revert, and the ETH will stay in the contract locked

## Proof of Concept
Alice deploy a smart contract called RussianRullet that in certain conditions, when it receives ETH through ```receive``` function, can revert.

This contract also has a function called ```tryLuck``` which just calls ```Exchange.execute{value:msg.value([])}``` function

1. Alice calls ```tryLuck``` with 1 ETH
1. ```Exchange.execute{value:msg.value([])}``` is executed
1. ```_returnDust``` is executed, and ETH is send back to RussianRullet smart contract
1. The call to RussianRullet smart contract for some reason reverts
1. _returnDust end its execution

The ETH send by Alice is now stuck in the contract.

## Tools Used
Manual review

## Recommended Mitigation Steps
Check ```callStatus``` value, and in case it is false, revert the operation.