Tigris Trade contest - Jeiwan's results

A multi-chain decentralized leveraged exchange featuring instant settlement and guaranteed price execution on 30+ pairs.

General Information

Platform: Code4rena

Start Date: 09/12/2022

Pot Size: $90,500 USDC

Total HM: 35

Participants: 84

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 12

Id: 192

League: ETH

Tigris Trade

Findings Distribution

Researcher Performance

Rank: 29/84

Findings: 3

Award: $442.95

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: minhtrng

Also found by: 0Kage, Aymen0909, HollaDieWaldfee, Jeiwan, KingNFT, bin2chen, hansfriese, rvierdiiev

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-659

Awards

201.2303 USDC - $201.23

External Links

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;
  2. full margin is transferred from the trader (fees are not subtracted): Trading.sol#L180;
  3. accounted margin excludes fess: 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). This function, however, subtracts fees from margin before transferring margin from traders:

_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

Consider not subtracting fees from _addMargin in addToPosition when calling _handleDeposit.

#0 - c4-judge

2022-12-20T16:16:33Z

GalloDaSballo marked the issue as duplicate of #659

#1 - c4-judge

2023-01-18T13:58:43Z

GalloDaSballo changed the severity to 3 (High Risk)

#2 - c4-judge

2023-01-22T17:43:20Z

GalloDaSballo marked the issue as satisfactory

Findings Information

🌟 Selected for report: 0xA5DF

Also found by: HollaDieWaldfee, Jeiwan, KingNFT

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
duplicate-576

Awards

230.0301 USDC - $230.03

External Links

Lines of code

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

Vulnerability details

Impact

The open interest trackers in the PairsContract will accumulate non-removable residual due to fees not being excluded from open interest values. The residual will grow over time.

Proof of Concept

Open interest is the total leveraged value deposited by traders to the protocol. It excludes fees since fees are subtracted from traders' positions and sent to a treasury. For example, when opening a market order, open interest is changed by the position size, (margin - fees) * leverage (Trading.sol#L198-L200):

uint256 _marginAfterFees = _tradeInfo.margin - _handleOpenFees(_tradeInfo.asset, _tradeInfo.margin*_tradeInfo.leverage/1e18, _trader, _tigAsset, false);
uint256 _positionSize = _marginAfterFees * _tradeInfo.leverage / 1e18;
...
if (_tradeInfo.direction) {
    tradingExtension.modifyLongOi(_tradeInfo.asset, _tigAsset, true, _positionSize);
} else {
    tradingExtension.modifyShortOi(_tradeInfo.asset, _tigAsset, true, _positionSize);
}

For limit orders, however, accounting is different. Fees are not applied when a limit order is created because limit orders are not active until they're executed (Trading.sol#L330-L346):

// @audit-info full margin is deposited
_handleDeposit(_tigAsset, _tradeInfo.marginAsset, _tradeInfo.margin, _tradeInfo.stableVault, _permitData, _trader);
...
// @audit-info full margin is accounted
position.mint(
    IPosition.MintTrade(
        _trader,
        _tradeInfo.margin,
        _tradeInfo.leverage,
        _tradeInfo.asset,
        _tradeInfo.direction,
        _price,
        _tradeInfo.tpPrice,
        _tradeInfo.slPrice,
        _orderType,
        _tigAsset
    )
);

However, when a limit order is executed, the change in open interest doesn't account for fees (Trading.sol#L513-L517):

// @audit-info fees are calculated on the full position size, this is correct
uint _fee = _handleOpenFees(trade.asset, trade.margin*trade.leverage/1e18, trade.trader, trade.tigAsset, true);
...
if (trade.direction) {
    // @audit wrong: fees are not subtracted from the position size
    tradingExtension.modifyLongOi(trade.asset, trade.tigAsset, true, trade.margin*trade.leverage/1e18);
} else {
    // @audit wrong: fees are not subtracted from the position size
    tradingExtension.modifyShortOi(trade.asset, trade.tigAsset, true, trade.margin*trade.leverage/1e18);
}
...
// @audit-info accounted margin excludes fees, correct
position.executeLimitOrder(_id, trade.price, trade.margin - _fee);

As a result, open interest will be increased by an amount that is bigger than the actual value deposited to the protocol. However, when positions are closed or liquidated, open interest is reduced by values that include fees (TradingExtension.sol#L70-L74, Trading.sol#L546-L550). This will create a residual in open interest that will accumulate over time and may, in the worst case, cause a denial of service when new positions are opened: the modifyLongOi and modifyShortOi revert when maximal open interest is reached (PairsContract.sol#L157, PairsContract.sol#L177).

Tools Used

Manual review

Consider excluding fees from the position size before updating open interest in the executeLimitOrder function.

#0 - c4-sponsor

2023-01-05T22:55:34Z

TriHaz marked the issue as sponsor confirmed

#1 - c4-judge

2023-01-15T16:21:28Z

GalloDaSballo marked the issue as duplicate of #576

#2 - c4-judge

2023-01-22T17:54:17Z

GalloDaSballo marked the issue as satisfactory

Awards

11.6941 USDC - $11.69

Labels

bug
2 (Med Risk)
satisfactory
duplicate-655

External Links

Lines of code

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

Vulnerability details

Impact

The deprecated interface doesn't provide additional data that can make integration with Chainlink more reliable. This can potentially lead the protocol to be more sensitive to disruptions or non-standard behavior of Chainlink oracles.

Proof of Concept

The verifyPrice function of the TradingLibrary library uses a deprecated Chainlink interface (TradingLibrary.sol#L113):

int256 assetChainlinkPriceInt = IPrice(_chainlinkFeed).latestAnswer();

As per the Chainlink documentation, the latestAnswer function is deprecated.

This interface doesn't allow to check the timestamp of when the latest price was reported and ensure that the price is not stale. Also, the Chainlink data feed aggregator provides minimal and maximal price values per each supported token to improve validation of prices returned by oracles. Using the old interface may result in the protocol not detecting stale or wrong prices reported by Chainlink feeds.

Tools Used

Manual review

Consider using the latestRoundData function instead of latestAnswer and consider following the Monitoring Data Feeds recommendations by Chainlink.

#0 - c4-judge

2022-12-20T16:34:47Z

GalloDaSballo marked the issue as duplicate of #655

#1 - c4-judge

2023-01-22T17:30:43Z

GalloDaSballo marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter