The protocol uses a liquidations priority list to loop over the collateral types and use that for operations about settlement and liquidations in deducting margin from the user balance, an incorrect assumption will enable the loop to go on without reverting and allow the code to make a wrong implementation to occur and disrupt the operation of the protocol
SettlementBranch
and LiquidationBranch
both call TradingAccount::deductAccountMargin
whenever they want to perform an operation like removing collateral or paying for fees, the main vulnerability exists in the deductAccountMargin
function so we will explore it
TradingAccount::deductMargin
L510 - L516
Inside the function we see that the liquidationPriority is loop over to get the collateral type to deduct from
TradingAccount::deductMargin
L529
This prevents the loop from going on if the collateral balance is finished, but it is a critically wrong assumption because here the code assumes that the collateral balance will only be zero after the end of the loop, this is critically wrong as would be shown with further explanation below
TradingAccount::deductAccountMargin
L534 - L560
In this Snippet there is a peculiar situation to take a look at these two block of code tries to remove tokens using withdrawMarginUsd, this function withdraws the given amount of margin and transfers them to the recipient, but it also levrages another function withdraw
when doing it, this function deducts the margin to be removed from the collateral balance, two snippets would be shown to explain what exactly is going on, for both functions
withdrawMarginUsd
withdraw
Now when viewing both together with the blocks of code in the deductAccountMargin
we can now get to the root of the issue,
When deductAccountMargin
is called it loops over collateral type to remove collateral, and remove fees and pnl as shown in the blocks of code, there are three deductions happening settlementfees, orderfees and pnl
Before this three block execute a check for the zero balance of the collateralType is made
in the first block which is settlement fees, when it makes a call to the withdrawMarginUsd
it enters one of the if/else block and a call to withdraw
is made
when that call is made inside the withdraw function, this is where the issue occurs, inside the function is subtracts the collateral to be removed from the balance and makes a check to see if the balance is zero, it removes it from the user list of collateral if it is, in our example this is what happens the amount to be removed will be equal to the balance of the collateral type which will lead to its removal
now inside the current loop of the deductAccountMargin
the second block, order fee will be executed with the same collateralType that has already being removed from the user enumerable set !
This becomes an issue inside the withdrawMarginUsd
where it checks the balance of the removed collateralType, which would be zero and tries to withdraw
again
Inside withdraw the fact that the collaterType has been removed does not affect the execution has Open zepplin enumerable set does not revert, if a key does not exist, it will return false instead which means this if statement will execute
Then it will Incorrectly subtract the amount from the totalDeposited for the collateralType as shown
inside the else block the execution will still not revert even though the collateral balance is zero which will return back to the deductAccountMargin loop where it will do this again for the pnl and incorrectly subtract the amount from the totalCollateral type deposited amount, before it moves on to the next loop and starts the process to get all fees deducted again, and if the other collateral has the same issue as the first one described in this report, it will also subtract from collateralType total deposited wrongly, it will do this until it finds the collateral that will pay for all the amount to deduct, but the damage to the marginCollateralConfiguration.totalDeposited
would have been extensive.
SInce the marginCollateralConfiguration.totalDeposited
is a crritcal parameter used to limit the total amount of a collateralType deposited to the protocol as shown below
This will allow over the cap deposit and will also Distort the whole accounting of the protocol, impacting it functionality
Manual Review
There were many times during the tracing of the call stack that presented an opportunity for a malicious user to prevent liquidations but a revert could just not happen when the collateral balance is zero, and because no reverts, that attack was mitigated, but that also lead to this issue of updating the total deposited collateral balance, a check should be implemented in the withdraw
function to prevent an update when the balance is zero.
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.