Platform: Code4rena
Start Date: 05/07/2023
Pot Size: $390,000 USDC
Total HM: 136
Participants: 132
Period: about 1 month
Judge: LSDan
Total Solo HM: 56
Id: 261
League: ETH
Rank: 32/132
Findings: 6
Award: $1,887.10
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: unsafesol
Also found by: 0xRobocop, 0xnev, peakbolt, rvierdiiev
339.2691 USDC - $339.27
https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/bigBang/BigBang.sol#L721-L739 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLendingCommon.sol#L83-L98
In _liquidateUser()/_orderBookLiquidation()
, the internal _repay()
function is never called, hence indicating that liquidator USDO balance is not burnt to repay borrower's CDP positions.
While it is true they only receive liquidation incentive based on current exchange rates, it essentially means that the liquidation mechanism is free of charge, since liquidator's do not risk their own CDP positions.
Depending on collateral type, this can cause the Tapioca's BigBang/Singularity markets to incur huge losses in the form of paying profits for liquidator's for free.
Manual Analysis
Call the internal _repay()
function when liquidator's liquidate borrower's CDP position under maximum LTV ratio via liquidate()
function. This forces liquidators to burn their EUSD (signifying loss)
Context
#0 - c4-pre-sort
2023-08-07T07:49:18Z
minhquanym marked the issue as primary issue
#1 - 0xRektora
2023-08-18T17:28:47Z
Out of scope. We mentioned that LiquidationQueue
will not be audited.
#2 - c4-sponsor
2023-08-18T17:29:07Z
0xRektora marked the issue as sponsor disputed
#3 - c4-judge
2023-09-20T16:25:31Z
dmvt marked the issue as unsatisfactory: Out of scope
#4 - nevillehuang
2023-10-02T19:39:51Z
While _orderBookLiquidation()
is out of scope, _liquidateUser()
is. Noticed that this could be a duplicate of #1355.
#5 - dmvt
2023-10-05T14:25:38Z
Agreed. Thanks for raising the issue!
#6 - c4-judge
2023-10-05T14:25:54Z
dmvt removed the grade
#7 - c4-judge
2023-10-05T14:26:16Z
dmvt marked the issue as duplicate of #1355
#8 - c4-judge
2023-10-09T21:29:01Z
dmvt marked the issue as satisfactory
🌟 Selected for report: Sathish9098
Also found by: 0xSmartContract, 0xnev, Udsen, jasonxiale, rvierdiiev, tsvetanovv
58.8874 USDC - $58.89
https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/bigBang/BigBang.sol#L604-L606 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLiquidation.sol#L350 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLeverage.sol#L21 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLeverage.sol#L58 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/bigBang/BigBang.sol#L336 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/bigBang/BigBang.sol#L384 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLeverage.sol#L147 https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLLeverage.sol#L97
Functions lacking both slippage and deadline checks
BigBang._liquidateUser()
: LinkSingularity._liquidateUser()
: LinkSGLLeverage.multiHopBuyCollateral()
: LinkSGLLeverage.multiHopSellCollateral()
: LinkFunctions lacking only deadline checks:
BigBang.buyCollateral()
: LinkBigBang.sellCollateral()
: LinkSGLLeverage.buyCollateral()
: LinkSGLLeverage.sellCollateral()
: LinkThe BigBang/Singularity._liquidateUser()
is called by _closedLiquidation()
which is in turn called by liquidate()
when liquidating CDP positions greater than maximum LTV ratios. One of the inputs is collateralToAssetSwapData
calldata which allows user to input necessary swap information such as slippage.
During the liquidation process, while the data is successfully decoded, it is never checked against minimumAmount swapped out when swapping collateral to USDO, and thus liquidator slippage tolerance is never checked against amount of USDO swapped and transferred back to BigBang/Singularity.sol
contract, similar to that performed in buyCollateral()/sellCollateral()
.
This issue also exist in LiquidationQueue.executeBids()
which is called by SGLLiquidation._orderBookLiquidation()
, where swapData
is passed in as input but never decoded and checked.
In addition, all functions that involves a swap does not allow for the pass in of a deadline check for transaction expiration. This could mean being exposed to sandwich attacks due to being exposed to undesired slippage, and ultimately lead to lower liquidation incentives transferred to liquidators.
Manual Analysis
Ensure slippage check is performed in all functions involving a swap
and consider adding an additional deadline check to prevent MEV/sandwich attacks.
Context
#0 - c4-pre-sort
2023-08-05T12:47:09Z
minhquanym marked the issue as duplicate of #1513
#1 - c4-judge
2023-09-29T21:47:37Z
dmvt marked the issue as satisfactory
🌟 Selected for report: 0xnev
1008.3444 USDC - $1,008.34
In SGLCommon._getInterestRate()
, the amount of fees that protocol accrue may be much lower than expected due to not converting feeAmount
back to Asset
amount.
feeAmount
is in terms of USDO amount, and is multiplied by totalAsset.base / fullAssetAmount
to essentially convert it back to base amount, that is the total asset to pay to protocol's fee receiver address.
However, this feeAmount
should be multiplied by base borrowed positions represented by _totalBorrow.base
instead of _totalAsset.base
. This could potentially overestimate/underestimate protocol fees paid to protocol via Singularity.withdrawFeesEarned()
since totalAssets used as collateral for lending USDO could be higher/lower than borrowed amount of USDO by lenders due to LTV ratio requirements.
function _getInterestRate() internal view returns ( ISingularity.AccrueInfo memory _accrueInfo, Rebase memory _totalBorrow, Rebase memory _totalAsset, uint256 extraAmount, uint256 feeFraction, uint256 utilization, bool logStartingInterest ) { ... ... extraAmount = (uint256(_totalBorrow.elastic) * _accrueInfo.interestPerSecond * elapsedTime) / 1e18; _totalBorrow.elastic += uint128(extraAmount); uint256 feeAmount = (extraAmount * protocolFee) / FEE_PRECISION; // % of interest paid goes to fee /// @audit feeAmount should be multiplied by _totalBorrow.base -> feeFraction = (feeAmount * _totalAsset.base) / fullAssetAmount; _accrueInfo.feesEarnedFraction += uint128(feeFraction); _totalAsset.base = _totalAsset.base + uint128(feeFraction); ... ...
Manual Analysis
function _getInterestRate() internal view returns ( ISingularity.AccrueInfo memory _accrueInfo, Rebase memory _totalBorrow, Rebase memory _totalAsset, uint256 extraAmount, uint256 feeFraction, uint256 utilization, bool logStartingInterest ) { ... ... extraAmount = (uint256(_totalBorrow.elastic) * _accrueInfo.interestPerSecond * elapsedTime) / 1e18; _totalBorrow.elastic += uint128(extraAmount); uint256 feeAmount = (extraAmount * protocolFee) / FEE_PRECISION; // % of interest paid goes to fee /// @audit feeAmount should be multiplied by _totalBorrow.base -- feeFraction = (feeAmount * _totalAsset.base) / fullAssetAmount; ++ feeFraction = (feeAmount * _totalBorrow_.base) / fullAssetAmount; _accrueInfo.feesEarnedFraction += uint128(feeFraction); _totalAsset.base = _totalAsset.base + uint128(feeFraction); ... ...
Context
#0 - c4-pre-sort
2023-08-07T07:53:36Z
minhquanym marked the issue as low quality report
#1 - c4-pre-sort
2023-08-07T07:53:57Z
minhquanym marked the issue as remove high or low quality report
#2 - c4-pre-sort
2023-08-07T07:54:02Z
minhquanym marked the issue as primary issue
#3 - c4-sponsor
2023-08-22T14:29:22Z
0xRektora marked the issue as sponsor disputed
#4 - c4-sponsor
2023-08-22T14:38:44Z
0xRektora marked the issue as sponsor confirmed
#5 - c4-judge
2023-09-30T12:37:59Z
dmvt marked the issue as selected for report