xanoutas/solidity-security-findings huggingface dataset:
Contract Owner Possesses Too Many Privileges
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L898-L975


# Vulnerability details

## Impact
The owner has many privileges in the contract:
setBlockDelay, setAllowedVault, setMaxWinPorcent, setLimitOrdenPriceRange, setFees, setTradingExtension

## Proof of Concept
      function setBlockDelay(
        uint _blockDelay
    )
        external
        onlyOwner
    {
        blockDelay = _blockDelay;
    }

    /**
     * @dev Whitelists a stablevault contract address
     * @param _stableVault StableVault address
     * @param _bool true if allowed
     */
    function setAllowedVault(
        address _stableVault,
        bool _bool
    )
        external
        onlyOwner
    {
        allowedVault[_stableVault] = _bool;
    }

    /**
     * @dev Sets max payout % compared to margin
     * @param _maxWinPercent payout %
     */
    function setMaxWinPercent(
        uint _maxWinPercent
    )
        external
        onlyOwner
    {
        maxWinPercent = _maxWinPercent;
    }

    /**
     * @dev Sets executable price range for limit orders
     * @param _range price range in %
     */
    function setLimitOrderPriceRange(uint _range) external onlyOwner {
        limitOrderPriceRange = _range;
    }

    /**
     * @dev Sets the fees for the trading protocol
     * @param _open True if open fees are being set
     * @param _daoFees Fees distributed to the DAO
     * @param _burnFees Fees which get burned
     * @param _referralFees Fees given to referrers
     * @param _botFees Fees given to bots that execute limit orders
     * @param _percent Percent of earned funding fees going to StableVault
     */
    function setFees(bool _open, uint _daoFees, uint _burnFees, uint _referralFees, uint _botFees, uint _percent) external onlyOwner {
        unchecked {
            require(_daoFees >= _botFees+_referralFees*2);
            if (_open) {
                openFees.daoFees = _daoFees;
                openFees.burnFees = _burnFees;
                openFees.referralFees = _referralFees;
                openFees.botFees = _botFees;
            } else {
                closeFees.daoFees = _daoFees;
                closeFees.burnFees = _burnFees;
                closeFees.referralFees = _referralFees;
                closeFees.botFees = _botFees;                
            }
            require(_percent <= DIVISION_CONSTANT);
            vaultFundingPercent = _percent;
        }
    }

    /**
     * @dev Sets the extension contract address for trading
     * @param _ext extension contract address
     */
    function setTradingExtension(
        address _ext
    ) external onlyOwner() {
        tradingExtension = ITradingExtension(_ext);
    }

    // ===== EVENTS =====

    event PositionOpened(
        TradeInfo _tradeInfo,
        uint _orderType,
        uint _price,
        uint _id,
        address _trader,
        uint _marginAfterFees
    );

    event PositionClosed(
        uint _id,
        u
TradingLibrary#verifyPrice doesn't check if data is fresh which can lead to costly downtime
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/utils/TradingLibrary.sol#L113


# Vulnerability details

## Impact

verifyPrice may check against stale data causing valid transactions to revert

## Proof of Concept

        if (_chainlinkEnabled && _chainlinkFeed != address(0)) {
            int256 assetChainlinkPriceInt = IPrice(_chainlinkFeed).latestAnswer();
            if (assetChainlinkPriceInt != 0) {
                uint256 assetChainlinkPrice = uint256(assetChainlinkPriceInt) * 10**(18 - IPrice(_chainlinkFeed).decimals());
                require(
                    _priceData.price < assetChainlinkPrice+assetChainlinkPrice*2/100 &&
                    _priceData.price > assetChainlinkPrice-assetChainlinkPrice*2/100, "!chainlinkPrice"
                );
            }
        }

Chainlink prices are used as a fail-safe in case malicious price data is being produced by a node. When pulling the price it never checks if the price is fresh. This can lead to legitimate transactions reverting if the Chainlink price is stale, causing the whole system to freeze until the oracle is back online.

## Tools Used

Manual Review 

## Recommended Mitigation Steps

Downtime in a futures market can be extremely costly. Since the Chainlink oracle is just a fail-safe it should be bypassed if the data is stale
Distribute is open to rewards manipulation  
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L225
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L240
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L185


# Vulnerability details

## Impact
The distribute() function is prone to manipulation by the first depositor if the totalShares is low, since the result of transferFrom() in distribute() is not checked. This can happen if a malicious user calls createLock() whereby shares = 1 then calls distribute() with a large amount. 

## Proof of Concept
1) User calls createLock() to create bond with _amount * _period == 365: then the number of shares will be equal to 0. For example, _amount = 31 wei and period = 12. 
2) User then calls distribute() with a very large _amount, the transferFrom() will fail but is not checked, so this will increase accRewardsPerShare by _amount * 1e18 per call via https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L225   
3) User then calls claim() in Lock which will call claim() in BondNFT, which triggers idToBond which will increment pending value to a very large value: https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L240-L241 
4) Amount transferred to user is based on the above large pending value: https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L185 

