Long-position--reserved collateral cannot be rebased
Description
An important change between the base GMX V1 contracts and this fork is that for long positions, the collateral token no longer has to be the index token:
function _validateTokens(address _collateralToken, address _indexToken, bool _isLong) private view {
if (_isLong) {
// _validate(_collateralToken == _indexToken, 42);
_validate(whitelistedTokens[_collateralToken], 43);
_validate(!stableTokens[_collateralToken], 44);
_validate(!stableTokens[_indexToken], 47);
return;
}
_validate(whitelistedTokens[_collateralToken], 45);
_validate(stableTokens[_collateralToken], 46);
_validate(!stableTokens[_indexToken], 47);
_validate(shortableTokens[_indexToken], 48);
}
This change implements the ability to trade price feeds that do not correspond to on-chain assets by having virtual index tokens that refer to price feeds.
However, in the unchanged increasePosition
code, there is an important reserve requirement:
function increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external override nonReentrant {
// [...]
_validateTokens(_collateralToken, _indexToken, _isLong);
// [...]
// reserve tokens to pay profits on the position
uint256 reserveDelta = usdToTokenMax(_collateralToken, _sizeDelta);
position.reserveAmount = position.reserveAmount.add(reserveDelta);
_increaseReservedAmount(_collateralToken, reserveDelta);
In the above code, _sizeDelta
is the leveraged size of the position in USD. The function usdToTokenMax
converts this, rounding up for slippage, into a quantity of the collateral token, which is then reserved. This guarantees that, as long as the reserved assets always equal or exceed the actual assets held by the protocol, the protocol will always be solvent. This is because, for both long and short positions, this reserved amount is the maximum profit that could be sent to the trader at position close.
On the other hand, if the collateral token is not necessarily equal to the index token for long positions, then this reserve amount may be exceeded. Since there is no mechanism to rebase the collateral reserved by an open position or force the closure of positions that are too profitable, this may allow individual positions to permanently expose the contract to an unlimited amount of risk.
Impact
In the base GMX V1 contracts, the maximum profit of a position is reserved when it is opened. For example, if the trader opens a 5x leveraged long ETH position with 1 ETH as collateral, when the position is opened, 5 ETH will be reserved. Then, later, no matter what the price of ETH is, the max payout to the trader is the entire 5 ETH — as the price approaches infinity, the payout asymptotically approaches 5 ETH.
However, since the change to _validateTokens
decouples the collateral and index assets, now the reserved quantity is different. In the same example, if the trader opens a 5x leveraged long ABC position with 1 ETH as collateral, at open, 5 ETH will still be reserved. But, if ABC doubles in price while ETH remains flat, the value of the position has increased, whereas the value of the collateral remains the same.
Whenever the economic risk of a position is probabilistic, instead of guaranteed, the risk parameters should be able to be rebased on newer market values, and liquidation should be possible if the position falls out of the range of risk that the protocol is willing to take on. However, since GMX V1 fully guarantees the payout for longs, it does not expect payouts to be larger than the reserved assets, and so there is no mechanism to rebase a position for future prices, or liquidate a long position due to it becoming too profitable. So, long positions can easily become dangerously toxic over time, and, even if the protocol as a whole becomes at risk of being insolvent due to this, there would still be no mechanism to close these positions.
Recommendations
One remediation strategy is to only allow short positions on the underlying contract. Long positions can be implemented at the user interface level, shown with a defined user-facing maximum price-feed value and be implemented as a short of an index whose value is the maximum minus the indexed quantity. This is not user-friendly, though, because a lower maximum is not user-friendly, and a higher maximum would lock up more collateral per unit of position size.
Another remediation strategy is to fork a different base platform. For example, GMX V2 explicitly supports synthetic markets with decoupled position and collateral assets, with an auto-deleveraging feature that probabilistically mitigates this risk.
Remediation
This issue has been acknowledged by Plume Network, as stated by the client, an Automatic De-leveraging system fix will be implemented off-chain with the following scope:
Track total reserves of the system
Track total long position exposure
Check if exposure is within a threshold of the reserves (e.g. exposure is 90% of reserves)
Reduce profitable positions sizes if the threshold has been reached
This new component falls out of the scope of this audit and has not been reviewed by Zellic.