Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · May 2026

Risk Oracle

for GMX

Guardian's review of Risk Oracle for GMX, published May 2026. The report records 13 findings across 2 review rounds, including 2 medium and 4 low.

Published
Review window
May 18 to 25, 2026
Rounds
Main Review, Remediation Review
Language
Solidity
Chains
Arbitrum, Avalanche
Sector
Perpetuals, Infrastructure
  • 0 Critical
  • 0 High
  • 2 Medium
  • 4 Low
  • 7 Informational

11 resolved · 2 acknowledged

Scope

  • GuardianOrg/gmx-synthetics-private-team1-1779120112532 c49ebdea008c2d75803a

Findings 13

Main Review

8 findings · May 18 to 22, 2026
  1. M-01 Medium Funding/Borrowing Update Influence Stale Markets Configuration Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/c49ebdea0001126908ad266dec1b870c254f2c6e/contracts/config/RiskOracleConfig.sol
    Round
    Main Review

    Description

    RiskOracleConfig allows the RISK_ORACLE role to directly update funding and borrowing rate parameters without first settling the market’s accrued funding and borrowing state. Since the next market update calculates elapsed time from the previous update timestamp using the current config, a newly set rate can be applied to the entire unsettled interval. This can overcharge or undercharge open positions and distort claimable funding or borrowing fees and therefore lead to step-wise jumps in positions health. This can even lead to positions being suddenly liquidatable. The more stale a market is the bigger the impact of such config changes.

    Recommendation

    Consider to update the market state (accrue funding, borrowing, etc.) before changing configurations that have any influence on a stale market state.

    Resolution

    Guardian: The remediation for stale borrowing accrual only settles borrowing before updates to BASE_BORROWING_FACTOR and ABOVE_OPTIMAL_USAGE_BORROWING_FACTOR. However, RiskOracleConfig also allows the RISK_ORACLE role to update OPTIMAL_USAGE_FACTOR, which is part of the kink borrowing-rate formula. When it is non-zero, MarketUtils.getBorrowingFactorPerSecond uses it to route into getKinkBorrowingFactor:

    uint256 optimalUsageFactor = getOptimalUsageFactor(dataStore, market.marketToken, isLong);
    
    if (optimalUsageFactor != 0) {
        return getKinkBorrowingFactor(
            dataStore,
            market,
            isLong,
            reservedUsd,
            poolUsd,
            optimalUsageFactor
        );
    }
    

    Inside getKinkBorrowingFactor, the value controls the kink threshold and the divisor used to calculate the above-optimal portion of the borrowing rate. Despite this, _isRiskOracleBorrowingBaseKey excludes OPTIMAL_USAGE_FACTOR. As a result, _settleBorrowingIfRequired returns without settling accrued borrowing before the new OPTIMAL_USAGE_FACTOR is stored. The next borrowing update can then apply the new kink threshold to the full elapsed stale interval since cumulativeBorrowingFactorUpdatedAt. This can overcharge or undercharge open positions for the stale interval and may affect position health and liquidation eligibility. The impact depends on market staleness, utilization, and the size of the optimal usage factor change.

    Recommendation Include OPTIMAL_USAGE_FACTOR in _isRiskOracleBorrowingBaseKey.

  2. M-02 Medium RiskOracleConfig is not granted CONTROLLER role Configuration Resolved
    Location
    RiskOracleConfig.sol, deployRiskOracleConfig.ts
    Round
    Main Review

    Description

    RiskOracleConfig requires the CONTROLLER role to function because it writes through DataStore and emits through EventEmitter, both of which are onlyController-gated. The deploy script deployRiskOracleConfig.ts attempts to grant CONTROLLER after deployment:

    afterDeploy: async ({ deployedContract }) => {
      await grantRoleIfNotGranted(deployedContract, "CONTROLLER");
    },
    

    However, grantRoleIfNotGranted immediately returns on existing mainnet deployment networks:

    if (hre.gmx.isExistingMainnetDeployment) {
      return;
    }
    

    Affected flows include:

    • setRiskOracleMarketEnabled, because it calls DataStore.setBool.
    • setBool, setAddress, setBytes32, setUint, and setInt, because they call the corresponding DataStore.set* method.
    • All successful setter flows, because they emit through EventEmitter, which also requires CONTROLLER

    Recommendation

    Add RiskOracleConfig to requiredRolesForContracts.CONTROLLER in config/roles.ts, and ensure the mainnet role update transaction grants the deployed RiskOracleConfig address the CONTROLLER role.

  3. L-01 Low Missing Cross-Parameter Sanity Checks Configuration Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/c49ebdea0001126908ad266dec1b870c254f2c6e/contracts/config/RiskOracleConfig.sol
    Round
    Main Review

    Description

    RiskOracleConfig allows the RISK_ORACLE role to directly update sensitive market parameters. Right now, values are mostly checked against broad per-key bounds, but important cross-parameter invariants are not enforced. For example, MIN_COLLATERAL_FACTOR should remain greater than or equal to MIN_COLLATERAL_FACTOR_FOR_LIQUIDATION; otherwise users may be able to open or increase positions that are immediately liquidatable.

    Recommendation

    Consider to add invariant checks before applying risk oracle updates.

  4. L-02 Low Risk Oracle Config Accepts Non-Market Addresses Configuration Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/c49ebdea0001126908ad266dec1b870c254f2c6e/contracts/config/RiskOracleConfig.sol#L57-L73
    Round
    Main Review

    Description

    RiskOracleConfig.setRiskOracleMarketEnabled only checks that the given market != address(0) before setting RISK_ORACLE_MARKET_ENABLED. It does not verify that the supplied address is a registered GMX market.

    Recommendation

    Consider to validate that the given market exists in GMX.

  5. L-03 Low Unnecessary Setters Exposed Warning Resolved
    Location
    RiskOracleConfig.sol
    Round
    Main Review

    Description

    RiskOracleConfig should only expose setUint for the currently allowlisted risk-oracle parameters, because those parameters are consumed by the protocol as uint256 configs. Instead, the contract exposes generic setters for multiple DataStore types:

    • setBool
    • setAddress
    • setBytes32
    • setUint
    • setInt Because the same base-key allowlist is shared by all setters, a RISK_ORACLE account can accidentally call the wrong setter for a critical risk parameter. The transaction can succeed and emit a config event, but the actual uint config used by the protocol remains unchanged.

    Recommendation

    Remove the non-uint setters from RiskOracleConfig if the internal risk oracle only needs to update uint risk parameters.

  6. L-04 Low RISK_ORACLE Role Missing In Scripts Warning Resolved
    Location
    GLOBAL
    Round
    Main Review

    Description

    The RISK_ORACLE is missing in multiple scripts:

    • validateRolesUtils only uses the Role library and not the new Role2 library
    • printRoles lacks the role in knownRoles
    • validateContractDeployment lacks the role in roleLabels
    • validateDeploymentUtils does not include it in the external-role allowlist

    Recommendation

    Consider to add the RISK_ORACLE to scripts wherever it is needed.

  7. I-01 Informational Unused Imports Superfluous Code Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/c49ebdea0001126908ad266dec1b870c254f2c6e/contracts/config/RiskOracleConfig.sol
    Round
    Main Review

    Description

    RiskOracleConfig.sol imports Keys2, Precision, Cast, and MarketUtils, but none of these are used.

    Recommendation

    Consider to remove unused code.

  8. I-02 Informational _validateKey performs a redundant role check Access Control Resolved
    Location
    RiskOracleConfig.sol
    Round
    Main Review

    Description

    All public risk-oracle setters are already protected by the onlyRiskOracle modifier. However, _validateKey checks the same role again:

    if (roleStore.hasRole(msg.sender, Role2.RISK_ORACLE)) {
        ...
        return;
    }
    
    revert Errors.InvalidBaseKey(baseKey);
    

    This role check is redundant for the current setters. Additionally, if _validateKey is reused by a future function that is not protected by onlyRiskOracle, callers without the role would receive InvalidBaseKey instead of the more accurate Unauthorized error.

    Recommendation

    Remove the role check from _validateKey and rely on the external onlyRiskOracle modifier for authorization.

