Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
006f305
plan: Add rebalancer simulation harness design
nambrot Jan 26, 2026
ab266c7
feat(rebalancer-sim): Add simulation harness for warp route rebalance…
nambrot Jan 27, 2026
1bf45b7
feat(rebalancer-sim): Add separate signers and parallel message proce…
nambrot Jan 28, 2026
bdaccb2
feat(rebalancer-sim): Add MessageTracker for off-chain message tracking
nambrot Jan 28, 2026
2d95f16
feat(rebalancer-sim): Add balancedTraffic scenario generator
nambrot Jan 28, 2026
56007f1
feat(rebalancer-sim): Enhance scenario configs with defaults and expe…
nambrot Jan 28, 2026
218a9eb
chore(rebalancer-sim): Regenerate scenarios with new format and balan…
nambrot Jan 28, 2026
b99d738
test(rebalancer-sim): Update tests to use scenario defaults and expec…
nambrot Jan 28, 2026
20af934
feat(rebalancer-sim): Add test utilities and inflight guard test
nambrot Jan 28, 2026
51b9241
feat(rebalancer-sim): Add RealRebalancerService for comparison testing
nambrot Jan 28, 2026
9ef29fa
fix(rebalancer-sim): Clean up signal handlers from RebalancerService
nambrot Jan 28, 2026
bc1589c
fix(rebalancer-sim): Fix state leaks causing multi-run test failures
nambrot Jan 28, 2026
980845a
fix(rebalancer-sim): Run both rebalancers by default for comparison
nambrot Jan 28, 2026
c3a4fca
fix(rebalancer-sim): Per-test anvil isolation for reliable multi-reba…
nambrot Jan 28, 2026
e27de79
feat(rebalancer-sim): Add HTML timeline visualizer for simulation res…
nambrot Jan 28, 2026
9173026
feat(rebalancer-sim): Improve visualizer with computed balances and r…
nambrot Jan 28, 2026
94e3ec3
feat(rebalancer-sim): Add rebalance e2e latency tracking
nambrot Jan 28, 2026
7854021
fix(rebalancer-sim): Disable provider polling to reduce RPC contention
nambrot Jan 28, 2026
a4cd977
fix(rebalancer-sim): Wait for RealRebalancerService initialization
nambrot Jan 28, 2026
006deb8
feat(rebalancer-sim): Add diagnostic logging to MessageTracker
nambrot Jan 28, 2026
bcf117a
fix(rebalancer-sim): Add cleanup between rebalancer comparison runs
nambrot Jan 28, 2026
d0c2952
refactor(rebalancer-sim): Simplify RealRebalancerRunner to in-process…
nambrot Jan 29, 2026
5c4c20f
fix(rebalancer-sim): Fix bridge to pull tokens and use actual balance…
nambrot Jan 29, 2026
db1c910
feat(rebalancer-sim): Improve balance curve visualization
nambrot Jan 29, 2026
53260a7
fix(rebalancer-sim): Fix transfer bar stacking overflow in visualizer
nambrot Jan 29, 2026
ba8589e
feat(rebalancer-sim): Add balance hover tooltips and config panel to …
nambrot Jan 29, 2026
26347db
feat(rebalancer-sim): Add scenario metadata to visualizer
nambrot Jan 29, 2026
bce1afa
chore(rebalancer-sim): Ignore entire results folder in gitignore
nambrot Jan 29, 2026
94a805a
Merge origin/main into feat/rebalancer-simulation-harness
nambrot Jan 30, 2026
e7d408e
fix(rebalancer-sim): Fix CI failures
nambrot Jan 30, 2026
74d74c2
fix(rebalancer-sim): Fix lint errors
nambrot Jan 30, 2026
c1c4251
docs(rebalancer-sim): Update README to match implementation
nambrot Jan 30, 2026
018fbd4
docs(rebalancer-sim): Add design decisions, usage examples, and futur…
nambrot Jan 30, 2026
33ed6d1
fix(rebalancer-sim): Address PR #7903 review comments (#7960)
nambrot Jan 30, 2026
7b438ba
fix(rebalancer-sim): Address PR #7903 human review comments (#7964)
nambrot Jan 30, 2026
f96999c
fix(rebalancer-sim): Rename rebalancer types and fix CLI rebalancer c…
nambrot Jan 30, 2026
e745538
fix(rebalancer-sim): Address final PR #7903 review comments
nambrot Jan 30, 2026
f05fe46
fix(rebalancer-sim): Fix CI test execution and format JSON files
nambrot Jan 30, 2026
3ea6378
fix(rebalancer-sim): Address PR review feedback
nambrot Jan 30, 2026
85781f6
refactor(rebalancer-sim): Rename CLIRebalancerRunner to ProductionReb…
nambrot Jan 30, 2026
0372b3f
fix(rebalancer-sim): Address PR review feedback
nambrot Jan 30, 2026
cdd5a89
refactor(rebalancer-sim): Flatten directory structure and consolidate…
nambrot Jan 30, 2026
77ddb29
ci(rebalancer-sim): Move to separate workflow with path filters
nambrot Jan 30, 2026
02b5c6a
fix(solidity): MockValueTransferBridge now transfers tokens with Safe…
nambrot Jan 30, 2026
f7b4c60
fix(rebalancer-sim): Fix scenarios directory path resolution
nambrot Jan 30, 2026
ce54159
fix(ci): Use underscore separator in rebalancer-sim matrix test names
nambrot Jan 30, 2026
3a09572
refactor(rebalancer-sim): Unify inflight-guard test with full-simulat…
nambrot Jan 30, 2026
32565bf
fix(rebalancer-sim): Add balanceTimeline to visualization data
nambrot Jan 30, 2026
83f0db8
style: Format inflight-guard.json with prettier
nambrot Jan 30, 2026
62a9cfa
fix(cli): Fix warp-rebalancer e2e test to simulate bridge delivery co…
nambrot Jan 30, 2026
9922620
chore: Add JSON to lint-staged precommit hook
nambrot Jan 30, 2026
eb05905
fix(rebalancer-sim): Address PR #7903 review comments
nambrot Feb 2, 2026
ee2935f
fix(rebalancer-sim): Cleanup simulation engine issues from PR review …
nambrot Feb 3, 2026
afbae12
refactor(rebalancer-sim): Use testcontainers for anvil setup (#8004)
nambrot-agent Feb 3, 2026
7f37f6c
Fix prettier
nambrot Feb 3, 2026
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
578 changes: 578 additions & 0 deletions .claude/rebalancer-simulation-plan.md

Large diffs are not rendered by default.

495 changes: 191 additions & 304 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions typescript/rebalancer-sim/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
dist
cache

# Simulation results (generated at runtime)
results/*.json
6 changes: 6 additions & 0 deletions typescript/rebalancer-sim/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"import": ["tsx"],
"extension": ["ts"],
"timeout": 120000,
"exit": true
}
271 changes: 271 additions & 0 deletions typescript/rebalancer-sim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# Rebalancer Simulation Harness

A fast, real-time simulation framework for testing Hyperlane warp route rebalancers against synthetic transfer scenarios.

## Purpose

This simulator helps answer questions like:

- Does the rebalancer respond correctly to liquidity imbalances?
- How quickly does the rebalancer restore balance after a traffic surge?
- What happens when bridge delays cause the rebalancer to over-correct?
- How do different rebalancer strategies compare on the same traffic pattern?

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│ SimulationEngine │
│ Orchestrates scenario execution, rebalancer polling, KPI collection│
└─────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌────────────────┐ ┌─────────────────┐
│ Scenario │ │ Rebalancer │ │ BridgeMock │
│ Generator │ │ Runner │ │ Controller │
│ │ │ │ │ │
│ Creates │ │ Simplified │ │ Simulates slow │
│ transfer │ │ rebalancer │ │ bridge delivery │
│ patterns │ │ for testing │ │ with config- │
│ │ │ │ │ urable delays │
└───────────────┘ └────────────────┘ └─────────────────┘
│ │ │
└────────────────────┼────────────────────┘
┌─────────────────────────────┐
│ Multi-Domain Deployment │
│ │
│ Single Anvil instance │
│ simulating N "chains" │
│ via different domain IDs │
│ │
│ Each domain has: │
│ - Mailbox (instant) │
│ - WarpToken + Collateral │
│ - Bridge (delayed) │
└─────────────────────────────┘
```

## Key Concepts

### Warp Token Mechanics

Understanding collateral flow is critical:

```
User sends FROM chain A TO chain B:
- Chain A: User deposits collateral → WarpToken GAINS collateral
- Chain B: Recipient withdraws → WarpToken LOSES collateral

This is counterintuitive! Transfers TO a chain DRAIN its liquidity.
```

### Two Message Paths

The simulator uses two different delivery mechanisms:

| Path | Mechanism | Delay | Use Case |
| -------------------- | ----------------------- | -------------------------- | ----------------------------------- |
| User transfers | MockMailbox | Instant | Simulates Hyperlane message passing |
Comment thread
nambrot-agent marked this conversation as resolved.
Outdated
| Rebalancer transfers | MockValueTransferBridge | Configurable (e.g., 500ms) | Simulates CCTP/bridge delays |

This separation is important because rebalancer transfers go through external bridges (CCTP, etc.) which have significant delays, while user transfers use Hyperlane's fast messaging.

## Directory Structure

```
typescript/rebalancer-sim/
├── src/
│ ├── deployment/ # Anvil + contract deployment
│ │ └── SimulationDeployment.ts
│ ├── scenario/ # Scenario generation & loading
│ │ ├── ScenarioGenerator.ts # Create synthetic scenarios
│ │ ├── ScenarioLoader.ts # Load from JSON files
│ │ └── types.ts # ScenarioFile, TransferScenario, etc.
│ ├── bridges/ # Bridge delay simulation
│ │ └── BridgeMockController.ts
│ ├── rebalancer/ # Rebalancer wrapper
│ │ └── HyperlaneRunner.ts # Simplified rebalancer for testing
│ ├── engine/ # Simulation orchestration
│ │ └── SimulationEngine.ts
│ └── kpi/ # Metrics collection
│ └── KPICollector.ts
├── scenarios/ # Pre-generated scenario JSON files
├── results/ # Test results (gitignored)
├── scripts/
│ └── generate-scenarios.ts
└── test/
├── scenarios/ # Unit tests for scenario generation
├── utils/ # Test utilities (Anvil management)
└── integration/ # Full simulation tests
```

## Scenario File Format

Each scenario JSON is self-contained with metadata, transfers, and default configurations:

```json
{
"name": "extreme-drain-chain1",
"description": "Tests rebalancer response when one chain is rapidly drained.",
"expectedBehavior": "95% of transfers go TO chain1, draining collateral...",
"duration": 10000,
"chains": ["chain1", "chain2", "chain3"],
"transfers": [...],
"defaultInitialCollateral": "100000000000000000000",
"defaultTiming": {
"bridgeDeliveryDelay": 500,
"rebalancerPollingFrequency": 1000,
"userTransferInterval": 100
},
"defaultBridgeConfig": {...},
"defaultStrategyConfig": {...},
"expectations": {
"minCompletionRate": 0.9,
"shouldTriggerRebalancing": true
}
}
```

Tests can use the defaults from JSON or override them for specific test needs.

## Running Simulations

### 1. Generate Scenarios (one-time)

```bash
pnpm generate-scenarios
```

Creates JSON files in `scenarios/` with various traffic patterns.

### 2. Run All Tests

```bash
pnpm test
```

Tests automatically detect if Anvil is available. If not installed, integration tests are skipped.

### 3. Run Specific Test

```bash
pnpm mocha test/integration/full-simulation.test.ts
pnpm mocha test/integration/inflight-guard.test.ts
```

### 4. View Results

Test results are saved to `results/` directory (gitignored):

```bash
cat results/extreme-drain-chain1.json
```

**Note:** If Anvil is not installed, integration tests will be skipped. Install Foundry with:

```bash
curl -L https://foundry.paradigm.xyz | bash && foundryup
```

## Scenario Types

### Predefined Scenarios (in `scenarios/`)

| Scenario | Description | Expected Behavior |
| -------------------------------- | ---------------------------------- | ------------------------- |
| `extreme-drain-chain1` | 95% of transfers TO chain1 | Heavy rebalancing needed |
| `extreme-accumulate-chain1` | 95% of transfers FROM chain1 | Heavy rebalancing needed |
| `large-unidirectional-to-chain1` | 5 large (20 token) transfers | Immediate imbalance |
| `whale-transfers` | 3 massive (30 token) transfers | Stress test response time |
| `balanced-bidirectional` | Uniform random traffic | Minimal rebalancing |
| `surge-to-chain1` | Traffic spike mid-scenario | Tests burst handling |
| `stress-high-volume` | 50 transfers, Poisson distribution | Load testing |
| `moderate-imbalance-chain1` | 70% of transfers to chain1 | Moderate rebalancing |
| `sustained-drain-chain3` | 30 transfers over 30s | Endurance test |

## Test Organization

### Unit Tests (`test/scenarios/`)

Test the scenario generation logic without running simulations:

- Does `unidirectionalFlow()` create correct transfer patterns?
- Does `randomTraffic()` distribute across all chains?
- Does serialization preserve BigInt amounts?

### Integration Tests (`test/integration/`)

Run full simulations on Anvil:

| Test File | Purpose |
| ------------------------- | ---------------------------------------------------- |
| `deployment.test.ts` | Verifies multi-domain deployment works |
| `full-simulation.test.ts` | Runs predefined scenarios, saves results |
| `inflight-guard.test.ts` | Demonstrates over-rebalancing without inflight guard |

### Why `inflight-guard.test.ts` is Separate

This test demonstrates a specific bug/limitation rather than testing a scenario type:

**What it proves:** Without tracking pending (inflight) transfers, the rebalancer sends redundant transfers because each poll sees "stale" on-chain balances.

**How it differs:**

- Uses custom inline scenario with extreme timing (3s bridge delay vs 200ms polling)
- Asserts on specific failure behavior (expects over-rebalancing)
- Documents a bug that needs fixing, not a passing scenario

## KPIs Collected

```typescript
interface SimulationKPIs {
totalTransfers: number;
completedTransfers: number;
completionRate: number; // 0-1, should be >0.9 with working rebalancer

averageLatency: number; // ms
p50Latency: number;
p95Latency: number;
p99Latency: number;

totalRebalances: number;
rebalanceVolume: bigint; // Total tokens moved by rebalancer

perChainMetrics: Record<
string,
{
initialBalance: bigint;
finalBalance: bigint;
transfersIn: number;
transfersOut: number;
}
>;
}
```

## Current Limitations

1. **Simplified Rebalancer**: The current `HyperlaneRunner` is a simplified implementation for testing, not the actual production rebalancer from `@hyperlane-xyz/rebalancer`.

2. **No Inflight Guard**: The simplified rebalancer doesn't track pending transfers, causing over-rebalancing when bridge delays are long relative to polling frequency.

3. **Single Anvil**: All "chains" run on one Anvil instance. Real cross-chain timing differences aren't simulated.

4. **Instant User Transfers**: User transfers via MockMailbox are instant. Real Hyperlane has ~15-30 second finality.

5. **No Gas Costs**: Gas costs aren't simulated. KPIs include rebalance count but not actual cost.

## Future Work
Comment thread
nambrot-agent marked this conversation as resolved.

### Phase 1: Integrate Real Rebalancer

- Wrap the actual `@hyperlane-xyz/rebalancer` service
- Add API for mocks (time stepping, explorer API mock)
- Support daemon mode with configurable polling

### Phase 2: Inflight Guard Testing

- Mock Explorer API for inflight transfer tracking
- Test scenarios that specifically require inflight awareness
- Validate that real rebalancer avoids over-correction
16 changes: 16 additions & 0 deletions typescript/rebalancer-sim/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import MonorepoDefaults from '../../eslint.config.mjs';

export default [
...MonorepoDefaults,
{
files: ['./src/**/*.ts'],
},
{
rules: {
// Disable restricted imports for Node.js built-ins since simulation harness is Node.js-only
'no-restricted-imports': ['off'],
// Allow console statements for simulation output
'no-console': ['off'],
},
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
];
61 changes: 61 additions & 0 deletions typescript/rebalancer-sim/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "@hyperlane-xyz/rebalancer-sim",
"version": "0.1.0",
"description": "Fast real-time simulation framework for testing Hyperlane warp route rebalancers",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsc",
"clean": "rm -rf dist cache",
"dev": "tsc --watch",
"generate-scenarios": "tsx scripts/generate-scenarios.ts",
"lint": "eslint -c ./eslint.config.mjs ./src",
"prettier": "prettier --write ./src",
"test": "mocha --config .mocharc.json './test/**/*.test.ts' --exit",
"test:ci": "pnpm test"
},
"dependencies": {
"@hyperlane-xyz/core": "workspace:*",
"@hyperlane-xyz/provider-sdk": "workspace:*",
"@hyperlane-xyz/registry": "catalog:",
"@hyperlane-xyz/rebalancer": "workspace:*",
"@hyperlane-xyz/sdk": "workspace:*",
"@hyperlane-xyz/utils": "workspace:*",
"ethers": "catalog:",
"pino": "catalog:",
"pino-pretty": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@hyperlane-xyz/tsconfig": "workspace:^",
"@types/chai": "catalog:",
"@types/chai-as-promised": "catalog:",
"@types/mocha": "catalog:",
"@types/node": "catalog:",
"chai": "catalog:",
"chai-as-promised": "catalog:",
"eslint": "catalog:",
"mocha": "catalog:",
"prettier": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:"
},
"engines": {
"node": ">=18"
},
"repository": "https://github.com/hyperlane-xyz/hyperlane-monorepo",
"keywords": [
"hyperlane",
"rebalancer",
"simulation",
"testing",
"warp-route"
],
"author": "Abacus Works, Inc.",
"license": "Apache-2.0"
}
Loading