xanoutas/solidity-security-findings huggingface dataset:
Reentrancy attacks in `mint` function
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L126-L161
 https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L156-L210
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L307-L349


# Vulnerability details

### Author: rotcivegaf

### Impact

The root of the problem are in the [`mint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L126-L161) who use [`_safeMint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L148). This mint call the function [`onERC721Received`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/b438cb695a1ac520cee6678610b161b1d5df4d9c/contracts/token/ERC721/ERC721.sol#L261-L264) of the token recipient, generating a possible reentrancy attack

### Proof of Concept

A wallet can call the [`initiateMarketOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L156-L210) or [`initiateLimitOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L307-L349) and this functions call the [`mint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L126-L161) function who inside call the [OZ `_safeMint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L148)
When the the `_safeMint` call `onERC721Received` of the `_mintTrade.account`, this one have the possibility to call functions and the lines [L149-L160 of `mint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L149-L160) are not execute yet, also [L207-L210 of `initiateMarketOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L207-L210) and [L347-L348 of `initiateLimitOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L347-L348)
The `_mintTrade.account` can call:
- [`executeLimitOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L475-L522) and avoid [`require(block.timestamp >= limitDelay[_id]);`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L491)
- [`cancelLimitOrder`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L352-L368), [`liquidatePosition`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L524-L556), [`initiateClo
Adding to an existing position results in leakage of margin in stable vault
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
'addToPosition' function adds additional margin to an existing trade. Margin value sent to '_handleDeposit' function sends margin adjusted for 'fees' instead of sending full margin. This results in a lesser margin withdrawn from traders account - since this directly impacts the margin amount deposited in stable vault, I have assigned 'HiGH' risk to this finding

In [Line 275](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L275), margin value sent to '_handleDeposit' is '_add Margin -_fee' instead of '_addMargin'. 

In [Line 651](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L651), note that the 'margin' amount is transfered from trader to this contract. Since we sent a 'margin adjusted for fees', a lesser margin will be deducted from trader account.

This messes with the fees calculations as tgUSD is minted to govNFT holders with no collateral backing the tokens. On a cumulative basis, when traders add Margin, this shortfall keeps growing





## Proof of Concept
Bob is a trader with an existing position on ETH/USDT pair. Bob adds an additional 100k USDT as margin. At a 0.1% mint fee, Bob will have a fees of 100$. Current contract is only sending 99,900 USDT margin amount to '_handleDeposit' which subsequently withdraws this amount from trader account.

100$ that was supposed to be rewarded to Gov NFT holders never made it to the protocol. Essentially the tgUSD minted to referrer/ bots is unbacked because of this shortfall 

## Tools Used

## Recommended Mitigation Steps

Strangely in Line 180 , when a new market order is created, logic is correctly handled. '_tradeinfo.margin' is sent to '_handleDeposit' instead of '_marginAfterfees'. However exact same logic in 'addPosition' is not followed.

I recommend to pass '_addMargin' instead of '_addMargin -_fee' to the 'handleDeposit' function  in line 275 for correct accounting
User can bypass maxWinPercent by closing out their position in smaller chunks
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

User can make more money than expected potentially causing liquidity issues for the stableVault

## Proof of Concept

        (IPosition.Trade memory _trade, uint256 _positionSize, int256 _payout) = tradingExtension._closePosition(_id, _price, _percent);
        position.setAccInterest(_id);
        _updateFunding(_trade.asset, _trade.tigAsset);
        if (_percent < DIVISION_CONSTANT) {
            if ((_trade.margin*_trade.leverage*(DIVISION_CONSTANT-_percent)/DIVISION_CONSTANT)/1e18 < tradingExtension.minPos(_trade.tigAsset)) revert("!size");
            position.reducePosition(_id, _percent);
        } else {
            position.burn(_id);
        }
        uint256 _toMint;
        if (_payout > 0) {
            unchecked {
                _toMint = _handleCloseFees(_trade.asset, uint256(_payout)*_percent/DIVISION_CONSTANT, _trade.tigAsset, _positionSize*_percent/DIVISION_CONSTANT, _trade.trader, _isBot);

                //@audit compares _toMint against margin of entire trade rather than just the portion being withdrawn
                if (maxWinPercent > 0 && _toMint > _trade.margin*maxWinPercent/DIVISION_CONSTANT) {
                    _toMint = _trade.margin*maxWinPercent/DIVISION_CONSTANT;
                }
            }
            _handleWithdraw(_trade, _stableVault, _outputToken, _toMint);
        }

maxWinPercent ensures that the user is only able to make up to a certain percentage gain. Trading#_closePosition implements a check [L625](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Trading.sol#L625) to reduce the user's payout if maxWinPercent is exceeded. The problem is that the check will be incorrect if the user is closing only a portion of their position, because it compares the amount to mint against the margin of the ENTIRE position rather than just the portion that is being closed.

This is possible because _trade is stored to memory before the reducePosition call, which modifies the trade storage. This means that the original margin value will be used instead of the modified one.

Example:
Assume maxWinPercent = 200e10 (100% gain) and trade.Margin = 100e18. Now the user's trade is highly profitable and the user is set to get a payout of 250e18 (150% gain). The check in L625 should reduce this to 200e18 (200e10 * 100e18 / 100e10). The user can avoid this limit by closing out 80% of their position at a time. This would mean that 80e18 of their margin would be closed which would net them a payout of 200e18 (150% gain). They could then repeat this process again withdrawing 80% of their new position. After 3 iterations they would have successfully avoided the cap on 99.2% of their original position.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Adjust the comparison to account for
Protocol is served as zero-slippage liquidity pool for stable coins
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Currently, protocol allows swap between stable coins like USDT, DAI, tigUSD at rate 1-1 all the time. While in reality, value of these tokens are not stayed the same, even for USDT-USDC, USDC usually has higher value than USDT.

Serving as zero-slippage pool for stable coins will usually make depositors losing funds and left the pool with single token. Everyone will deposit low value token and withdraw high value one. And the result is honest users, who only deposit tokens to Vault without acknowledge this is usually the one suffering loss.

Also, it increased the chance of tigUSD got depegged because to be honest, it's a new token with lowest trust among these stable coins so most of the time, Vault will only contains tigUSD.

## Proof of Concept

Consider the scenario

There is a StableVault that support both USDC and DAI. Assume 1 DAI = 0.998 USDC
1. Alice deposit 1000 DAI to Stable Vault.
2. Alice withdraw 1000 USDC from Stable Vault.  
3. Profit = 1000 USDC - 1000 DAI = 1000 - 0.998 * 1000 = 2 USDC

## Tools Used
Manual Review

## Recommended Mitigation Steps

Consider only allowing users withdraw the exact token they deposited
Protocol loss in case price is far away from stop loss price.
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Each position when opened on Tigris has an optional stop loss price. Whenever market price drop below stop loss price, the position can be closed automatically by bot. However, there is a flaw in term of what price position should be closed. In the current codebase, even when market price drops below stop loss price, the position is still closed at stop loss price.
```solidity
function _limitClose(
    uint _id,
    bool _tp,
    PriceData calldata _priceData,
    bytes calldata _signature
) external view returns(uint _limitPrice, address _tigAsset) {
    ...
    if (_tp) {
        ...
    } else {
        if (_trade.slPrice == 0) revert("7"); //LimitNotSet
        if (_trade.direction) {
            if (_trade.slPrice < _price) revert("6"); //LimitNotMet
        } else {
            if (_trade.slPrice > _price) revert("6"); //LimitNotMet
        }
        _limitPrice = _trade.slPrice; 
        // @audit High: Protocol loss in case price far from slPrice. 
        // e.g Long sl = 100, currentPrice = 90, should be close at 90
    }
}
```

If the market price is drop far away from stop loss price, protocol will result in loss. 

## Proof of Concept

Consider the scenario
1. Alice has a long position, she set `slPrice = 100`. 
2. When market price is drop to `90`, her position is still closed at `100` when it should be closed at `90`. So she got `100 - 90` in price diff and this profit is taken from protocol.

## Tools Used
Manual Review

## Recommended Mitigation Steps

Consider closing position at market price when reaching stop loss. In addition to that, adding check to make sure close price is not too far, for example: more than 1%
Wrong formula to calculate new price in function `addToPosition()` could be abused by users
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

In function `addToPosition()`, new margin amount is added to existing position. This margin asset is added to position immediately to increase position size. Since they are added at different times so they have different prices, and there is only one slot for price in Position metadata. So we have to take weighed average of previous price and current price as open price for the whole position.

In function `addToPosition()`, it did exactly what I've just described
```solidity
uint _newMargin = _trade.margin + _addMargin;
uint _newPrice = _trade.price*_trade.margin/_newMargin + _price*_addMargin/_newMargin;
```

However, it used margin amount as the weights in its formula, which is wrong because `margin` of position can be changed without changing the price when using functions `addMargin()` or `removeMargin()`. As you can see below, it only modify margin and leverage, but not the price.

```solidity
function addMargin(
    uint256 _id,
    address _marginAsset,
    address _stableVault,
    uint256 _addMargin,
    ERC20PermitData calldata _permitData,
    address _trader
)
    external
{
    ...
    unchecked {
        uint256 _newMargin = _trade.margin + _addMargin;
        uint256 _newLeverage = _trade.margin * _trade.leverage / _newMargin;
        if (_newLeverage < asset.minLeverage) revert("!lev");
        position.modifyMargin(_id, _newMargin, _newLeverage);
        emit MarginModified(_id, _newMargin, _newLeverage, true, _trader);
    }
}
```

The result is users can abused this wrong formula, calling `addMargin()` to increase margin value and call `addToPosition()` to manipulate the openning price of position.

## Proof of Concept

Consider the scenario
1. Alice mints new position when `price = 100` and using `margin = 50`. So now, Alice got a position with `{price: 100, margin: 50}`
2. Alice calls `addMargin()` with param `margin = 100` to previous position. Now, Alice's position is `{price: 100, margin: 150}`
3. Alice calls `addToPosition()` with param `price = 150` and `margin = 100`. Now, Alice's position has `newMargin = 250` and `newPrice` calculated as
```solidity
_newPrice = _trade.price*_trade.margin/_newMargin + _price*_addMargin/_newMargin;
_newPrice = 100*150/250 + 150*100/250 = 120
```

When the correct calculation should be only accounted for step 1 and step 3.
```solidity
_newPrice = 100*50/150 + 150*100/150 = 133.33
```

The wrong price is lower than expected. Imagine this is a long position, users can abused this and get profit without risk.


## Tools Used
Manual Review

## Recommended Mitigation Steps

Consider using position size as weights instead of margin value when calcuting new price.
Users can abused function `extendLock()` to get delayed undistributed reward instantly.
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Function `Lock.extendLock()` is used to reset the lock time and extend the period and token amount to an existing lock. But before extending the period and adding token amount, it did not call `bondNFT.distribute()` first. 

Function `bondNFT.distribute()` is used to calculate, sync the accumulated reward in case new epochs are happened.
```solidity
function distribute(
    address _tigAsset,
    uint _amount
) external {
    if (totalShares[_tigAsset] == 0 || !allowedAsset[_tigAsset]) return;
    IERC20(_tigAsset).transferFrom(_msgSender(), address(this), _amount);
    unchecked {
        uint aEpoch = block.timestamp / DAY;
        if (aEpoch > epoch[_tigAsset]) {
            for (uint i=epoch[_tigAsset]; i<aEpoch; i++) {
                epoch[_tigAsset] += 1;
                accRewardsPerShare[_tigAsset][i+1] = accRewardsPerShare[_tigAsset][i];
            }
        }
        accRewardsPerShare[_tigAsset][aEpoch] += _amount * 1e18 / totalShares[_tigAsset];
    }
    emit Distribution(_tigAsset, _amount);
}
```

It did not call `bondNFT.distribute()` which means current epoch is not synced yet. In addition to that, in function `bondNFT.extendLock()`, it used current epoch value to assign `bondPaid`. The result is `bondPaid[]` used value from previous epoch to calculate, so users can get all reward from last epoch to latest one immediately. 
```solidity
function extendLock(
    uint _id,
    address _asset,
    uint _amount,
    uint _period,
    address _sender
) external onlyManager() {
    ...
    unchecked {
        ...
        bondPaid[_id][bond.asset] = accRewardsPerShare[bond.asset][epoch[bond.asset]] * _bond.shares / 1e18;
    }
    emit ExtendLock(_period, _amount, _sender,  _id);
}
```

## Proof of Concept

Consider the scenario
1. Alice opened a lock with just `1 token wei` amount long time ago.
2. At the moment, assuming current date is `1234` which means current epoch should be 1234 but `epoch[]` is just synced to `1000`.
3. Alice call `extendLock()` with `amount = 1 million`, value of `bondPaid[]` for her position is calculated using `accRewardsPerShare[]` of epoch `1000`. 
4. Alice call `claim()`, in function `claim()`, it did the distribution in BondNFT, so now epoch is synced to `1234`. So she get reward for epoch from `1000` to `1234` for free.

## Tools Used
Manual Review

## Recommended Mitigation Steps

Consider calling `claimGovFees()` in function `extendLock()` first, just like others functions `lock()`, `claim()`,...
Wrong amount deposited by users in `addToPosition()` function
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

Function `addToPosition()` is used to add margin and position size to existing position. The amount of new margin will be added is from `uint _addMargin` input param. However, when handling depositing, the actual margin amount transferred in is smaller than expected, it deducted `_fee`, which it should not.

As the result, Trading contract is not received enough margin but still increase position size for users, impact is loss of funds for protocol.


## Proof of Concept

In function `addToDeposit()`, it called `_handleDeposit()` with amount is `_addMargin - _fee`.

```solidity!
function addToPosition(
    uint _id,
    uint _addMargin,
    PriceData calldata _priceData,
    bytes calldata _signature,
    address _stableVault,
    address _marginAsset,
    ERC20PermitData calldata _permitData,
    address _trader
)
    external
{
    ...
    uint _fee = _handleOpenFees(_trade.asset, _addMargin*_trade.leverage/1e18, _trader, _trade.tigAsset, false);
    _handleDeposit(
        _trade.tigAsset,
        _marginAsset,
        _addMargin - _fee, // @audit High: should not deduct fee in depositing
        _stableVault,
        _permitData,
        _trader
    );
    ...
}
```

Check out similar call in function `initiateMarketOrder()`, which is not deducting fee.
```solidity
function initiateMarketOrder(
    TradeInfo calldata _tradeInfo,
    PriceData calldata _priceData,
    bytes calldata _signature,
    ERC20PermitData calldata _permitData,
    address _trader
)
    external
{
    ...
    uint256 _positionSize = _marginAfterFees * _tradeInfo.leverage / 1e18;
    _handleDeposit(_tigAsset, _tradeInfo.marginAsset, _tradeInfo.margin, _tradeInfo.stableVault, _permitData, _trader);
    ...
}
```

## Tools Used
Manual Review

## Recommended Mitigation Steps

Should not deduct fee when calling to function `_handleDeposit()` in function `addToDeposit()`
TradingExtension.sol: Correct price value is not used in `_limitClose` function.
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/TradingExtension.sol#L88-L120


# Vulnerability details

## Impact
All the price related calculation will be incorrect inside the function `_limitClose`.

## Proof of Concept

        getVerifiedPrice(_trade.asset, _priceData, _signature, 0);

        uint256 _price = _priceData.price;


        if (_trade.orderType != 0) revert("4"); //IsLimit


        if (_tp) {
            if (_trade.tpPrice == 0) revert("7"); //LimitNotSet
            if (_trade.direction) {
                if (_trade.tpPrice > _price) revert("6"); //LimitNotMet
            } else {
                if (_trade.tpPrice < _price) revert("6"); //LimitNotMet
            }
            _limitPrice = _trade.tpPrice;
        } else {
            if (_trade.slPrice == 0) revert("7"); //LimitNotSet
            if (_trade.direction) {
                if (_trade.slPrice < _price) revert("6"); //LimitNotMet
            } else {
                if (_trade.slPrice > _price) revert("6"); //LimitNotMet
            }
            _limitPrice = _trade.slPrice;
        }

In above code snippet, getVerifiedPrice will return the price value.

The returned price value is not used, instead of it, the _priceData.price is used which is incorrect.

Here is the link for getVerifiedPrice function - https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/TradingExtension.sol#L163-L188

## Tools Used

Manual review

## Recommended Mitigation Steps

use the price value that is returned from the function `getVerifiedPrice` which is correct value.
Incorrect Assumption of Stablecoin Market Stability
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L39-L51
https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L60-L72


# Vulnerability details

## Impact

The `StableVault` contract attempts to group all types of stablecoins under a single token which can be minted for any of the stablecoins supported by the system as well as burned for any of them.

This is at minimum a medium-severity vulnerability as the balance sheet of the `StableVault` will consist of multiple assets which do not have a one-to-one exchange ratio between them as can be observed by trading pools such as [Curve](https://curve.fi/#/ethereum/pools/3pool/deposit) as well as the [Chainlink oracle reported prices themselves](https://data.chain.link/ethereum/mainnet/stablecoins/usdc-usd).

Given that the contract exposes a 0% slippage 1-to-1 exchange between assets that in reality have varying prices, the balance sheet of the contract can be arbitraged (especially by flash-loans) to swap an undesirable asset (i.e. USDC which at the time of submission was valued at `0.99994853` USD) for a more desirable asset (i.e. USDT which at the time of submission was valued at `1.00000000` USD) acquiring an arbitrage in the price by selling the traded asset. 

## Proof of Concept

To illustrate the issue, simply view the exchange output you would get for swapping your USDC to USDT in a stablecoin pool (i.e. CurveFi) and then proceed to [invoke `deposit`](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L39-L51) with your USDC asset and retrieve your [incorrectly calculated `USDT` equivalent via `withdraw`](https://github.com/code-423n4/2022-12-tigris/blob/main/contracts/StableVault.sol#L60-L72).

The arbitrage can be observed by assessing the difference in the trade outputs and can be capitalized by selling our newly acquired `USDT` for `USDC` on the stablecoin pair we assessed earlier, ultimately ending up with a greater amount of `USDC` than we started with. This type of attack can be extrapolated by utilizing a flash-loan rather than our personal funds. 

## Tools Used

Manual review of the codebase, [Chainlink oracle resources](https://data.chain.link/popular), [Curve Finance pools](https://curve.fi/#/ethereum/pools).

## Recommended Mitigation Steps

We advise the `StableVault` to utilize Chainlink oracles for evaluating the inflow of assets instead, ensuring that all inflows and outflows of stablecoins are fairly evaluated based on their "neutral" USD price rather than their subjective on-chain price or equality assumption.