Closed sherlock-admin closed 8 months ago
1 comment(s) were left on this issue during the judging contest.
takarez commented:
invalid: i dont think the order can be executed in the same block due to the executability time and the block time on base is at max 3 sec i believe.
xiaoming90
high
Position can be immediately liquidated after opening
Summary
Position can be immediately liquidated after opening by timing the submission of the Pyth price, leading to a loss of assets for the victim (Long traders)
Vulnerability Detail
At $T2$, Keeper A executes open Bob's position with the existing on-chain Pyth price of \$1000. Note that the on-chain price on the Pyth contract is already outdated compared to the latest Pyth available off-chain. However, per the design, when the order is executed at $T2$, any price data with a timestamp between $T1$ and $T2$ is considered valid and can be used within the
executeOpen
function to execute an open order.In addition, when executing the open position, the protocol does not require the keeper to update the price as long as the current price is still valid (within the range of $T1$ and $T2$. Thus, this provides the keepers executing the orders an option to update the price or not.
The following shows that an acceptable oracle price that can be used when executing the order is between the current time (
block.timestamp
) and the order executable time (_executableAtTime
).In this scenario, Keeper A chooses not to update the Pyth price when executing Bob's open order at T2. Thus, the order is executed with the current (outdated - In Blue) price of \$1000 per ETH. Assume that Bob's position order is as follows:
Note: For simplicity's sake, assume that the market is perfectly hedged; thus, there is no accrued funding fee.
When executing Bob's open order, Keeper A could skip or avoid the updating of the Pyth price by passing in an empty
updateData
array. Inspecting the source code of Pyth on-chain contract, theoracleOracle.updatePriceFeeds
function will not perform any update (for-loop is not executed as length = 0).https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/DelayedOrder.sol#L386
https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/OracleModule.sol#L69
For this specific position, if the price of ETH drops from \$1000 to \$960, the PnL of the position will be as follows, and Bob's position will become liquidatable.
Since there is no minimum waiting or delay for executing liquidation on-chain, Keeper A could back-run its previous
executeOpen
TX with a liquidation TX that liquidates Bob's position. The liquidation TX will update the Pyth price to the new off-chain price (\$960 - In Red). When theliquidate
function attempts to compute Bob's position remaining margin with the updated price, it will return (2 ETH - 2.08 ETH = -0.08), which indicates the position is underwater and liquidatable.By doing so, Keeper A will not only gain the keeper fee from the first
executeOpen
TX, but also the keeper fee + liquidation fee from the second liquidation TX. Bob incurs a loss here as his position is closed immediately after opening, and he lost his margin, which is paid to the liquidator (as a liquidation fee) and the LPs (remaining margin).Sidenote: The oracle's
maxDiffPercent
check will not guard against this attack effectively. For instance, in the above example, if the Chainlink price is \$975 and themaxDiffPercent
is 5%, the Pyth price of \$960 or \$1000 still falls within the acceptable range. If themaxDiffPercent
is reduced to a smaller margin, it will potentially lead to a more serious issue where all the transactions get reverted when fetching the price, breaking the entire protocol.Impact
Loss of assets for the victim (Long traders) as shown in the scenario above.
Code Snippet
https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/DelayedOrder.sol#L386
https://github.com/sherlock-audit/2023-12-flatmoney/blob/main/flatcoin-v1/src/OracleModule.sol#L69
Tool used
Manual Review
Recommendation
Following are some of the measures that can be applied to mitigate the issue:
Duplicate of #194