Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions forked-env-example/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# GMX Fork Testing Environment Variables

# Arbitrum RPC URL (required)
# You can use a public RPC or a provider like Alchemy/Infura for better performance
ARBITRUM_RPC_URL=https://arb1.arbitrum.io/rpc
32 changes: 32 additions & 0 deletions forked-env-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Compiler files
cache/
out/

# Dependencies
lib/
.gitmodules

# Environment variables
.env

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Dotenv file
.env
.env.test

# Node.js (if using Hardhat/TypeScript tooling)
node_modules/

# macOS
.DS_Store

# IDE
.vscode/
.idea/
*.swp
*.swo
*~
60 changes: 60 additions & 0 deletions forked-env-example/QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# GMX Forked env example - Quick Start

Standalone fork tests for GMX Synthetics V2 on Arbitrum. Describes the steps to open/close positions using real mainnet contracts.

**Self-contained**: Copy this directory anywhere and follow the setup below.

## Setup (2 minutes)

```bash
cd forked-env-example

# Initialize git (required for forge install if setting up as a stand alone repo)
git init

# Install dependencies (just forge-std)
forge install foundry-rs/forge-std --no-commit

# Set Arbitrum RPC URL
cp .env.example .env && source .env
```

## Run Tests

```bash
forge test --fork-url $ARBITRUM_RPC_URL -vv
forge test --fork-url $ARBITRUM_RPC_URL --match-test testOpenLongPosition -vv
forge test --fork-url $ARBITRUM_RPC_URL --match-test testCloseLongPosition -vv
```

## What This Does

Tests demonstrate the GMX order flow:

1. **Create order** - User sends collateral + execution fee to GMX
2. **Execute order** - Keeper executes with oracle prices (mocked in these tests)
3. **Verify position** - Check position was created/closed correctly

Example: `testOpenLongPosition` opens a 2.5x leveraged long ETH position with 0.001 ETH collateral (~$3.89 → ~$9.7 position).

## How It Works

**Fork testing**: Tests run against real GMX contracts on Arbitrum mainnet at block 392496384 (matches a real transaction).

**Oracle mocking**: GMX uses Chainlink Data Streams (off-chain signed prices). Oracle provider bytecode is replaced with a mock using **`vm.etch`** so orders can be executed without real signatures.

**Key files**:
- `contracts/constants/GmxArbitrumAddresses.sol` - Production contract addresses (all arbitrum deployments [here](https://github.com/gmx-io/gmx-synthetics/blob/main/docs/arbitrum-deployments.md))
- `contracts/mocks/MockOracleProvider.sol` - Oracle price mocking. Critical step to bypass the Chainlink Data Stream signature verification on a forked env
- `contracts/interfaces/IGmxV2.sol` - Minimal GMX interfaces. Miminal code copied from the GMX contracts/interfaces.
- `contracts/utils/GmxForkHelpers.sol` - Reusable helpers for order creation, state queries
- `test/GmxOrderFlow.t.sol` - Main test contract

## What You'll Learn

- How to create GMX orders (MarketIncrease, MarketDecrease)
- Two-step execution model (user creates → keeper executes)
- Handling oracle prices and execution fees
- Querying positions and verifying state changes

**Oracle provider address** mocked `0xE1d5a068c5b75E0c7Ea1A9Fe8EA056f9356C6fFD` (Chainlink Data Stream provider verified from mainnet txs).
82 changes: 82 additions & 0 deletions forked-env-example/contracts/constants/GmxArbitrumAddresses.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
* GMX Synthetics V2 contract addresses on Arbitrum mainnet
* @dev All addresses from official GMX deployments
* @dev Source: https://github.com/gmx-io/gmx-synthetics/blob/main/docs/arbitrum-deployments.md
* @dev Last Updated: Aug 13, 2025
*/
library GmxArbitrumAddresses {
// ============ Core Protocol Contracts ============

/// Central key-value store for all protocol data
address internal constant DATA_STORE = 0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8;

/// Role-based access control
address internal constant ROLE_STORE = 0x3c3d99FD298f679DBC2CEcd132b4eC4d0F5e6e72;

/// Reader contract for querying protocol state
address internal constant READER = 0x65A6CC451BAfF7e7B4FDAb4157763aB4b6b44D0E;

/// Router contract for plugin transfers
address internal constant ROUTER = 0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6;

/// Main entry point for creating deposits, withdrawals, and orders
address internal constant EXCHANGE_ROUTER = 0x87d66368cD08a7Ca42252f5ab44B2fb6d1Fb8d15;

/// Handles order execution logic
address internal constant ORDER_HANDLER = 0x04315E233C1c6FfA61080B76E29d5e8a1f7B4A35;

/// Oracle contract for price feeds
address internal constant ORACLE = 0x7F01614cA5198Ec979B1aAd1DAF0DE7e0a215BDF;

/// Oracle price store
address internal constant ORACLE_STORE = 0xA8AF9B86fC47deAde1bc66B12673706615E2B011;

/// Chainlink Data Streams oracle provider
/// @dev Used for WETH and USDC price feeds on mainnet
address internal constant CHAINLINK_DATA_STREAM_PROVIDER = 0xE1d5a068c5b75E0c7Ea1A9Fe8EA056f9356C6fFD;

/// Order vault - holds collateral for pending orders
address internal constant ORDER_VAULT = 0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5;

/// Deposit handler
address internal constant DEPOSIT_HANDLER = 0x563E8cDB5Ba929039c2Bb693B78CE12dC0AAfaDa;

/// Withdrawal handler
address internal constant WITHDRAWAL_HANDLER = 0x1EC018d2b6ACCA20a0bEDb86450b7E27D1D8355B;

/// Event emitter
address internal constant EVENT_EMITTER = 0xC8ee91A54287DB53897056e12D9819156D3822Fb;

/// Deposit vault
address internal constant DEPOSIT_VAULT = 0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55;

/// Withdrawal vault
address internal constant WITHDRAWAL_VAULT = 0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55;

// ============ Markets ============

/// ETH/USD perp market
/// @dev Long collateral: WETH, Short collateral: USDC, Index: WETH
address internal constant ETH_USD_MARKET = 0x70d95587d40A2caf56bd97485aB3Eec10Bee6336;

/// BTC/USD perp market
/// @dev Long collateral: WBTC, Short collateral: USDC, Index: WBTC
address internal constant BTC_USD_MARKET = 0x47c031236e19d024b42f8AE6780E44A573170703;

// ============ Tokens ============

/// Wrapped ETH on Arbitrum
address internal constant WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;

/// Native USDC on Arbitrum (not bridged)
address internal constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;

/// Wrapped BTC on Arbitrum
address internal constant WBTC = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f;

/// USDC.e (bridged USDC from Ethereum)
address internal constant USDC_E = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8;
}
Loading
Loading