## Tools Used
None

## Recommended Mitigation Steps
Add a check on the transferFrom() result and also add a check on the number of shares created.
ERC20 approvals may need to be set to 0 beforehand
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L652
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L117


# Vulnerability details

## Impact
There are some instances where there is an ERC20 approval for a max uint256 amount. ERC20 tokens such as USDT require the address allowance to be set to 0 beforehand, so this would cause reverts for those tokens.

## Proof of Concept
-Token such as USDT gets whitelisted
-Normal call for to stableVault contract will revert, via https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L652 since USDT requires an approval of 0 first  
-This can also apply in the Lock, asset distribution to bondNFT contract: https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L117

## Tools Used
None

## Recommended Mitigation Steps
In the above scenarios, precede the corresponding lines with the line: IERC20(_asset).approve(_spender) ,0) to account for that edge case.
IERC20.transfer does not support all ERC20 token
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L651


# Vulnerability details

## Impact


## Proof of Concept
Token like USDT known for using non-standard ERC20. (Missing return boolean on transfer).

Contract function _handleDeposit will always revert when try to transferfrom this kind of tokens.

## Tools Used
manual review 
## Recommended Mitigation Steps
We recommend using OpenZeppelin's SafeERC20 instead of IERC20 transferfrom. This accepts ERC20 token with no boolean return like USDT
Unreleased locks cause the reward distribution to be flawed in BondNFT
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L150
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L225


# Vulnerability details

## Impact
After a lock has expired, it doesn't get any rewards distributed to it. But, unreleased locks cause other existing bonds to not receive the full amount of tokens either. The issue is that as long as the bond is not released, the `totalShares` value isn't updated. Everybody receives a smaller cut of the distribution. Thus, bond owners receive less rewards than they should.

A bond can be released after it expired by the owner of it. If the owner doesn't release it for 7 days, anybody else can release it as well. As long as the owner doesn't release it, the issue will be in effect for at least 7 epochs.

Since this causes a loss of funds for every bond holder I rate it as HIGH. It's likely to be an issue since you can't guarantee that bonds will be released the day they expire.

## Proof of Concept
Here's a test showcasing the issue:

