xanoutas/solidity-security-findings huggingface dataset:
Wrong address input in BondNFT.createLock results in wrong _owner being saved in memory
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Wrong address input in createLock results in function failure of extendLock and claim. 

## Proof of Concept

When someone calls lock in Lock.sol, the function BondNFT.createLock is called .

        bondNFT.createLock( _asset, _amount, _period, msg.sender);

bondNFT.createLock takes in 4 input parameters, _asset, _amount, _period and msg.sender.

    function createLock(
        address _asset,
        uint _amount,
        uint _period,
        address _owner
    ) external onlyManager() returns(uint id) {

When creating the lock and bond, the address is incorrectly written as address(0).

            Bond memory _bond = Bond(
                id,             // id
                address(0),     // owner

In extendLock, bond.owner == _sender is checked. If bond.owner is address(0) and _sender is the address extending the bond (not address(0)), then extendLock cannot function.

        require(bond.owner == _sender, "!owner");

Similarly, for claim, if _claimer is not bond.owner, then claim cannot be processed.

## Tools Used

Manual Review

## Recommended Mitigation Steps

The owner should not be address(0) but msg.sender instead. In this case, since msg.sender is passed to createLock, the input parameter _owner should be used instead

            Bond memory _bond = Bond(
                id,             // id
                _owner,     // owner
Transfering GovNFT does not update mapping userDebt
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Because of not updating the mapping `userDebt` for receiver (`userDebt[for]`) on the transfer of GovNFT token, the receiver may be harmed, because of not getting collected fees.

## Proof of Concept
Function `_transfer(...)` is not updated for `userDebt[for]`, [here](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/GovNFT.sol#L86-L102).

## Tools Used
Manual review.

## Recommended Mitigation Steps
Consider updating value in `userDebt[for]` on transfer.
Functions of Trading contract can be reentered by Position.sol#mint
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Both the contracts of Position and Trading may not work correctly.

## Proof of Concept
The [Position.sol#mint](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L148) calls `_safeMint` will trigger a `_checkOnERC721Received` callback, which can be used to reenter.

Crackers can use this vulnerability to attack the protocol.

For example, the cracker could send a reentrant attack tx with a call stack like this:
1. cracker calls his own contract Cx.
2. in Cx: calls `Trading.sol#initiateMarketOrder`
3. in Trading: calls `Position.sol#mint`
4. in Position: calls `Cx#_checkOnERC721Received`
5. in Cx: calls `Trading.sol#liquidatePosition` (reenter here)
6. in Trading: calls `Position.sol#burn`
7. in Cx: calls `Cx#initiateLimitOrder`
8. in Trading: calls `Position.sol#mint`
9. in Position: calls `Cx#_checkOnERC721Received`

In step 6, it burned the Position NFT that created in step 3, and the state will be updated in a confusing way because the burnt NFT hasn't done state initialization in step 3 yet.
In step 8, it created another Position NFT with the same tokenId as the one in step 3, which is also an anomaly.
After step 9, when the stack returns to step 3, the burnt NFT will now go on the state initialization.

## Tools Used
Manual

## Recommended Mitigation Steps
Simplest solution: replace [`_safeMint`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/Position.sol#L148)  with `_mint`.
Lack of Input Validation
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableToken.sol#L13-L23


# Vulnerability details

## Impact
Without proper input validation, it is possible for attackers to pass malicious input to the contract, potentially causing unintended behavior or even allowing the attacker to exploit the contract.
## Proof of Concept
an attacker could pass a negative value as the 'amount' parameter to the 'burnFrom' function, which could allow them to destroy more tokens than they are supposed to be able to. Similarly, an attacker could pass an invalid address as the 'account' parameter, which could cause the function to behave unexpectedly.

## Tools Used
Myrthil

## Recommended Mitigation Steps
add input validation to the contract to ensure that the inputs are within the expected range and conform to the required format. For example, you could add a check to ensure that the 'amount' parameter is greater than zero and that the 'account' parameter is a valid address.
Minter privilege escalation vulnerability
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableToken.sol#L38-L54


# Vulnerability details

## Impact
The contract owner could potentially abuse their privileges to manipulate the token supply and undermine the integrity of the token economy.

## Proof of Concept
The 'setMinter' function in the contract allows the contract owner to set the minter status for any address, meaning that the owner could potentially grant themselves minter privileges and bypass the 'onlyMinter' modifier. This could allow the owner to mint and burn tokens without being restricted by the 'onlyMinter' modifier, potentially leading to inflation or deflation of the token supply
## Tools Used
mythril Manticore

## Recommended Mitigation Steps
Add an additional check to the setMinter function to ensure that the contract owner cannot set their own minter status.
Implement an emergency stop mechanism that can halt the contract in the event of a crisis or emergency.
Perform regular security audits to identify and fix potential vulnerabilities.
Wrong update of stoploss in `TradingExtension._limitClose()`
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`TradingExtension._limitClose()` returns a wrong stoploss which is favorable for users and it would be a significant loss for the protocol.

## Proof of Concept
`TradingExtension._limitClose()` is used to set takeprofit/stoploss prices for the pending order and execute them.

```solidity
File: 2022-12-tigris\contracts\TradingExtension.sol
088:     function _limitClose(
089:         uint _id,
090:         bool _tp,
091:         PriceData calldata _priceData,
092:         bytes calldata _signature
093:     ) external view returns(uint _limitPrice, address _tigAsset) {
    ...
111:         } else {
112:             if (_trade.slPrice == 0) revert("7"); //LimitNotSet
113:             if (_trade.direction) {
114:                 if (_trade.slPrice < _price) revert("6"); //LimitNotMet
115:             } else {
116:                 if (_trade.slPrice > _price) revert("6"); //LimitNotMet
117:             }
118:             _limitPrice = _trade.slPrice; //@audit should be _price
119:         }
120:     }
```

It always returns a preset takeprofit or stoploss after checking prices and stoploss price shouldn't be set like that.

For example, a user opens a long position at 100 and sets stoploss = 90 and if the price drops to 80 rapidly, the position will be closed automatically at 80(not 90 the stoploss price).

I've been a trader for several years and it's normal for many trading platforms.

But in the [documentation](https://docs.tigris.trade/protocol/trading-and-fees#limitations), it says `Stop order prices are not guaranteed.` which is absolutely correct but no mention about `stoploss` which isn't guaranteed as well.

Totally, the current implementation of stoploss is profitable for users as they can close orders at predetermined stoploss without any slippage.

It means the protocol would suffer some loss because of slippage instead of traders.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Recommend using a `not guaranteed stoploss` like normal trading platforms.

```
    function _limitClose(
        uint _id,
        bool _tp,
        PriceData calldata _priceData,
        bytes calldata _signature
    ) external view returns(uint _limitPrice, address _tigAsset) {
       ...
        } 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 = _price; //++++++++++++++++++++++++++++
        }
    }
```
Wrong position size calculation in `TradingLibrary.pnl()`
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/utils/TradingLibrary.sol#L48
https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L552
https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L624


# Vulnerability details

## Impact
Users will pay less closing fees than they should when they have a profitable short position.

Also, they will pay more fees when they have a lost short position.

## Proof of Concept
`TradingLibrary.pnl()` calculates the new position size like below.

```solidity
    function pnl(bool _direction, uint _currentPrice, uint _price, uint _margin, uint _leverage, int256 accInterest) external pure returns (uint256 _positionSize, int256 _payout) {
        unchecked {
            uint _initPositionSize = _margin * _leverage / 1e18;
            if (_direction && _currentPrice >= _price) {
                _payout = int256(_margin) + int256(_initPositionSize * (1e18 * _currentPrice / _price - 1e18)/1e18) + accInterest;
            } else if (_direction && _currentPrice < _price) {
                _payout = int256(_margin) - int256(_initPositionSize * (1e18 - 1e18 * _currentPrice / _price)/1e18) + accInterest;
            } else if (!_direction && _currentPrice <= _price) {
                _payout = int256(_margin) + int256(_initPositionSize * (1e18 - 1e18 * _currentPrice / _price)/1e18) + accInterest;
            } else {
                _payout = int256(_margin) - int256(_initPositionSize * (1e18 * _currentPrice / _price - 1e18)/1e18) + accInterest;
            }
            _positionSize = _initPositionSize * _currentPrice / _price; //@audit should check direction
        }
    }
```

And the new position size is used in [liquidatePosition()](https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L552) and [_closePosition()](https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L624) for closing fee calculation.

The purpose of this logic is to take more fees when the order is profitable using a larger position size.

But with the short position, the position is profitable when the current price is less than the opening price.

So if users have a profitable short position, [_positionSize](https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/utils/TradingLibrary.sol#L48) will be less than `_initPositionSize` and users will pay less fee for the profitable position.

Similarly, they will pay more closing fees when they have a lost short position.

## Tools Used
Manual Review

## Recommended Mitigation Steps
`TradingLibrary.pnl()` should be modified like the below.

```solidity
    function pnl(bool _direction, uint _currentPrice, uint _price, uint _margin, uint _leverage, int256 accI
Users can bypass the `maxWinPercent` limit using a partially closing
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L625-L627


# Vulnerability details

## Impact
Users can bypass the `maxWinPercent` limit using a partial closing.

As a result, users can receive more funds than their upper limit from the protocol.

## Proof of Concept
As we can see from the [documentation](https://docs.tigris.trade/protocol/trading-and-fees#limitations), there is limitation of a maximum PnL.

```
Maximum PnL is +500%. The trade won't be closed unless the user sets a Take Profit order or closes the position manually.
```

And this logic was implemented like below in `_closePosition()`.

```solidity
File: 2022-12-tigris\contracts\Trading.sol
624:                 _toMint = _handleCloseFees(_trade.asset, uint256(_payout)*_percent/DIVISION_CONSTANT, _trade.tigAsset, _positionSize*_percent/DIVISION_CONSTANT, _trade.trader, _isBot);
625:                 if (maxWinPercent > 0 && _toMint > _trade.margin*maxWinPercent/DIVISION_CONSTANT) { //@audit bypass limit
626:                     _toMint = _trade.margin*maxWinPercent/DIVISION_CONSTANT;
627:                 }
```

But it checks the `maxWinPercent` between the partial payout and full margin so the below scenario is possible.

1. Alice opened an order of margin = 100 and PnL = 1000 after taking closing fees.
2. If `maxWinPercent` = 500%, Alice should receive 500 at most.
3. But Alice closed 50% of the position and she got 500 for a 50% margin because it checks `maxWinPercent` with `_toMint = 500` and `_trade.margin = 100`
4. After she closed 50% of the position, the remaining margin = 50 and PnL = 500 so she can continue step 3 again and again.
5. As a result, she can withdraw almost 100% of the initial PnL(1000) even though she should receive at most 500.

## Tools Used
Manual Review

## Recommended Mitigation Steps
We should check the `maxWinPercent` between the partial payout and partial margin like below.

```solidity
    _toMint = _handleCloseFees(_trade.asset, uint256(_payout)*_percent/DIVISION_CONSTANT, _trade.tigAsset, _positionSize*_percent/DIVISION_CONSTANT, _trade.trader, _isBot);

    uint256 partialMarginToClose = _trade.margin * _percent / DIVISION_CONSTANT; //+++++++++++++++++++++++
    if (maxWinPercent > 0 && _toMint > partialMarginToClose*maxWinPercent/DIVISION_CONSTANT) { 
        _toMint = partialMarginToClose*maxWinPercent/DIVISION_CONSTANT;
    }
```
Wrong deposit amount in `Trading.addToPosition()`
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Users should deposit a full amount of `_addMargin` but it deducts `_fee` now.

As a result, users will pay less funds than they should.

## Proof of Concept
In `addToPosition()`, users deposits `_addMargin - _fee` after the fee calculation.

```solidity
File: 2022-12-tigris\contracts\Trading.sol
274:         uint _fee = _handleOpenFees(_trade.asset, _addMargin*_trade.leverage/1e18, _trader, _trade.tigAsset, false);
275:         _handleDeposit(
276:             _trade.tigAsset,
277:             _marginAsset,
278:             _addMargin - _fee, //@audit -fee?
279:             _stableVault,
280:             _permitData,
281:             _trader
282:         );
```

Actually, users should deposit `_addMargin` because several kinds of fees are paid in `_handleOpenFees()` already.

Other deposit logics like [initiateMarketOrder()](https://github.com/code-423n4/2022-12-tigris/blob/b2ebb8ea1def4927a747e7a185174892506540ab/contracts/Trading.sol#L180) were implemented correctly but it's wrong only in `addToPosition()`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
`_handleDeposit()` should use `_addMargin` instead of `_addMargin - _fee`.
LP Rewards can be increased infinitely by a malicious liquidity provider
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

LP Rewards can be increased infinitely by a malicious liquidity provider

## Proof of Concept

A Bond NFT holder can claim pending rewards from a bond using the function `Lock.claim()`.

```solidity
function claim(
    uint256 _id
) public returns (address) {
    claimGovFees();
    (uint _amount, address _tigAsset) = bondNFT.claim(_id, msg.sender);
    IERC20(_tigAsset).transfer(msg.sender, _amount);
    return _tigAsset;
}
```

This `claim()` function calls the `BondNFT.claim()` and if the bond is an expired one there is an additional process to reimburse the rewards for that expired bond.

So the protocol calculates how much rewards were allocated to that expired bond from the `bond.expireEpoch` to `epoch[bond.asset]` and reimburse that to the current `accRewardsPerShare`.

The problem is it calculates the `_pendingDelta` using the `bond.expireEpoch` and `epoch[bond.asset]` while this can be called multiple times.

```solidity
    function claim(
        uint _id,
        address _claimer
    ) public onlyManager() returns(uint amount, address tigAsset) {
        Bond memory bond = idToBond(_id);
        require(_claimer == bond.owner, "!owner");
        amount = bond.pending;
        tigAsset = bond.asset;
        unchecked {
            if (bond.expired) {
                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]);
                if (totalShares[bond.asset] > 0) {
                    accRewardsPerShare[bond.asset][epoch[bond.asset]] += _pendingDelta*1e18/totalShares[bond.asset];
                }
            }
            bondPaid[_id][bond.asset] += amount;
        }
        IERC20(tigAsset).transfer(manager, amount);
        emit ClaimFees(tigAsset, amount, _claimer, _id);
    }
```

As we can see, the `claim()` function does not do anything to release the expired bond or remember the time that this reimbursement happens.
So an expired bond holder can trigger this function by calling `Lock.claim()` repeatedly to increase the `accRewardsPerShare`.
This means the rewards for liquidity providers can be increased to infinite.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Release the bond if expired in the function `BondNFT.claim()` or remember the last time that the `_pendingDelta` was calculated to make sure the reimbursement does not happen more than once.
The function `StableVault.withdraw` has the risk of of stealing `_token` tokens
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## The function `StableVault.withdraw` has the risk of of stealing `_token` tokens
The function `StableVault.withdraw` does a swap from `tigAsset` to `_token`. Then it is expected to burn `tigAsset` in exchange for token `_token`. However the function itself doesn't check if the `_msgSender()`  has enough balance of `stable` tokens to burn. And the ERC20 Mintable implementation doesn't have the implementation for `burnFrom` as we can see, for example, OpenZeppelin's (OZ) implementation [`Here`](`https://github.com/OpenZeppelin/openzeppelin-contracts/blob/9b3710465583284b8c4c5d2245749246bb2e0094/contracts/token/ERC20/ERC20Mintable.sol`). The OpenZeppelin's (OZ) ERC20Burnable implementation, however, has the `burnFrom` which checks the balance of the account before burning the tokens and it reverse in case the balance is not enough. But since there is no way to garantee that calling `burnFrom` in the function `StableVault.withdraw` will revert in case there is not enough balance, any external user could keep withrawing tokens without burning the counterpart.

## File: StableVault.sol#L65
[`StableVault.sol#L65`](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L65)

```solidity
 function withdraw(address _token, uint256 _amount) external returns (uint256 _output) {
        IERC20Mintable(stable).burnFrom(_msgSender(), _amount);
        _output = _amount/10**(18-IERC20Mintable(_token).decimals());
        IERC20(_token).transfer(
            _msgSender(),
            _output
        );
    }
```

## Recommended Mitigation Steps
Ensure the `burnFrom` reverts in case of not enough balance of `stable` tokens to burn. The implementation of `burnFrom` would be the one of OZ `ERC20Burnable` instead of `ERC20Mintable` or any other implementation that sees fit. But the better solution would be to check the balance of `stable` to burn for `_msgSender()` on the function `StableVault.withdraw` itself and revert in case of not enogh balance.
StableVault will collapse when any listed stablecoins depeg
Audit: 2022-12-tigris-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

When any of the listed stablecoins depeg, the bad coins will drive out the good coins, leaving only bad coins in StableVault, resulting in the collapse of StableVault.

## Proof of Concept

Given:

StableVault listed three stablecoins: USDD, USDC, and USDT.

1. Alice deposited 1,000 USDC and got 1,000 tigUSD (stable) back.
2. USDD depegged to 0.8 USD.
3. Bob bought 1000 USDD with 800 USDC, then deposited to StableVault, getting 1,000 tigUSD.
4. Bob called a withdrawal burn of 1,000 tigUSD and got 1,000 USDC, earning 200 USDC.

Alice could not get any USDC back; she could only choose to withdraw USDD and bear the loss.

## Recommended Mitigation Steps

Each StableVault should only be allowed to wrap one `token` to its corresponding `tigAsset`.