xanoutas/solidity-security-findings huggingface dataset:
Position Creation Re-Entrancy Vulnerability
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `Position` contract performs a `_safeMint` operation when actually creating a position via `mint`. The vulnerability arises from the code's deviation from the Checks-Effects-Interactions (CEI) pattern and in particular, the `initId` entry written to after creation internally as well as the `limitDelay` entry written to after creation in the [`initiateLimitOrder` function](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L333-L347) of `Trading`. This re-entrancy can be exploited in two ways and as such, this exhibit can be considered as counting for either one or two major vulnerabilities (in the past, some C4 contests have permitted the same flaw exploited in a different manner to be counted twice).

The first vulnerability is significant as it causes [the `trades` function to yield a significantly inflated `accInterest` variable](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Position.sol#L62). This can be exploited by creating a position via `initiateMarketOrder` in the `Trading` contract and invoking `addToPosition` right afterwards. All validation checks are passed and the position now permanently stores the invalid `accInterest` to `storage` via the `setAccInterest` function.

The second vulnerability is also significant and arises from the inexistent prevention of a limit-order duration. Given that `limitDelay` is never written to, it is possible to immediately create a limit order and consume it in the same transaction via `executeLimitOrder` further compromising the system by being able to acquire an instant profit.

## Proof of Concept

For the first vulnerability, we can observe that the `initId` entry is [written to with a default value](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Position.sol#L153) causing calculations in the `trades` function to yield a zero-value in `accInterest` for a properly initialized NFT. Given that we can re-enter all contracts of the system, we can invoke the `addToPosition` function with a zero amount to permanently store the inflated `accInterest` value that is temporarily possible in the hijacked `mint` call.

All logical checks (`_checkOwner`, `_checkDelay`, `tradingExtension.validateTrade`, `_checkVault`) will be passed successfully as the entries the `mint` function of `Position` writes to after `_safeMint` are solely utilized during the burn operation and even then remain unvalidated. At this point, we have managed to permanently store the inflated `accInterest` value which we can claim after the block delay for positions has passed.

For the second vulnerability, we can observe that the [limit order delay is written to](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Trading.sol#L347) after the `mint` operation concludes. This permits us to exploit it, closing our limit order im
BondNFT.sol : `createLock` does not update the owner
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/BondNFT.sol#L57-L86


# Vulnerability details

## Impact
Since the owner does not updated, the bond can not be extended or claimed or released by the respective owner to whom the bond was supposed to be.

## Proof of Concept


        function createLock(
        address _asset,
        uint _amount,
        uint _period,
        address _owner
    ) external onlyManager() returns(uint id) {
        require(allowedAsset[_asset], "!Asset");
        unchecked {
            uint shares = _amount * _period / 365;
            uint expireEpoch = epoch[_asset] + _period;
            id = ++totalBonds;
            totalShares[_asset] += shares;
            Bond memory _bond = Bond(
                id,             // id
                address(0),     // owner
                _asset,         // tigAsset token
                _amount,        // tigAsset amount
                epoch[_asset],  // mint epoch
                block.timestamp,// mint timestamp
                expireEpoch,    // expire epoch
                0,              // pending
                shares,         // linearly scaling share of rewards
                _period,        // lock period
                false           // is expired boolean
            );
            _idToBond[id] = _bond;
            _mint(_owner, _bond);
        }
        emit Lock(_asset, _amount, _period, _owner, id);
    }

 
The createLock function set the owner as address(0)   `address(0),     // owner`

## Tools Used

Manual review

## Recommended Mitigation Steps

Update the correct owner.
Delisted token will cause user loss
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L89-L96
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L875-L878
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L236
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L390


# Vulnerability details


## Impact

In StableVault.sol, the asset could be delisted, and no longer be allowed to trade. However, existing positions of the delisted asset will also be affected. Those positions can not be closed, or add margin to the positions. The users can only be liquidated and suffer fund loss.


## Proof of Concept


The asset could be delisted in vault.
```solidity
File: contracts/StableVault.sol
89:     function delistToken(address _token) external onlyOwner {
90:         require(allowed[_token], "Not added");
91:         tokenIndex[tokens[tokens.length-1]] = tokenIndex[_token];
92:         tokens[tokenIndex[_token]] = tokens[tokens.length-1];
93:         delete tokenIndex[_token];
94:         tokens.pop();
95:         allowed[_token] = false;
96:     }
```

After delisted, the `_checkVault()` will revert.
```solidity
File: contracts/Trading.sol
875:     function _checkVault(address _stableVault, address _token) internal view {
876:         require(allowedVault[_stableVault], "Unapproved stablevault");
877:         require(_token == IStableVault(_stableVault).stable() || IStableVault(_stableVault).allowed(_token), "Token not approved in vault");
878:     }
```

As a result, users won't be able to call the following functions:
- `initiateCloseOrder()`
- `addMargin()`

```solidity
File: contracts/Trading.sol
222:     function initiateCloseOrder() {

236:         _checkVault(_stableVault, _outputToken);

378:     function addMargin() {

390:         _checkVault(_stableVault, _marginAsset);
```

The only way to close these positions with delisted assets is to liquidate.


## Tools Used
Manual analysis.

## Recommended Mitigation Steps

Enforce the close of associated assets before delisting. Could implement some admin function to handle the special condition.
Trading and Position is reentrancy attack vulnerable
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The storage states of contract Position will become chaotic.
All functions of contract [Trading](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L79) may be affected by this, and there will be a variety of uncertain security risks.

## Proof of Concept
When [Position.mint()](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L131) is called by [Trading.initiateMarketOrder()](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L204) or [Trading.initiateLimitOrder()](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L333), the reentrant-friendly function [`_safeMint()`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L148) is used to create the new Position NFT:
```solidity
function mint(MintTrade memory _mintTrade) external onlyMinter {
    uint newTokenID = _tokenIds.current();

    Trade storage newTrade = _trades[newTokenID];
    newTrade.margin = _mintTrade.margin;
    ...
    _safeMint(_mintTrade.account, newTokenID);
    ...
    _tokenIds.increment();
    ...
}
```

The `_safeMint()` will callback `_checkOnERC721Received()` of the `_mintTrade.account` if the account is a contract.

If a hacker initiates an order using a special trader(which is a maliciouse contract), it will be able to reenter any function of contract [Tranding](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb/contracts/Trading.sol#L333) and [Position](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L9).

The following shows one of the many possible attacks, which will make orders of contract Position chaotic:

```
diff --git a/contracts/mock/BadTrader.sol b/contracts/mock/BadTrader.sol
new file mode 100644
index 0000000..362a13c
--- /dev/null
+++ b/contracts/mock/BadTrader.sol
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "../Trading.sol";
+import "../Position.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
+
+contract BadTrader is Ownable {
+    Trading public trading;
+    Position public position;
+    IERC20 public token;
+
+    uint256 private reenter;
+    bytes private data;
+    uint256[] private createdPositions;
+
+    constructor(Trading _trading, Position _position, IERC20 _token) {
+        trading = _trading;
+        position = _position;
+        token = _token;
+        token.approve(address(trading), type(uint256).max);
+    }
+
+
Withdrawal Denial-of-Service Attack
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/Lock.sol#L78-L92


# Vulnerability details

## Impact

The `Lock` contract is meant to be the `manager` of the `BondNFT` implementation and performs accounting of asset inflow and outflow via the `totalLocked` variable that is incremented during a `lock` and decremented during a `release`. The issue arises in the fact that the `extendLock` function can increase the assets of a particular bond but does not increment the `totalLocked` variable. 

As a result, when the `release` function is executed the `totalLocked` variable will be decremented for more than it has accounted for thus causing whatever funds were deposited using `extendLock` to be un-retrievable either for the user that deposited them or, in a worst case malicious attack, for the remaining users that have yet not withdrawn their locks.

## Proof of Concept

We can create a 7-day empty bond and then invoke `extendLock` with a significant amount of funds, specifying the minimum period possible. 

If no one detects our significantly large deposit for 7 days and withdraws, we can perform a withdrawal at the end and all funds we have deposited will "tap" into the `totalLocked` allocation of other deposits. This will cause the other deposits to no longer be withdrawable due to an underflow that would occur in `release`.

Given that you cannot `release` a bond early, the attack is trivial to execute if users specify a period larger than `7` days for their deposit (which they are presumably incentivized to do so under the Tigris ecosystem).

## Tools Used

Manual review.

## Recommended Mitigation Steps

We advise the `totalLocked` variable to be properly incremented during an `extendLock` operation as well. Given that the variable entry is purely utilized for off-chain purposes, it could potentially be omitted entirely as well since other measurement methodologies can be used such as actual EIP-20 asset balances.
Incorrect Reward Accounting System
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L54
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L68
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.so#L79-L81
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.soll#L96-L99


# Vulnerability details

## Impact

The reward accounting system of the `GovNFT` contract is flawed as it improperly constantly increments the reward accounting variables rather than overwriting them in the cases that they need to be so. The main vulnerability appears to arise from all transfer-like operations and how they increment the `userPaid` entries. Additionally, self-transfers should be prohibited as they do not allow the system to properly account rewards in the current state either.

## Proof of Concept

We can create a simple state machine with one NFT asset and simple values to illustrate the issue. Let's consider a `_mint` operation with a value of `5` for the asset's `accRewardsPerNFT` entry. Our user's state can be observed as follows:

```solidity
// 0 += 5 -> userPaid = 5
userPaid[to][asset] += accRewardsPerNFT[asset];
```

As time passes, a distribution occured incrementing the `accRewardsPerNFT` value by `5`:

```solidity
// 5 += 5 / 1 -> accRewardsPerNFT = 10
accRewardsPerNFT[_tigAsset] += _amount/totalSupply()
```

At this point, the user is owed a `5` unit delta based on the implementation of `claim` and in detail [`pending`](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L296-L298): 

```solidity
function pending(address user, address _tigAsset) public view returns (uint256) {
    // return 0 + 1 * 10 - 5; -> return 5;
    return userDebt[user][_tigAsset] + balanceOf(user)*accRewardsPerNFT[_tigAsset] - userPaid[user][_tigAsset]; 
}
``` 

If the user performs a `mint` operation or a self-transfer however, we have the following state change (self-transfer):

```solidity
// 0 += 10 -> userDebt = 10
userDebt[from][asset] += accRewardsPerNFT[asset];
// 10 -= 5 / 1 -> userDebt = 5
userDebt[from][asset] -= userPaid[from][asset]/balanceOf(from);
// 5 -= 5 / 1 -> userPaid = 0
userPaid[from][asset] -= userPaid[from][asset]/balanceOf(from);
// 0 += 10 -> userPaid = 10
userPaid[to][asset] += accRewardsPerNFT[asset];
```

Instead of acquiring a `5` increase in our debt that we can claim later, we have in-fact acquired a `5` unit **deficiency**, causing the [`pending function above`](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/GovNFT.sol#L296-L298) to actually fail to even execute due to an underflow. The same applies to a `mint` operation as well as a `transfer` to a party that has not yet claimed their rewards.

## Tools Used

Manual review.

## Recommended Mitigation Steps

We advise the reward attribution system to be revised to use an overwrite of the `userPaid` value rather than an incremention as the latter will not properly work for users w
Fees are not collected from traders in `addToPosition`
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The protocol doesn't receive fees when margin is added via the `addToPosition` function. An equal amount of tigUSD is minted even though it's not backed by margin.
## Proof of Concept
The protocol takes fees from margin provided by users: for example, when opening a market order:
1. fees are calculated on the total order size (margin * leverage): [Trading.sol#L178](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L178);
1. full margin is transferred from the trader (fees are not subtracted): [Trading.sol#L180](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L180);
1. accounted margin excludes fess: [Trading.sol#L183-L194](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L183-L194) (see `_marginAfterFees`).

Thus, traders provide full margin when opening orders and fees are subtracted from accounted margin. If trader closes an order, they'll receive margin - fees.

The protocol also allows traders to add margin to existing positions at a different price, via the `addToPosition` function ([Trading.sol#L255](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L255)). This function, however, subtracts fees from margin *before* transferring margin from traders:
```solidity
_handleDeposit(
    _trade.tigAsset,
    _marginAsset,
    _addMargin - _fee, // @audit trader won't pay fees
    _stableVault,
    _permitData,
    _trader
);
```

As a result, the protocol doesn't receive fees when margin is added via `addToPosition`.
## Tools Used
Manual review
## Recommended Mitigation Steps
Consider not subtracting fees from `_addMargin` in `addToPosition` when calling `_handleDeposit`.
Repeat claim expired BondNFT reward to increase `accRewardsPerShare` and steal fund
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/BondNFT.sol#L168-L184
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/BondNFT.sol#L235-L243


# Vulnerability details


## Impact

In `BondNFT.sol#claim()`, `accRewardsPerShare[][]` is amended to reflect the expired shares. But the current way introduces other issues:
- All the rewards could be drained.
- `accRewardsPerShare` might be inaccurate.


## Proof of Concept

The rationale behind the unchecked block below seems to take into account the shares of reward of the expired bond. However, the current way is flawed.

```solidity
File: contracts/BondNFT.sol
168:     function claim(
169:         uint _id,
170:         address _claimer
171:     ) public onlyManager() returns(uint amount, address tigAsset) {
    
176:         unchecked {
177:             if (bond.expired) {
178:                 uint _pendingDelta = (bond.shares * accRewardsPerShare[bond.asset][epoch[bond.asset]] / 1e18 - bondPaid[_id][bond.asset]) - (bond.shares * accRewardsPerShare[bond.asset][bond.expireEpoch-1] / 1e18 - bondPaid[_id][bond.asset]);
179:                 if (totalShares[bond.asset] > 0) {
180:                     accRewardsPerShare[bond.asset][epoch[bond.asset]] += _pendingDelta*1e18/totalShares[bond.asset];
181:                 }
182:             }
183:             bondPaid[_id][bond.asset] += amount;
184:         }
```

Moreover, this treatment could be abused to steal all the rewards in the contract.

Assuming, on day 0, `epoch[tigAsset]` is 100, `accRewardsPerShare[tigAsset][100]` is 200. `totalShares[tigAsset]` is 300. (decimal calculation will be omitted for simplicity)
The malicious user will do the following:
- have 2 BondNFT with the same tigAsset, both have shares of 100, and id 1001 and 1002. 1001 expires 7 days later, 1002 expires 14 days later. Now `totalShares[tigAsset]` is 500.
- on day 6, `accRewardsPerShare[tigAsset][106]` is 300, which will be used for 1001 rewards calculation.
- on day 9, `accRewardsPerShare[tigAsset][109]` is 400, due to new distribution.
- on day 9, 1001 has expired for 2 days. The user will call `Lock.sol#claim(1001)` to have the reward for 1001.
`_pendingDelta` will be 100 * 400 - 100 * 300 = 10000.
According to line 180, `accRewardsPerShare[tigAsset][109]` will be increased by 10000 / 500 = 20, change from 400 to 420.
- The user then calls `Lock.sol#claim(1001)` again. `bond.pending` will be 0, but the unchecked block will be run one more time, `_pendingDelta` will be 100 * 420 - 100 * 300 = 12000.
`accRewardsPerShare[tigAsset][109]` will be increased by 12000 / 500 = 24, change from 420 to 444.
- the user will repeat until `accRewardsPerShare[tigAsset][109]` is large enough. Then using bond 1002, to claim the rewards. With a large `accRewardsPerShare[tigAsset][109]`, all the reward balance can be taken.


The claimable reward is stored in `bond.pending`, w
Hardcoded stable coin exchange rate
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L44-L72


# Vulnerability details

## Impact

Currently the exchange rate for tigUSD and other stable coin is hardcoded as 1:1. According to the [doc](https://docs.tigris.trade/protocol/tigusd-and-stablevault) 

> They are redeemable 1:1 for any asset in its respective StableVault

However, mainstream stable coins prices did fluctuates, USDT/USDC/DAI all could go up and down. If the strict 1:1 exchange rate is applied, sometimes there would be arbitrage opportunity and the contract could suffer fund loss.


## Proof of Concept

From the historical prices of some stable coins, it can be seen that price could deviate from 1, and different stable coins are not in phase with each other, which leaves potential room to take advantage of tigUSD vault.

- [USDT](https://coinmarketcap.com/currencies/tether/)
in 11/21/2018, the price falls to as low as 0.9734, and in 12/20/2018 rises to as high as 1.024.

- [USDC](https://coinmarketcap.com/currencies/usd-coin/)
in 03/19/2020, the price falls to as low as 0.9709, and in 01/31/2020 rises to as high as 1.0436.

- [DAI](https://coinmarketcap.com/currencies/multi-collateral-dai/)
in 05/16/2020, the price falls to as low as 0.9846, and in 09/11/2020 rises to as high as 1.0423.

Considering the non-transparency of USDT, regulatory risks of USDC and other defi protocols, unforeseen attack causing contract being compromised, etc. Not to mention severe events for luna/UST. The prices of stable coins are not guaranteed to keep stay around 1 USD.


The exchange rate for tigUSD and other stable coins are hardcoded as 1:1 in StableVault.sol.
```solidity
File: contracts/StableVault.sol
44:     function deposit(address _token, uint256 _amount) public {
45:         require(allowed[_token], "Token not listed");
46:         IERC20(_token).transferFrom(_msgSender(), address(this), _amount);
47:         IERC20Mintable(stable).mintFor(
48:             _msgSender(),
49:             _amount*(10**(18-IERC20Mintable(_token).decimals()))
50:         );
51:     }

53:     function depositWithPermit(address _token, uint256 _amount, uint256 _deadline, bool _permitMax, uint8 v, bytes32 r, bytes32 s) external {
54:         uint _toAllow = _amount;
55:         if (_permitMax) _toAllow = type(uint).max;
56:         ERC20Permit(_token).permit(_msgSender(), address(this), _toAllow, _deadline, v, r, s);
57:         deposit(_token, _amount);
58:     }


65:     function withdraw(address _token, uint256 _amount) external returns (uint256 _output) {
66:         IERC20Mintable(stable).burnFrom(_msgSender(), _amount);
67:         _output = _amount/10**(18-IERC20Mintable(_token).decimals());
68:         IERC20(_token).transfer(
69:             _msgSender(),
70:             _output
71:         );
72:     }
```

If some day USDT prices falls to 0.99, but DAI still 1.00. Users can deposit with USDT and withdraw with DAI