Open sherlock-admin2 opened 7 months ago
1 comment(s) were left on this issue during the judging contest.
takarez commented:
valid: whole lot of lessons taught here; high(3)
Wow, this is a detailed explanation. We did find this issue during the audit and glad that you have found it as well!
I believe this type of scenario is also fixed in the PR for #186: https://github.com/dhedge/flatcoin-v1/pull/266/commits/3a95a5b932fb9dcd770afd589751ecfd151360a8
It appears that this issue #186 and #192 are covering the same ground.
The protocol team fixed this issue in PR/commit https://github.com/dhedge/flatcoin-v1/pull/266.
The Lead Senior Watson signed off on the fix.
xiaoming90
high
Incorrect handling of PnL during liquidation
Summary
The incorrect handling of PnL during liquidation led to an error in the protocol's accounting mechanism, which might result in various issues, such as the loss of assets and the stable collateral total being inflated.
Vulnerability Detail
First Example
Assume a long position with the following state:
Let the current
StableCollateralTotal
be $x$ andmarginDepositedTotal
be $y$ at the start of the liquidation.Firstly, the
settleFundingFees()
function will be executed at the start of the liquidation process. The effect of thesettleFundingFees()
function is shown below. The long trader'smarginDepositedTotal
will be reduced by 100, while the LP'sstableCollateralTotal
will increase by 100.Since the position's settle margin is below the liquidation margin, the position will be liquidated.
At Line 109, the condition
(settledMargin > 0)
will be evaluated asTrue
. At Line 123:The
liquidationFee
will be to +20 at Line 127 below. This basically means that all the remaining margin of 20 will be given to the liquidator, and there should be no remaining margin for the LPs.At Line 133 below, the
vault.updateStableCollateralTotal
function will be executed:When
vault.updateStableCollateralTotal
is set to-100
,stableCollateralTotal
is equal to $x$.https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/LiquidationModule.sol#L85
Next, the
vault.updateGlobalPositionData
function here will be executed.The final
newMarginDepositedTotal
is $y + 80$ andstableCollateralTotal
is $x -100$, which is incorrect. In this scenariostableCollateralTotal
. The correct finalstableCollateralTotal
should be $x$.newMarginDepositedTotal
is $y + 80$, which is incorrect as this indicates that the long trader's pool has gained 80 ETH, which should not be the case when a long position is being liquidated.Second Example
The current price of rETH is \$1000.
Let's say there is a user A (Alice) who makes a deposit of 5 rETH as collateral for LP.
Let's say another user, Bob (B), comes up, deposits 2 rETH as a margin, and creates a position with a size of 5 rETH, basically creating a perfectly hedged market. Since this is a perfectly hedged market, the accrued funding fee will be zero for the context of this example.
Total collateral in the system = 5 rETH + 2 rETH = 7 rETH
After some time, the price of rETH drop to \$500. As a result, Bob's position is liquidated as its settled margin is less than zero.
$$ settleMargin = 2\ rETH + \frac{5 \times (500 - 1000)}{500} = 2\ rETH - 5\ rETH = -3\ rETH $$
During the liquidation, the following code is executed to update the LP's stable collateral total:
LP's stable collateral total increased by 2 rETH.
Subsequently, the
updateGlobalPositionData
function will be executed.https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/LiquidationModule.sol#L159
Within the
updateGlobalPositionData
function, theprofitLossTotal
at Line 179 will be -5 rETH. This means that the long trader (Bob) has lost 5 rETH.At Line 205 below, the PnL of the long traders (-5 rETH) will be transferred to the LP's stable collateral total. In this case, the LPs gain 5 rETH.
Note that the LP's stable collateral total has been increased by 2 rETH earlier and now we are increasing it by 5 rETH again. Thus, the total gain by LPs is 7 rETH. If we add 7 rETH to the original stable collateral total, it will be 7 rETH + 5 rETH = 12 rETH. However, this is incorrect because we only have 7 rETH collateral within the system, as shown at the start.
https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/FlatcoinVault.sol#L173
Third Example
At $T0$, the marginDepositedTotal = 70 ETH, stableCollateralTotal = 100 ETH, vault's balance = 170 ETH
Position Size = 500 ETH
Leverage = (500 + 20) / 20 = 26x
Liquidation Fee = 50 ETH
Liquidation Margin = 60 ETH
Entry Price = \$1000 per ETH
At $T1$, the position's settled margin falls to 60 ETH (margin = +70, accrued fee = -5, PnL = -5) and is subjected to liquidation.
Firstly, the
settleFundingFees()
function will be executed at the start of the liquidation process. The effect of thesettleFundingFees()
function is shown below. The long trader'smarginDepositedTotal
will be reduced by 5, while the LP'sstableCollateralTotal
will increase by 5.Next, this part of the code will be executed to send a portion of the liquidated position's margin to the liquidator and LPs.
50 ETH will be sent to the liquidator and the remaining 10 ETH should goes to the LPs.
Next, the
vault.updateGlobalPositionData
function here will be executed.The reason why the profitLossTotal = -5 is because there is only one (1) position in the system. So, this loss actually comes from the loss of Bob's position.
The
newMarginDepositedTotal = 0
is correct. This is because the system only has 1 position, which is Bob's position; once the position is liquidated, there should be no margin deposited left in the system.However,
stableCollateralTotal = 125
is incorrect. Because the vault's collateral balance now is 170 - 50 (send to liquidator) = 120. Thus, the tracked balance and actual collateral balance are not in sync.Impact
The following is a list of potential impacts of this issue:
Code Snippet
https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/LiquidationModule.sol#L85
Tool used
Manual Review
Recommendation
To remediate the issue, the
profitLossTotal
should be excluded within theupdateGlobalPositionData
function during liquidation.The existing
updateGlobalPositionData
function still needs to be used for other functions besides liquidation. As such, consider creating a separate new function (e.g., updateGlobalPositionDataDuringLiquidation) solely for use during the liquidation that includes the above fixes.The following attempts to apply the above fix to the three (3) examples described in the report to verify that it is working as intended.
First Example
Let the current
StableCollateralTotal
be $x$ andmarginDepositedTotal
be $y$ at the start of the liquidation.StableCollateralTotal = $x$ + 100
marginDepositedTotal = $y$ - 100
StableCollateralTotal = ($x$ + 100) - 100 = $x$
marginDelta = -(position.marginDeposited + positionSummary.accruedFunding) = -(20 + (-100)) = 80
newMarginDepositedTotal = marginDepositedTotal + marginDelta = ($y$ - 100) + 80 = ($y$ - 20)
No change to StableCollateralTotal here. Remain at $x$
1) The LPs should not gain or lose in this scenario. Thus, the fact that the StableCollateralTotal remains as $x$ before and after the liquidation is correct. 2) The
marginDepositedTotal
is ($y$ - 20) is correct because the liquidated position's remaining margin is 20 ETH. Thus, when this position is liquidated, 20 ETH should be deducted from themarginDepositedTotal
3) No revert during the execution.Second Example
StableCollateralTotal = 5 + 2 = 7 ETH
marginDelta = -(position.marginDeposited + positionSummary.accruedFunding) = -(2 + 0) = -2
marginDepositedTotal = marginDepositedTotal + marginDelta = 2 + (-2) = 0
StableCollateralTotal = 7 ETH, marginDepositedTotal = 0 (Total 7 ETH tracked in the system)
Balance of collateral in the system = 7 ETH. Thus, both values are in sync. No revert.
Third Example
marginDepositedTotal = 70 + (-5) = 65
StableCollateralTotal = 100 + 5 = 105
50 ETH sent to the liquidator from the system: Balance of collateral in the system = 170 ETH - 50 ETH = 120 ETH
StableCollateralTotal = 120 ETH
marginDelta= -(position.marginDeposited + positionSummary.accruedFunding) = -(70 + (-5)) = -65
marginDepositedTotal = 65 + (-65) = 0
StableCollateralTotal = 120 ETH, marginDepositedTotal = 0 (Total 120 ETH tracked in the system)
Balance of collateral in the system = 120 ETH. Thus, both values are in sync. No revert.