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
Rank: 13/84
Findings: 5
Award: $1,643.49
π Selected for report: 0
π Solo Findings: 0
π Selected for report: hansfriese
766.7669 USDC - $766.77
user can have a profit greater than maxWinPercent
_closePosition() will limit the user's profit to no greater than maxWinPercent But there is a problem with the check here: When the user is not completely closed (_percent < DIVISION_CONSTANT,example:50%) The error is still compared to 100% of the maximum profit: _payout <= (_trade.marginmaxWinPercent/DIVISION_CONSTANT) It should be multiplied by the percentage of the maximum profit to be normal as: _payout <= _trade.margin_percent*maxWinPercent/DIVISION_CONSTANT/DIVISION_CONSTANT
example: margin = 100 maxWinPercent = 500% The current profit is 800 which is closed twice (50% each time):
First close 50 : payout = 800 * 50% = 400 (it's ok because 400 < 100 * 5) Second closing of the remaining 50: payout = 400 * 100% = 400, since 400 > 50 * 5 (current maxWinPercent), only 250 can be taken Finally the total profit of 400 + 250 = 650 is greater than maxWinPercent(100 * 5 = 500)
function _closePosition( uint _id, uint _percent, uint _price, address _stableVault, address _outputToken, bool _isBot ) internal { ... if (_payout > 0) { unchecked { _toMint = _handleCloseFees(_trade.asset, uint256(_payout)*_percent/DIVISION_CONSTANT, _trade.tigAsset, _positionSize*_percent/DIVISION_CONSTANT, _trade.trader, _isBot); if (maxWinPercent > 0 && _toMint > _trade.margin*maxWinPercent/DIVISION_CONSTANT) { //****** need * _percent***// _toMint = _trade.margin*maxWinPercent/DIVISION_CONSTANT; //****** need * _percent***// } ...
function _closePosition( uint _id, uint _percent, uint _price, address _stableVault, address _outputToken, bool _isBot ) internal { ... unchecked { _toMint = _handleCloseFees(_trade.asset, uint256(_payout)*_percent/DIVISION_CONSTANT, _trade.tigAsset, _positionSize*_percent/DIVISION_CONSTANT, _trade.trader, _isBot); - if (maxWinPercent > 0 && _toMint > _trade.margin*maxWinPercent/DIVISION_CONSTANT) { - _toMint = _trade.margin*maxWinPercent/DIVISION_CONSTANT; + if (maxWinPercent > 0 && _toMint > _trade.margin*_percent*maxWinPercent/DIVISION_CONSTANT/DIVISION_CONSTANT) { + _toMint = _trade.margin**_percent*maxWinPercent/DIVISION_CONSTANT/DIVISION_CONSTANT; } }
#0 - TriHaz
2023-01-03T19:12:14Z
Valid but duplicate, already confirmed by #507.
#1 - c4-sponsor
2023-01-03T19:12:26Z
TriHaz requested judge review
#2 - c4-judge
2023-01-22T10:07:50Z
GalloDaSballo marked the issue as duplicate of #507
#3 - c4-judge
2023-01-23T09:09:27Z
GalloDaSballo changed the severity to 3 (High Risk)
#4 - c4-judge
2023-01-23T09:09:35Z
GalloDaSballo marked the issue as satisfactory
π Selected for report: minhtrng
Also found by: 0Kage, Aymen0909, HollaDieWaldfee, Jeiwan, KingNFT, bin2chen, hansfriese, rvierdiiev
201.2303 USDC - $201.23
addToPosition() user no pay the fees
In addToPosition(), call _handleDeposit() passes "_addMargin - _fee", which should be the full "_addMargin", not minus fee.
This way the user pays less
function addToPosition( uint _id, uint _addMargin, PriceData calldata _priceData, bytes calldata _signature, address _stableVault, address _marginAsset, ERC20PermitData calldata _permitData, address _trader ) external { ... _handleDeposit( _trade.tigAsset, _marginAsset, _addMargin - _fee, //****@audit pass "_addMargin - _fee" , should just pass _addMargin _stableVault, _permitData, _trader );
function addToPosition( uint _id, uint _addMargin, PriceData calldata _priceData, bytes calldata _signature, address _stableVault, address _marginAsset, ERC20PermitData calldata _permitData, address _trader ) external { ... _handleDeposit( _trade.tigAsset, _marginAsset, - _addMargin - _fee, + _addMargin, _stableVault, _permitData, _trader );
#0 - c4-judge
2022-12-20T16:16:36Z
GalloDaSballo marked the issue as duplicate of #659
#1 - c4-judge
2023-01-18T13:58:46Z
GalloDaSballo changed the severity to 3 (High Risk)
#2 - c4-judge
2023-01-22T17:43:22Z
GalloDaSballo marked the issue as satisfactory
π Selected for report: hansfriese
Also found by: bin2chen
567.9755 USDC - $567.98
in executeLimitOrder() :
The final price of LimtOrder is uncertain, although it is limited to a certain range (limitOrderPriceRange) but the final price is not guaranteed to meet the Stop loss price so it is still necessary to check
note:addToPosition() maybe has same problem, it will change price , Checking the slPrice again also feels reasonable
function executeLimitOrder( uint _id, PriceData calldata _priceData, bytes calldata _signature ) external { unchecked { _checkDelay(_id, true); tradingExtension._checkGas(); if (tradingExtension.paused()) revert TradingPaused(); require(block.timestamp >= limitDelay[_id]); IPosition.Trade memory trade = position.trades(_id); uint _fee = _handleOpenFees(trade.asset, trade.margin*trade.leverage/1e18, trade.trader, trade.tigAsset, true); (uint256 _price, uint256 _spread) = tradingExtension.getVerifiedPrice(trade.asset, _priceData, _signature, 0); if (trade.orderType == 0) revert("5"); if (_price > trade.price+trade.price*limitOrderPriceRange/DIVISION_CONSTANT || _price < trade.price-trade.price*limitOrderPriceRange/DIVISION_CONSTANT) revert("6"); //LimitNotMet if (trade.direction && trade.orderType == 1) { if (trade.price < _price) revert("6"); //LimitNotMet } else if (!trade.direction && trade.orderType == 1) { if (trade.price > _price) revert("6"); //LimitNotMet } else if (!trade.direction && trade.orderType == 2) { if (trade.price < _price) revert("6"); //LimitNotMet trade.price = _price; } else { if (trade.price > _price) revert("6"); //LimitNotMet trade.price = _price; } if(trade.direction) { trade.price += trade.price * _spread / DIVISION_CONSTANT; } else { trade.price -= trade.price * _spread / DIVISION_CONSTANT; } /******@audit without _checkSl() ****// if (trade.direction) { tradingExtension.modifyLongOi(trade.asset, trade.tigAsset, true, trade.margin*trade.leverage/1e18); } else { tradingExtension.modifyShortOi(trade.asset, trade.tigAsset, true, trade.margin*trade.leverage/1e18); } _updateFunding(trade.asset, trade.tigAsset); position.executeLimitOrder(_id, trade.price, trade.margin - _fee); emit LimitOrderExecuted(trade.asset, trade.direction, trade.price, trade.leverage, trade.margin - _fee, _id, trade.trader, _msgSender()); } }
add check
function executeLimitOrder( uint _id, PriceData calldata _priceData, bytes calldata _signature ) external { .... _updateFunding(trade.asset, trade.tigAsset); + _checkSl(_tradeInfo.slPrice, _tradeInfo.direction, _price); position.executeLimitOrder(_id, trade.price, trade.margin - _fee); emit LimitOrderExecuted(trade.asset, trade.direction, trade.price, trade.leverage, trade.margin - _fee, _id, trade.trader, _msgSender()); } }
#0 - GalloDaSballo
2022-12-20T01:39:02Z
Lacking in impact but seems to mostly be valid
#1 - GalloDaSballo
2022-12-22T00:55:35Z
After re-reading, ultimately the lack of check is self-evident
That said I think the Warden may have missed a bigger exploit as they didn't go through writing a poc.
But I must agree that the lack of check is worth flagging by itself
#2 - c4-judge
2022-12-22T00:56:14Z
GalloDaSballo marked the issue as duplicate of #512
#3 - c4-judge
2023-01-22T17:53:55Z
GalloDaSballo marked the issue as satisfactory
95.824 USDC - $95.82
_handleOpenFees() may not work
_handleOpenFees() will transfer the fees to the gov contract at the end, but calling gov.distribute() without token.approve(gov) first it will can't transfer the token
note: _handleCloseFees() has approve(gov,max) first, but in general _handleOpenFees will be called before _handleCloseFees() so it will be not work
function _handleOpenFees( uint _asset, uint _positionSize, address _trader, address _tigAsset, bool _isBot ) ... _positionSize * _fees.burnFees / DIVISION_CONSTANT, _referrer != address(0) ? _positionSize * _fees.referralFees / DIVISION_CONSTANT : 0, _positionSize * _fees.botFees / DIVISION_CONSTANT, _referrer ); IStable(_tigAsset).mintFor(address(this), _daoFeesPaid); } //****@audit without _tigAsset.approve() first ***// gov.distribute(_tigAsset, IStable(_tigAsset).balanceOf(address(this))); }
function _handleOpenFees( uint _asset, uint _positionSize, address _trader, address _tigAsset, bool _isBot ) ... _positionSize * _fees.burnFees / DIVISION_CONSTANT, _referrer != address(0) ? _positionSize * _fees.referralFees / DIVISION_CONSTANT : 0, _positionSize * _fees.botFees / DIVISION_CONSTANT, _referrer ); IStable(_tigAsset).mintFor(address(this), _daoFeesPaid); } + IStable(_tigAsset).approve(address(gov), type(uint).max); gov.distribute(_tigAsset, IStable(_tigAsset).balanceOf(address(this))); }
#0 - c4-judge
2022-12-21T15:12:34Z
GalloDaSballo marked the issue as duplicate of #324
#1 - c4-judge
2022-12-22T01:09:36Z
GalloDaSballo marked the issue as duplicate of #256
#2 - c4-judge
2022-12-22T01:11:43Z
GalloDaSballo marked the issue as duplicate of #649
#3 - c4-judge
2023-01-22T17:53:26Z
GalloDaSballo marked the issue as satisfactory
π Selected for report: rbserver
Also found by: 0x52, 0xDecorativePineapple, 0xdeadbeef0x, 8olidity, Jeiwan, Rolezn, __141345__, bin2chen, eierina, fs0c, gzeon, joestakey, koxuan, kwhuo68, ladboy233, rvierdiiev, yixxas
11.6941 USDC - $11.69
verifyPrice() don't work
According to Chainlinkβs documentation (Deprecated API Reference, Migration Instructions, and API Reference), the latestAnswer function is deprecated. This function does not throw an error if no answer has been reached, but instead returns 0, causing an incorrect price
https://docs.chain.link/data-feeds/price-feeds/api-reference#latestanswer
Recommend using the latestRoundData function to get the price instead. Also recommend adding checks on the return data with proper revert messages if the price is stale or the round is incomplete, for example:
(uint80 roundID, int256 price, , uint256 timeStamp, uint80 answeredInRound) = IPrice(_chainlinkFeed).latestRoundData(); require(answeredInRound >= roundID, "..."); require(timeStamp != 0, "...");
#0 - c4-judge
2022-12-20T16:34:51Z
GalloDaSballo marked the issue as duplicate of #655
#1 - c4-judge
2023-01-22T17:30:45Z
GalloDaSballo marked the issue as satisfactory