```js
// 09.Bonds.js

    it.only("test", async function () {
      await stabletoken.connect(owner).mintFor(owner.address, ethers.utils.parseEther("100"));
      await lock.connect(owner).lock(StableToken.address, ethers.utils.parseEther("100"), 100);
      await stabletoken.connect(owner).mintFor(user.address, ethers.utils.parseEther("1000"));
      await lock.connect(user).lock(StableToken.address, ethers.utils.parseEther("1000"), 10);
      await stabletoken.connect(owner).mintFor(owner.address, ethers.utils.parseEther("1000"));
      await bond.distribute(stabletoken.address, ethers.utils.parseEther("1000"));

      await network.provider.send("evm_increaseTime", [864000]); // Skip 10 days
      await network.provider.send("evm_mine");

      [,,,,,,,pending,,,] = await bond.idToBond(1);
      expect(pending).to.be.equals("499999999999999999986");
      [,,,,,,,pending,,,] = await bond.idToBond(2);
      expect(pending).to.be.equals("499999999999999999986");


      await stabletoken.connect(owner).mintFor(owner.address, ethers.utils.parseEther("1000"));
      await bond.distribute(stabletoken.address, ethers.utils.parseEther("1000"));

      await network.provider.send("evm_increaseTime", [86400 * 3]); // Skip 3 days
      await network.provider.send("evm_mine");

      // Bond 2 expired, so it doesn't receive any of the new tokens that were distributed
      [,,,,,,,pending,,,] = await bond.idToBond(2);
      expect(pending).to.be.equals("499999999999999999986");

      // Thus, Bond 1 should get all the tokens, increasing its pending value to 1499999999999999999960
      // But, because bond 2 wasn't released (`totalShares` wasn't updated), bond 1 receives less tokens than it should.
      // Thus, the following check below fails
      [,,,,,,,pending,,,] = await bond.idToBond(1);
      expect(pending).to.be.equals("1499999999999999999960");

      await lock.connect(user).release(2);

      expe
Error in trade accumulated interest calculation
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L41-L65


# Vulnerability details

## Impact
The trade's accumulated interest may result smaller than expected when long open interest is zero.

## Proof of Concept

https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L41-L65

```solidity
function trades(uint _id) public view returns (Trade memory) {
	...
	int256 _pendingFunding;
	if (_trade.direction && longOi[...][...] > 0) {
		_pendingFunding = (int256(block.timestamp-lastUpdate[...][...])*fundingDeltaPerSec[...][...])*1e18/int256(longOi[...][...]);
		if (longOi[...][...] > shortOi[...][...]) {
			_pendingFunding = -_pendingFunding;
		} else { // NOTE else shortOi > longOi
			_pendingFunding = _pendingFunding*int256(1e10-vaultFundingPercent[_trade.asset][_trade.tigAsset])/1e10;
		}
	} else if (shortOi[_trade.asset][_trade.tigAsset] > 0) { //
		_pendingFunding = (int256(block.timestamp-lastUpdate[...][...])*fundingDeltaPerSec[...][...])*1e18/int256(shortOi[...][...]);
		if (shortOi[...][...] > longOi[...][...]) { // shortOi > longOi
			_pendingFunding = -_pendingFunding;
		} else {
			_pendingFunding = _pendingFunding*int256(1e10-vaultFundingPercent[...][...])/1e10;
		}
	}
	_trade.accInterest += (int256(_trade.margin*_trade.leverage/1e18)*(accInterestPerOi[...][...][...]+_pendingFunding)/1e18)-initId[_id];
	
	return _trade;
}
```

The `if` at [line 47 in Position.sol](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L47) is an and condition between the direction of the trade and the long open interest for the asset/tig asset pair, so if the long open interest is zero, the `else` branch at [line 54](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L54) will be executed no matter what is the direction of the trade. As a result, the `_pendingFunding` value will be nonzero and added to the `_trade.accInterest` at [line 62](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L62). Note that the inverse condition is not true, i.e. if direction is short and short open interest is zero then `_pendingFunding` is zero.

In conclusion if trade direction is true and long open interest is zero, the trade is losing interest because the calculated `_pendingFunding` for short open interest is then sign inverted and added to the `_trade.accInterest` at line [line 62](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Position.sol#L62).

## Tools Used
Manual review

## Recommended Mitigation Steps

```solidity
if (_trade.direction && longOi[...][...] > 0) {
	...
} else if (!_trade.direction && shortOi[_trade.asset][_trade.tigAsset] > 0) {
	...
}
```

or

```solidity
if (_trade.directio
Deposits would revert
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Trading.sol#L643-L659


# Vulnerability details

## Impact

When the margin asset is USDT, after the first deposit all following ones would revert allowing no more trades.

## Proof of Concept

The [`_handleDeposit()`](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Trading.sol#L643-L659) function in [Trading.sol](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Trading.sol)'s Trading contract is calling [approve()](https://github.com/code-423n4/2022-12-tigris/blob/0cb05a462e78c4470662e9d9a4f9ab587f266bb5/contracts/Trading.sol#L652) inconditionally at every deposit.

The USDT Tethered stablecoin uses a mitigation to avoid approve race conditions which reverts if the allowance is non-zero, as showed in the code snippet below taken from [USDT contract on Etherscan](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7#code#L194):

```solidity
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {

	// To change the approve amount you first have to reduce the addresses`
	//  allowance to zero by calling `approve(_spender, 0)` if it is not
	//  already 0 to mitigate the race condition described here:
	//  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
	require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));

	allowed[msg.sender][_spender] = _value;
	Approval(msg.sender, _spender, _value);
}
```

## Tools Used
Manual review

## Recommended Mitigation Steps

Either approve the exact amount to be transferred before the transfer if the exact amount can be known in advance at the calling contract level, or set to an high enough amount (e.g. max uint) before the deposit and 0 immediately after, or just call the margin asset approve once when it is whitelisted using max uint but keep in mind that not all token contracts implements the 'unlimited approval' feature and therefore each transfer will decrease the allowance by the transferred amount.
ChainLink oracle could be using stale data
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/utils/TradingLibrary.sol#L113


# Vulnerability details

## Impact
ChainLink oracle could be using stale data which can cause unexpected behavior in trading and make trading susceptible to oracle attacks (flash loans etc). 

## Proof of Concept
In verifyPrice() the ChainLink feed should use latestRoundData() instead of latestAnswer() https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/utils/TradingLibrary.sol#L113  since latestAnswer() returns the last value but the data could be stale. Using latestRoundData() allows for checking the updateTime and answeredInRound fields for staleness validation. 

## Tools Used
None

## Recommended Mitigation Steps
Use (roundId, price, , updateTime, answeredInRound) = IPrice(_chainlinkFeed).latestRoundData() and add latestRoundData() to IPrice interface.
Proxy approval can be done for address without fallback or with malicious fallback
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L583


# Vulnerability details

## Impact
In approveProxy() https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L583 since there are no validation checks on the proxy address, it is possible the msg sender approves either a contract with no fallback or one with a malicious fallback function. The first case leads to the ETH being lost and the second case leads to the possibility for stolen ETH.

## Proof of Concept
Either: 
1) User sends ETH to contract without fallback function, leading to lost ETH 
2) User sends ETH to contract with malicious fallback function, leading to lost ETH 

## Tools Used
None

## Recommended Mitigation Steps
Use a whitelist for allowed proxy contracts.
Range set to 0 in setLimitOrderPriceRange() can cause issues in executeLimitOrder()
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L939
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L496


# Vulnerability details

## Impact
Can cause issues in executeLimitOrder() to always revert unless price = trade.price  

## Proof of Concept
-Set _range to 0 in setLimitOrderPriceRange() in https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L939 
-This then causes executeLimitOrder() to always revert unless price = trade.price via https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L496 

## Tools Used
None

## Recommended Mitigation Steps
Add in a check so that _range cannot be 0 in setLimitOrderPriceRange()
setFees should update fees only for new orders
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L943-L969


# Vulnerability details

## Impact

Users have to put a lot of trust in the owner, which should not be the case in a web3 application.

They might not want to use the platform knowing about such a risk.

## Proof of Concept

In [Trading.sol#L943-L969](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L943-L969)
the owner may at any time update the close fees for already open positions to values unacceptable for the traders, essentially stealing their funds.

## Tools Used

Visual Studio Code

## Recommended Mitigation Steps

Consider refactoring the code, so that the platform users know all the fees before placing a position, and `setFees` affects only new orders.
Assuming that tokens will always be 18 decimal
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L180


# Vulnerability details

## Impact

The contract is always divided by 10**18 assuming that all tokens will be 18 decimal but there is nothing that force an ERC20 token to be 18 decimal. Since there is function addAsset then this means that different ERC20 token can be added and one of them may be a token with 18 decimal.

## Proof of Concept

Any token with less or more than 18 decimal will cause a wrong calculation in different places in the contract. https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/BondNFT.sol#L178

## Tools Used
VSCODE
## Recommended Mitigation Steps

Get the decimals from the function ```decimals()``` and divide by it.
tradin.sol _handleWithdraw does not verify that tokens were transferred successfully
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L676


# Vulnerability details

## Impact
If for some reason the ERC20 transfer is temporarily failing, the user would totally lose his allocation and funds. All the state variables would already have been updated at this stage, so he can't call withdraw again. There is no way to withdraw these locked tokens.

## Proof of Concept
At the last point of withdraw, the function is sending the funds to the user, and does not check the return value - whether it has succeeded:

IERC20(_outputToken).transfer(_trade.trader, IERC20(_outputToken).balanceOf(address(this)) - _balBefore);

## Tools Used
mnual review
## Recommended Mitigation Steps
add a check that verifies that the transfer has succeede