Attacker deposits a large collateral, and creates an order with large size delta. Later, it fills.
Then, the attacker creates second order, and later it fills.
The second order will increase the mark price, so the old position will gain large unrealized profit. This profit will be added to the collateral balance, and makes the margin balance larger.
Now that the margin balance is larger (thanks to the unrealized profit), it allows creating another order again.
The attacker repeats this, until reaching to the maximum allowed open interest.
Now the attacker has a huge collateral balance and position size.
Now the attacker's account is liquidatable.
After it is liquidated, huge amount of fund will be remained as collateral in the attacker's account.
Attacker can now easily withdraw them.
https://github.com/Cyfrin/2024-07-zaros/blob/main/src/perpetuals/branches/SettlementBranch.sol#L435
https://github.com/Cyfrin/2024-07-zaros/blob/main/src/perpetuals/leaves/TradingAccount.sol#L200
The following scenario is implemented in the PoC. It can be verified easily by running the PoC.
Attacker deposits 1_000_000 USDz as collateral in ETH market where its index price is 1000$.
Attack creates a long position order with size 92_000.
This order will be filled at mark price 1004.6$.
The attacker creates another long position order with size 60_000. It is expected that it reverts because very simply the final position size would be 152_000 each with minimum price of 1000$. So, the final position value would be 152_000 * 1000 = 152M
, and its required inital margin would be 0.01 * 152M = 1.52M
, while the collateral worths only 1M, which is less than the initial required margin.
But the protocol is implemented differently, and this order will be filled successfully. Because, when the new order 60_000 is created, the mark price will increase to 1012.2 (this is because the skew increases). Then, the unrealized profit associated to the old position will be added to the collateral which is equal to 92_000 * (1012.2 - 1004.6) = 699_200$
, so the collateral would be equal to 1_000_000 + 699_200 = 1.699M
by ignoring the fees for simplicity (the PoC demonstrates the real case). It can be seen that since collateral is higher than initial required margin 1.52M
, it allows the order to be created and later filled.
Now, the attacker has a long position with size 152_000, while the original collateral was 1M.
The attacker repeats this scenario again. I.e. he again creates another long position order with size 60_000, and it later be filled. He repeats this until it reaches to the maxOpenInterest
(maximum profit of the attacker is when reaching to this limit, that is why some iterations are done. Without any iteration, the attacker would steal less fund). The PoC shows that 15 iterations are enough to reach to this limit. The following table shows each step in real case (including fees).
The table shows that after the final order being filled, the marginCollateralBalance
is equal to 46_394_200
. For sure this account is liquidatable because the requiredMaintenanceMarginUsdX18
is equal to 992k * 1049.6 * 0.005 = 5_206_016
(where 1049.6 is the mark price when closing the entire position). While the marginBalanceUsdX18
is just equal to 46_394_200 - (1096.2 - 1049.6) * 992k = 167_000
.
When this account is liquidated, the requiredMaintenanceMarginUsdX18
will be deducted from marginCollateralBalance
, so the remaining would be 46_394_200 - 5_206_016 - liquidation fee = 41_188_179
. Moreover, the position would be entirely closed.
Now, the attack can withdraw the remaining marginCollateralBalance
which is equal to 41_188_179
. So, the attacker just paid 1M, but he could steal almost 41M, equal to 40M profit.
Note that after the second iteration, the account is liquidatable. If after the second iteration, it is liquidated, the attacker would steal 1_368_555$
, equal to 368_555$
profit.
The following test shows what explained above.
Draining the protocol.
This scenario should be investigated from different point of views:
Unrealized profit is added to the collateral balance. This collateral balance plays the role for validating the margin requirements. One possible solution is to separate the unrealized profit from the collateral when validating the margin requirements.
The protocol should not allow a user to increase a position when it is liquidatable (it should be stopped at least in iteration 2). There is some checks for such cases, but it is bypassed because the unrealized profit is added to the collateral and increased the margin balance.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.