Dolomite engaged Guardian to review the security of its GMX V2 module, allowing users to use GM tokens as collateral for borrowing on Dolomite. From the 1st of November to the 15th of November, a team of 4 auditors reviewed the source code in scope.
- Published
- Review window
- November 1 to 15, 2024
- Language
- Solidity
- Chains
- Arbitrum
- Sector
- Lending
- 2 Critical
- 4 High
- 13 Medium
- 15 Low
- 0 Informational
Scope
Test suiteGuardianAudits/DolomitePoCs
Overview
Dolomite engaged Guardian to review the security of its GMX V2 module, allowing users to use GM tokens as collateral for borrowing on Dolomite. From the 1st of November to the 15th of November, a team of 4 auditors reviewed the source code in scope.
Issues Detected Throughout the engagement 6 High/Critical issues were uncovered and remediated by the Dolomite team. During the fix review, 3 Critical issues were uncovered in the code changes.
Security Recommendation Given the number of High and Critical issues detected, Guardian supports a thorough internal review and independent security review of the protocol at a finalized frozen commit.
Findings 34
-
GLOBAL-1 Critical DoS Callbacks Through Simple Transfer DoS Resolved
Description
Proof of concept: PoC
When the callback
afterWithdrawalExecutionis triggered, it is checked that the amount of GM tokens sent to GMX to be redeemed match the amount of GM actually redeemed: Require.that(withdrawalInfo.inputAmount == _withdrawal.numbers.marketTokenAmount)GMX records how much GM needs to be withdrawn by comparing the balance of GM before and after the function
createWithdrawalis called. An attacker can easily cause a mismatch betweenwithdrawalInfo.inputAmountandmarketTokenAmountby sending 1 wei of GM to the Withdrawal Vault before an unwrapping is initiated.The short or long token will be stuck in the Unwrapper Trader, the vault will remain frozen prohibiting any user execution, and the Core protocol will still believe the user holds the GM collateral which no longer truly exists. This will affect any user’s normal withdrawal as well as liquidations.
This attack is applicable to the
afterDepositCancellationcallback as well due to the following validation:assert(_deposit.numbers.initialLongTokenAmount == 0 ||_deposit.numbers.initialShortTokenAmount == 0);An attacker can send 1 wei to the Deposit Vault prior to the call of thecreateDepositfunction, causing a revert at this assertion.Recommendation
Do not use a strict equality. Modify the validations such that the amounts of the event data are expected to be greater than or equal to the amounts Dolomite expects from its system. Furthermore, if two tokens are received from GMX upon cancellation, either deposit the unintended token into Dolomite if supported or send it to the Vault owner.
Resolution
Dolomite Team: The issue was resolved in commit 90b5b77.
-
ITVF-1 Critical Liquidations Prevented With Pending Action DoS Resolved
Description
Proof of concept: PoC
When performing a liquidation, the amount the user is to be liquidated for is validated with function
_validateWithdrawalAmountForUnwrapping:Require.that(balance - (withdrawalPendingAmount + depositPendingAmount) > 0);A user can simply initiate a withdrawal for their entire balance, but specify a
minOutputAmountof long/short token that is impossible to achieve with the provided amount of GM to withdraw. If GMX were to execute their withdrawal, the user would listen to the cancellation event, and reinitiate another unwrapping with the same parameters.By doing so, the user prevents the Liquidator from passing any withdrawal amount greater than 0, as
balance = withdrawalPendingAmountandbalance - (withdrawalPendingAmount +depositPendingAmount) == 0which fails the above validation.Furthermore, an attacker could actually perform this attack through self-liquidation, as a
minOutputAmountis passed to theprepareForLiquidationfunction as well.Recommendation
When a user attempts to initiate a deposit or withdrawal, verify whether the account is liquidatable. If so, prevent the action from being initiated. An edge case exists such that a user may becoming liquidatable when they already having a pending deposit/withdrawal, but this solution will prevent the continuous, malicious use of pending deposits/withdrawals to prevent liquidation.
Furthermore, consider restricting the
minOutputAmounta liquidator can pass to prevent liquidation delay through self-liquidation.Resolution
Dolomite Team: The issue was resolved in commits 843181e and bf66738.
Guardian: Ensure the
minOutputAmounton_params.extraDatais validated with function_checkMinAmountIsNotTooLarge. Furthermore, do not allow liquidatable users to initiate a wrapping and clearly document this behavior.Additionally, validate that
_params.extraDatais 32 bytes long to prevent a malicious actor from padding the bytes and causing an OOG error when copying it into memory. 11 -
GMXL-1 High Withdrawals Apply _minOutputAmount To One Side Logical Error Resolved
Description
In the
executeInitiateUnwrappingfunction thewithdrawalParamsare created with a_minOutputAmountthat can only apply to either theshortTokenoutput or thelongTokenoutput.However withdrawals in GMX remove a split of the
shortTokenandlongToken. The resultingshortTokenandlongTokenare then subjected to thelongTokenSwapPathandshortTokenSwapPath, the outputs of which are validated against theminLongTokenAmountandminShortTokenAmountrespectively.Therefore even though the outputs are swapped to the same token, take for example the short token, the short token that is directly removed from GMX will be validated against the entire
_minOutputAmountand the short token that is received from the portion that was removed as thelongTokenand swapped to theshortTokenwill be validated against aminShortTokenAmountof 0.This results in the withdrawal likely failing the minimum output validation as the portion of short token that is directly removed from GMX is unlikely to solely pass the
_minOutputAmountvalidation. Additionally, there can be no minimum output that applies to the portion of funds that are swapped which is exactly the portion that ought to be validated.Recommendation
Refactor the
_minOutputAmountlogic such that the minimum output can be split amongst theminLongTokenAmountandminShortTokenAmount.Resolution
Dolomite Team: The issue was resolved in commit 5963990.
-
GMXL-2 High Redemptions Incorrectly Appear Unpaused Logical Error Resolved
Description
The function
isExternalRedemptionPausedis used to determine if GM withdrawals are currently paused, utilizing the current PnL-to-Pool Factors as one validation for a paused state.The validation only compares against the
maxPnlForAdlandmaxPnlForWithdrawalswithisLong =true, although the threshold factor may be different withisLong = false. This is incorrect as theshortPnlToPoolFactorshould be compared against theMAX_PNL_FACTOR_FOR_WITHDRAWALSforisLong = falseas inMarketUtils.validateMaxPnlFurthermore, the condition verifies that the
shortPnlToPoolFactoror thelongPnlToPoolFactorshould not exceed themaxPnlForWithdrawals, as GMX will revert on withdrawal execution when the threshold is passed. However, the check also looks at themaxPnlForAdl, which does not impact withdrawal execution on GMX.As a result, redemptions may appear possible when the PnL-to-Pool Factor exceeds both the
maxPnlForWithdrawalsandmaxPnlForAdl, although that is not the case and the withdrawal will fail.Additionally, GMX can disable withdrawal creation, execution, or a market entirely which should also be included to verify that redemptions are paused. Because the market appears unpaused, users can modify their collateralization when liquidations aren’t possible, or zap into more of the irredeemable GM tokens across Dolomite.
Recommendation
Fetch the
maxPnlForWithdrawalswith bothisLong = trueandisLong = falseFor both long and short thresholds, update the validation to only check whether theshortPnlToPoolFactororlongPnlToPoolFactorexceeds themaxPnlForWithdrawals:bool isShortPnlTooLarge = shortPnlToPoolFactor > int256(maxPnlForWithdrawalsShort); bool isLongPnlTooLarge = longPnlToPoolFactor > int256(maxPnlForWithdrawalsLong);In addition, verify that the market is enabled and withdrawal features are enabled through the Datastore.
Resolution
Dolomite Team: The issue was resolved in commit 11b095a.
-
AIMUTI-1 High Severely Undercollateralized Positions Cannot Be Liquidated Logical Error Resolved
Description
Proof of concept: PoC
During liquidation, an issue appears when the total available user amount to liquidate is equal to the input liquidation amount. The input liquidation amount is passed to the
liquidatefunction from theLiquidatorProxyV4WithGenericTradercontract when liquidating a position.Liquidation always creates a second set of call and sell actions. When the amounts are equal, the second call and sell action will be executed with 0 as input. This results in execution failing because the first set of actions clears the withdrawal position.
It is also worth mentioning that the account is not vaporizable in this state since it still holds a positive balance in the GM market.
Recommendation
In the
createActionsForUnwrappingfunction fromAsyncIsolationModeUnwrapperTraderImpldo not create a second call and sell action if the difference between the input amount and available amount is zero.If the mentioned solution is implemented, 2 dummy actions that do not have any side-effects should be created as a workaround. This is needed to maintain compatibility with the liquidation proxy, which at this point already creates an action array using the length 4 for the liquidation unwrapping.
A different solution can be modifying the
LiquidatorProxyV4WithGenericTraderto determine if this would be a 2 or 4 step liquidation.Resolution
Dolomite Team: The issue was resolved in commit f434e31.
-
AIMUTI-2 High Withdrawal Keys Misused by Differing Subaccount in Liquidations DoS Resolved
Description
Proof of concept: PoC
When a liquidation is executed on an account from a vault with multiple accounts, a malicious actor can pass the withdrawal key belonging to an account that is different from the one being liquidated and block the vault.
Consider the scenario where a vault has accounts A and B:
By liquidating account A using account B’s key, account B’s withdrawal information is cleared. If account B has a withdrawal that needs to be retried, the execution will fail as the stored withdrawal information is empty.
Even if account B becomes liquidatable and uses account A’s key to perform the liquidation, that will fail because
DolomiteMarginwould interpret that as a borrow increase in an unborrowable market. This happens when account A’s withdrawal is for a larger amount that account B’s.The vault remains frozen and all operations from any sub-accounts are blocked. Such a hijacked liquidation can occur when both withdrawals have the same output token and are both retryable. The withdrawal can be retryable from either an on-going liquidation or from a failed withdrawal of a healthy position.
Recommendation
In the
_callFunctionfunction, verify that the storedaccountNumbermatches the_accountInfo.numberbut only if the call action was sent from a liquidation operation.When sent from a liquidation operation, the
_accountInfo.numbervariable holds the liquidatable account but when sent from a normal unwrapping it is the ZAP account number.Resolution
Dolomite Team: The issue was resolved in commit f434e31.
-
VAULT-1 Medium Vault Owner Can Cancel Liquidation Access Control Resolved
Description
In the
cancelWithdrawalfunction, the vault owner can cancel liquidations which is essentially a withdrawal of the GM tokens. When a liquidator usesprepareForLiquidation, they trigger a forced withdrawal from the underwater vault.The issue arises as the
cancelWithdrawalfunction doesn't distinguish between user-initiated withdrawals and forced withdrawals (like liquidations). This allows a user to repeatedly cancel withdrawal attempts, causing a loss of funds for both the protocol (insolvency) and the liquidator as only the first liquidation execution fee is covered by the user.Recommendation
Differentiate between normal withdrawals and those from liquidation. Restrict the vault owner from calling the
cancelWithdrawalfunction when there is a pending withdrawal initiated by theprepareForLiquidationfunction.Resolution
Dolomite Team: The issue was resolved in commit 20b002e.
-
GMXO-1 Medium Withdrawal Not Necessarily 50-50 Logical Error Resolved
Description
When calculating the swap price impact, it is assumed that withdrawing GM tokens provides 50% in the short token and the other 50% in long tokens. According to documentation, "Assume under the worst case, we liquidate 10% of the supply cap (which would entail a swap for half of that, 5%, to USDC (short token))."
However, that is not necessarily the case because long and short tokens are withdrawn with their value relative to the total pool value e.g. if the total pool value is $100 and $80 is from the long token, 80% of the withdrawn value will be in long tokens.
This assumption leads to an inaccuracy in the resulting price impact calculation, affecting the calculated price of the GM token in the
_getGmTokenPriceAfterPriceImpactfunction.Recommendation
Consider using the reader to get the current token ratios in the market and adjust the
wethAmountInaccordingly.Resolution
Dolomite Team: We have decided to no longer measure price impact but increase the liquidation penalty instead.
-
UAIWT-1 Medium Excess GM Not Partially Deposited On Supply Cap Logical Error Resolved
Description
After a deposit is created, any extra GM tokens that are received are then deposited into Dolomite Margin for the user. If this amount would equal or surpass the maximum allowed value for a market, then it is entirely sent to the vault owner.
This is done in the
_depositIntoDefaultPositionAndClearDepositfunction from theUpgradeableAsyncIsolationModeWrapperTradercontract.The issue is that if the maximum is exceeded, then the entire excess is wrongly sent to the the vault owner, instead of only the difference that causes the
maxWeito be exceeded. The user may temporarily miss out on borrowing power as even a slight excess over cap leads to transferring the whole amount to the vault owner.Recommendation
Modify the
_depositIntoDefaultPositionAndClearDepositfunction so that it sends only the excess that would not fit into the market to the vault owner and deposit the rest into Dolomite in the user’s account.Resolution
Dolomite Team: The issue was resolved in commit a490adb.
-
GMXUT-1 Medium Can't Unfreeze Vault If Execution Interrupted DoS Resolved
Description
During the unwrapping process, the vault is frozen by incrementing the mapping
_vaultToPendingAmountWeiMapby_amountDeltaWei.value.Once unwrapping concludes, the
_vaultToPendingAmountWeiMapfunction is reduced by_amountDeltaWei.value, effectively unfreezing the vault.However, the unwrapping process may fail, as acknowledged in the
afterWithdrawalExecutionfunction:// @audit: If GMX changes the keys OR if the data sent back is malformed (causing the above requires to // fail), this will fail. This will result in us receiving tokens from GMX and not knowing who they // are for, nor the amount. The only solution will be to upgrade this contract and have an admin // "unstuck" the funds for users by sending them to the appropriate vaults.
In the event of a failure in the
afterWithdrawalExecutionfunction, the protocol can recover the funds but is unable to unfreeze the vault. Consequently, the user remains unable to utilize their vault, including unwrapping any remaining funds.Recommendation
Enable the Admin to invoke the
setVaultAccountPendingAmountForFrozenStatusfunction, providing a means to unfreeze an account if execution is ever interrupted.Resolution
Dolomite Team: The issue was resolved in commit cfd0a09.
-
GLOBAL-2 Medium Lack Of Liquidation Incentives Incentives Resolved
Description
According to the Dolomite whitepaper, “Liquidations forcefully repay any debt that is owed by a borrower by transferring an equivalent amount of collateral from the borrower to the liquidator, plus a liquidation penalty of 5%.”
The penalty is used as a reward for performing the liquidation and maintaining protocol solvency. However, the integration lacks a reward for liquidations in the modules, leaving no incentive for a user to trigger the
prepareForLiquidationfunction if the unwrapping and swap into the Core protocol succeeds.Recommendation
Consider providing the liquidator a reward in the output token for the liquidation.
Resolution
Dolomite Team: The issue was resolved in commit 54ab211.
-
GMXL-3 Medium Withdrawals Fail When A Backing Token is Zero Logical Error Resolved
Description
The
outputTokenandsecondaryOutputTokenare always validated to be equal after the execution of a withdrawal:_outputTokenAddress.value == _secondaryOutputTokenAddress.valueHowever, the GMX
SwapUtils.swapfunction does not alter the resulting token in the case that the input into the swap is 0. An input of 0 can occur if the GMX market is one-sided at the point of withdrawal execution e.g. market only has 1 ETH and no USDC deposited.Another scenario this may occur in is if the amount being withdrawn is very small. This ultimately means that the validation will fail and the tokens will be stuck in the unwrapper trader.
Recommendation
Modify the check such that if the value of the withdrawal output amount is 0, then the
_outputTokenAddressand the_secondaryOutputTokenAddressdo not have to match.Resolution
Dolomite Team: The issue was resolved in commit 0a8e6d3.
Guardian: Compare
_withdrawalInfo.outputTokenagainst the_outputTokenAddressif the requested output token is the long token. Otherwise compare it against the_secondaryOutputTokenAddress. Afterwards, if the other token’s output is non-zero, ensure the two token addresses match. -
UAIWT-2 Medium Fund Transfer From Wrapper Trader Can Be Skipped Logical Error Resolved
Description
If a user receives more GM than the
minOutputAmountthey set, the excess GM has to be deposited into the Core protocol such that the Vault balance and Core balance align. However, this state assumes_shouldSkipTransferhas been set tofalseupon deposit creation in the call to functionIsolationModeTokenVaultV1WithFreezable.executeDepositIntoVault:else { Require.that( isVaultFrozen(), _FILE, "Vault should be frozen" ); _setShouldVaultSkipTransfer(/* _shouldSkipTransfer = */ false); }It is possible to overwrite this pre-requisite state and set
_shouldSkipTransfer = truethrough the functionUpgradeableAsyncIsolationModeUnwrapperTrader.callFunctionwhen the sender is an operator.In this case, once the deposit is resolved and the
afterDepositExecutioncallback is triggered, the fund transfer into the Vault would be skipped and the funds would remain stuck inside the wrapper trader.Recommendation
Prior to calling
factory.depositIntoDolomiteMarginFromTokenConverter, explicitly_setShouldVaultSkipTransfer(/* _shouldSkipTransfer = */ false);Resolution
Dolomite Team: The issue was resolved in commit 0a8e6d3.
-
GMXO-2 Medium Potentially Misleading PnL Factor Oracle Risk Resolved
Description
In the
GmxV2MarketTokenPriceOraclecontract the call to functiongetMarketTokenPriceuses theMAX_PNL_FACTOR_FOR_WITHDRAWALSPnL type to read the market token price from GMX. This is typically the most constrictive PnL Type, such that trader profit is capped to the smallest amount relative to theMAX_PNL_FACTOR_FOR_TRADERSandMAX_PNL_FACTOR_FOR_DEPOSITS.Consequently, the resulting price of the market token will be higher when measured using the more constrictive
MAX_PNL_FACTOR_FOR_WITHDRAWALS. This will ultimately cause a user’s collateral to have a greater value than if the another type was used.Out of an abundance of caution, it may be preferable to use the less constrictive
MAX_PNL_FACTOR_FOR_DEPOSITS, so that the collateral is not optimistically valued by capping the PnL to a lower amount.Recommendation
Consider using the less constrictive
MAX_PNL_FACTOR_FOR_DEPOSITSto read the price of the GM token.Resolution
Dolomite Team: The issue was resolved in commit 990d726.
-
GLOBAL-3 Medium Vault Frozen On Single Account Logical Error Resolved
Description
When a subaccount creates a deposit or withdrawal, the Vault is frozen to prevent misuse such as borrowing when the underlying funds are not present, putting the protocol at risk. While the Vault is frozen, all other subaccounts are unable to perform any actions, including depositing, withdrawing, and borrowing.
This poses a potential problem as a deposit or withdrawal may take a prolonged time to be executed, and the order cannot be cancelled for the
MIN_ORACLE_BLOCK_CONFIRMATIONS. During this period, a subaccount that is close to liquidation is unable to deposit into the protocol and save their position.Recommendation
Consider allowing users to deposit and withdraw if another subaccount is frozen, but not borrow. Otherwise, explicitly document to users that if one account is in a frozen state, all other accounts cannot perform Vault actions.
Resolution
-
GMXO-3 Medium Liquidator Can Force Liquidation Protocol Manipulation Resolved
Description
When calculating the price of GM, the value is adjusted down by any negative price impact upon withdrawal. A liquidator can shift the price down on a position that is near liquidation by putting capital into GMX. This will alter the price that is calculated, and put the user in a liquidatable state.
A liquidator can use this to have first access to liquidating the user and get a unfair advantage compared to the other liquidators. Note that this manipulation can be done by non-liquidators as well to grief other users.
Recommendation
Document the behavior of GM’s pricing to users and carefully monitor the pricing for manipulation. Furthermore, utilize higher liquidity pools to minimize price impact.
Resolution
Dolomite Team: We have adjusted the oracle to no longer user price impact.
-
FIVF-1 Medium Liquidation Needs To Match Pending Output Token Validation Acknowledged
Description
The
expectedConversionTokenvalidation is meant to ensure that the values of two different tokens aren't added and subtracted in the_accountInfoToPendingAmountWeiMapand_vaultToPendingAmountWeiMapmappings.Due to this check, liquidations will fail if the
outputTokendoes not match theoutputTokenon a pending deposit/withdrawal. Forcing liquidations to use a specificoutputTokencan lead to less long/short token being withdrawn on redemption by experiencing negative price impact when swapping to that particularoutputToken.Furthermore, because the Oracle assumes liquidations are typically performed from long token to short token, a user could further exacerbate the price impact mispricing by forcing a liquidation from short to long token instead.
Recommendation
Reconsider if the
expectedConversionTokencheck is even necessary. The_amountDeltaWei.valueis always in GM, so subtracting two different token values should not occur. However, this would require a change to the_accountInfoToOutputTokenMapas an account could be experiencing two different conversion tokens.Resolution
Dolomite Team: Acknowledged.
-
AITB-1 Medium Unbounded Execution Fee For Deposits and Withdrawals Validation Resolved
Description
After deposit or withdrawal execution, the excess execution fee is refunded. However, this refund is inaccessible by the user initiating the wrapping or unwrapping, but rather held by the Dolomite Margin owner.
This can potentially cause asset loss as there are no limits on how much
msg.valuea user can forward as the execution fee. A user may prefer to first deposit for GM directly through GMX, as they are assured that they don’t lose native tokens unnecessarily.Recommendation
If the attribution of gas refunds is too constrictive with current size limits, consider bounding how much
msg.valuea user forwards for GMX execution.Resolution
Dolomite Team: The issue was resolved in commit c1949b8.
-
GLOBAL-4 Medium Sequencer May Experience Outages Logical Error Acknowledged
Description
While the Arbitrum sequencer is down it is possible for a users position to go from healthy to undercollateralized. During this time the average user will not be able to rescue their position as they will not be able to submit orders directly through Arbitrum.
However, most liquidators will be automated and would be sophisticated enough to submit liquidation transactions through the delayed inbox on L1. When the sequencer is back online the transactions submitted through the delayed box will be executed first, meaning the position will be liquidated before the users have a chance to rescue their position.
Recommendation
Consider adding a grace period after outages to allow users some time to save their position when the sequencer is back online.
Resolution
Dolomite Team: Acknowledged.
-
GLOBAL-5 Low Features May Be Disabled Logical Error Resolved
Description
GMX may choose to disable certain features of its protocol, including but not limited to deposit execution. If this feature was paused, a Dolomite user would be able to create a deposit even when execution of any deposits isn’t occurring.
As a result, a deposit will be created, unexecuted, and unable to be cancelled for the
MIN_ORACLE_BLOCK_CONFIRMATIONSperiod. The user will face a period where their funds are inaccessible as a result.Recommendation
Consider disallowing initiating wrappings when deposit creation or execution is disabled on GMX. Otherwise, clearly document this behavior to users and monitor when features are disabled.
Resolution
Dolomite Team: The issue was resolved in commit 38e9e52.
-
GMXL-4 Low Inefficient Execution Fee Handling Superfluous Code Resolved
Description
The GMX V2 system accepts and stores the execution fee as wrapped native tokens. The
exchangeRouter.sendWntfunction simply wraps themsg.valuesent into native tokens and transfers them to the vault. Therefore it is unnecessary to unwrap the wrapped native tokens only for thesendWntfunction to wrap them again.Recommendation
Transfer the execution fee into the GMX V2 system with the
exhangeRouter.sendTokensfunction.Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
UAIWT-3 Low Incorrect maxWei Limit Bypass Check Logical Error Resolved
Description
When receiving excess GM after a deposit, if it surpasses or is equal to the maximum allowed value for a market, then it is entirely sent to the vault owner. The check incorrectly includes equality with maximum WEI, since Dolomite allows deposits up to the maximum WEI, but not exceeding it.
Recommendation
Modify the comparison in the if of the
_depositIntoDefaultPositionAndClearDepositfunction from>=maxWeito> maxWei.Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
GMXL-5 Low Redundant Self Import Superfluous Code Resolved
Description
In the
GmxV2Library.solfile, the library itself is reimported redundantly.Recommendation
Remove the self import from line 24.
Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
GMXO-4 Low GM Price Impact May Be Misrepresented Oracle Risk Resolved
Description
The GM oracle calculates the price impact to appropriately adjust GM's price when the price impact is negative. However, the calculation is done using the short token as the output amount.
The short token being the output is not always the case, as a user can liquidate with the output token as the long token, which may result in an entirely different price impact than measured. As a result, GM’s resultant price will be misrepresented.
However, Dolomite does note that liquidations will generally be into USDC which is why price impact is measured from long to short token.
Recommendation
Consider adjusting the
_getAdjustedAccountValueslogic to take into account the output token during a liquidation and/or documenting this behavior to users.Resolution
Dolomite Team: We have decided to remove the price impact calculation and instead increase the liquidation penalty.
-
GTPB-1 Low Incorrect Interface Used Typo Resolved
Description
When calculating the actions length, the
traderTypeisIsolationModeWrapperbut theIIsolationModeUnwrapperTraderinterface is used.Recommendation
Use the
IIsolationModeWrapperTraderinterface for consistency.Resolution
Dolomite Team: The issue was resolved in commit 297f43c.
-
UAIWT-4 Low Dolomite Assumes Owner Can Handle GM Documentation Resolved
Description
In the case that the deposit of excess GM into the borrow account fails, and the market supply caps are exceeded, Dolomite transfers those GM tokens directly to the vault owner.
However, a Vault can be created for an arbitrary owner and Dolomite assumes that the owner can handle GM. A potential problem arises if the Vault's account is a contract which cannot support the transfer of GM, and those tokens will be locked.
Recommendation
Document to users this behavior to prevent unexpected loss of funds.
Resolution
Dolomite Team: The issue was resolved in commit d247fb8.
-
UAIWT-5 Low Inaccurate File Name Typo Resolved
Description
The
_FILEname is set to "IsolationModeWrapperTraderV2" which is not the actual name of the file. This differs from the standard within theUpgradeableAsyncIsolationModeUnwrapperTrader.solcontract where_FILE = "UpgradeableUnwrapperTraderV2"Recommendation
Change it to
bytes32 private constant _FILE = "UpgradeableWrapperTraderV2";Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
GMXO-5 Low Typo Typo Resolved
Description
There is a typo in the comment: “there’s on cap” should be “there’s no cap”.
Recommendation
Fix the typo as described above.
Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
GMXO-6 Low Inaccurate Comment Documentation Resolved
Description
In the price oracle the documentation states: "/// @dev All of the GM tokens listed have, at-worst, 20 bp for the price deviation".
However, this conflicts with the constant
PRICE_DEVIATION_BPwhich is set to 25 bp.Recommendation
Update the comment to reflect the 25bp price deviation.
Resolution
Dolomite Team: The issue was resolved in commit 6d1f1d8.
-
GLOBAL-6 Low Wrapping Fails With Zero Output Documentation Resolved
Description
The call to function
swapExactInputForOutputfails if the_minOutputAmountWeiis 0. As a result, the user is unable to swap their tokens into market tokens without setting theminOutputAmountof market tokens, which is not a requirement for GMX deposits.Recommendation
Clearly document the behavior that a user is required to specify a non-zero
minOutputAmountfor a deposit to be created.Resolution
Dolomite Team: The issue was resolved in commit d247fb8.
-
GLOBAL-7 Low Liquidations Cannot Include Pending Keys Documentation Acknowledged
Description
While a deposit or withdrawal is pending, the order is yet to be marked as retryable. If a liquidation were to be triggered through the
LiquidatorProxyV4WithGenericTradercontract with a pending key, it will ultimately call functioncreateActionsForWrappingand fail due to the retryable validation.This is important to note as multiple keys can be passed for a liquidation through the
ZapParams, and one failing will prevent the other liquidations from occurring.Recommendation
Clearly document that liquidations should exclude pending deposits/orders that are not marked as retryable to prevent liquidation failure.
Resolution
Dolomite Team: Acknowledged.
-
GMXIVF-1 Low Swap-only GMX Markets Are Unsupported Unsupported Feature Resolved
Description
GMX supports markets which are solely used for swapping and no trading is allowed. In such markets, the index token is
address(0).When the
VaultFactoryis being constructed,INDEX_TOKEN_MARKET_ID =DOLOMITE_MARGIN().getMarketIdByTokenAddress(INDEX_TOKEN);calls DolomiteMargin to fetch themarketIdfor the token, but it will revert onGetters._requireValidTokendue to the zero address being the parameter.Vaults cannot be created for swap-only market tokens and users will not be able to use these tokens as collateral for their borrow positions.
Recommendation
Explicitly document that swap-only markets will not be supported, or add extra handling when the index token is empty to prevent a revert when fetching the
marketIdand the Chainlink price for that market.Resolution
Dolomite Team: The issue was resolved in commit d247fb8.
-
VAULT-2 Low Stored Liquidation Fee Too Insufficient Incentives Acknowledged
Description
In order to open a borrow position on the GMX vault, users need to also submit in advance an execution fee that will be used in case of liquidation.
If for various reasons, such as network clogging, the fee must be increased, then issues appear because all existing opened positions will most likely not have enough fees deposited to cover liquidation. In this case liquidators would need to add the extra fee themselves, making the position less desirable to liquidate.
Recommendation
One solution is to have the wrapper provide the difference, where the team deposits the amount directly to it after increasing the fees.
Another solution is that the team perform these liquidations at a loss to themselves. This solution is a bit more gas intensive then the first suggestion but does not require the calculation of a deposit amount for all existing borrowing positions in advance.
Resolution
Dolomite Team: In this case, Dolomite will act as the liquidator of last resort and cover the cost ourselves.
-
GMXO-7 Low Only Fee For Negative Price Impact Is Read Oracle Risk Resolved
Description
When reading the swap fee from GMX,
forPositiveImpactis always set asfalse. This is okay as the fee is ultimately always valued withforPositiveImpact = falsewhen withdrawing in GMX.However, it should be made clear that the
getFeeBpByMarketTokenfunction will only return the largest swap fee, which is the one considering negative price impact.Recommendation
Consider adding documentation for function
getFeeBpByMarketTokenso that it is clear it does not consider the positive price impact swap fee.Resolution
Dolomite Team: The issue was resolved in commit 8dc257b.
No findings match.
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