Remediation Review

5 findings · May 25, 2026
  1. I-01 Informational Coarser operational controls than ConfigSyncer Best Practices Acknowledged
    Location
    RiskOracleConfig.sol
    Round
    Remediation Review

    Description

    The older Chaos Labs risk-oracle flow used ConfigSyncer as an adapter between the external Risk Oracle and GMX config writes. Before applying an update, ConfigSyncer checked multiple operational disable flags:

    • global sync feature disable;
    • per-market disable;
    • per-parameter disable;
    • per-market-parameter disable. RiskOracleConfig uses a simpler direct-write model. A CONFIG_KEEPER can enable or disable all risk-oracle updates for a market through setRiskOracleMarketEnabled, and a RISK_ORACLE account can then write allowlisted parameters for enabled markets. This is simpler and may be intentional, but it is also less granular. If the internal risk oracle has an issue with one parameter family, such as funding, borrowing, or caps, operators can only disable the whole market or revoke the RISK_ORACLE role. They cannot pause a single base key or a single market/base-key pair while allowing other risk-oracle updates to continue.

    Recommendation

    Consider whether RiskOracleConfig should include finer emergency controls similar to ConfigSyncer, especially:

    • a global risk-oracle config feature disable;
    • a per-base-key disable;
    • optionally, a per-market/base-key disable. If this coarser control model is intentional, document the operational runbook for pausing faulty risk-oracle updates, including whether operators should disable affected markets or revoke the RISK_ORACLE role.
  2. I-02 Informational Allowlist differs from previous ConfigSyncer Warning Acknowledged
    Location
    RiskOracleConfig,sol
    Round
    Remediation Review

    Description

    The new RiskOracleConfig allowlist is not a direct replacement for the previous Chaos Labs ConfigSyncer risk-oracle surface. ConfigSyncer can apply updates for the following base keys that RiskOracleConfig does not allow:

    • SWAP_IMPACT_FACTOR
    • SWAP_IMPACT_EXPONENT_FACTOR
    • BORROWING_FACTOR
    • BORROWING_EXPONENT_FACTOR
    • RESERVE_FACTOR
    • OPEN_INTEREST_RESERVE_FACTOR Conversely, RiskOracleConfig adds support for some keys that ConfigSyncer did not support, including:
    • MAX_POSITION_IMPACT_FACTOR
    • GLV_MAX_MARKET_TOKEN_BALANCE_USD
    • GLV_MAX_MARKET_TOKEN_BALANCE_AMOUNT
    • FUNDING_FACTOR
    • FUNDING_EXPONENT_FACTOR
    • THRESHOLD_FOR_STABLE_FUNDING
    • THRESHOLD_FOR_DECREASE_FUNDING
    • MIN_COLLATERAL_FACTOR
    • MIN_COLLATERAL_FACTOR_FOR_OPEN_INTEREST_MULTIPLIER
    • MIN_COLLATERAL_FACTOR_FOR_LIQUIDATION This may be intentional if the internal Risk Oracle is meant to manage a different parameter set than the Chaos Labs integration. If not, the migration could silently remove automated risk-oracle management for swap impact, legacy borrowing, and reserve-factor parameters.

    Recommendation

    Confirm whether the new allowlist intentionally drops the ConfigSyncer-only keys.

  3. I-03 Informational Incorrect NatSpec Documentation Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/8c2d75803a8c843ed2a72d8a3503981a70609527/contracts/config/RiskOracleConfig.sol#L214-L228
    Round
    Remediation Review

    Description

    The NatSpec of the _validateKeyAndMarket function states out that the function returns an address but it returns a market now.

    Recommendation

    Consider to update the NatSpec.

  4. I-04 Informational Unused EventUtils Superfluous Code Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/8c2d75803a8c843ed2a72d8a3503981a70609527/contracts/config/RiskOracleConfig.sol#L25
    Round
    Remediation Review

    Description

    EventUtils.IntItems is unused now.

    Recommendation

    Consider to remove unused code.

  5. I-05 Informational Stale updateMarketConfigUtils Script Configuration Resolved
    Location
    https://github.com/GuardianOrg/gmx-synthetics-private-team1-1779120112532/blob/8c2d75803a8c843ed2a72d8a3503981a70609527/scripts/updateMarketConfigUtils.ts#L8-L12
    Round
    Remediation Review

    Description

    The list of RISK_ORACLE managed keys in the updateMarketConfigUtils.ts script is stale.

    Recommendation

    Consider to update this script.

More from GMX

All 52 reports
  1. Incentives Contracts

    17 findings 17 findings: 3 medium, 3 low, 11 informational
  2. Donation Contracts

    25 findings 25 findings: 1 medium, 6 low, 18 informational
  3. V2.3 Pre-Review

    7 findings 7 findings: 7 informational
  4. Token Delisting Procedure

    24 findings4 high 24 findings: 4 high, 12 medium, 3 low, 5 informational

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.

Get a quote