Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
84 changes: 84 additions & 0 deletions .github/workflows/rebalancer-sim-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: rebalancer-sim-test

on:
push:
branches: [main]
paths:
- 'typescript/rebalancer/**'
- 'typescript/rebalancer-sim/**'
- 'solidity/contracts/mock/MockValueTransferBridge.sol'
- '.github/workflows/rebalancer-sim-test.yml'
pull_request:
paths:
- 'typescript/rebalancer/**'
- 'typescript/rebalancer-sim/**'
- 'solidity/contracts/mock/MockValueTransferBridge.sol'
- '.github/workflows/rebalancer-sim-test.yml'
workflow_dispatch:

concurrency:
group: rebalancer-sim-${{ github.ref }}
cancel-in-progress: true

jobs:
rebalancer-sim-test-matrix:
runs-on: depot-ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
test:
# Split full-simulation by scenario (file_grep-pattern, using _ as separator)
- full-simulation_extreme-drain
- full-simulation_extreme-accumulate
- full-simulation_large-unidirectional
- full-simulation_whale-transfers
- full-simulation_balanced-bidirectional
- full-simulation_random-with-headroom
# Other test files
- inflight-guard
- harness-setup
- unidirectional
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
Comment thread
nambrot marked this conversation as resolved.

- name: pnpm-build
uses: ./.github/actions/pnpm-build-with-cache

- name: Setup Foundry
uses: ./.github/actions/setup-foundry

- name: Run rebalancer-sim test (${{ matrix.test }})
working-directory: typescript/rebalancer-sim
run: |
if [[ "${{ matrix.test }}" == *"_"* ]]; then
FILE=$(echo "${{ matrix.test }}" | cut -d_ -f1)
PATTERN=$(echo "${{ matrix.test }}" | cut -d_ -f2)
pnpm mocha --config .mocharc.json "./test/**/*${FILE}*.test.ts" --grep "${PATTERN}" --exit
else
pnpm mocha --config .mocharc.json "./test/**/*${{ matrix.test }}*.test.ts" --exit
fi

- name: Upload test results
if: always()
uses: actions/upload-artifact@v5
with:
name: rebalancer-sim-results-${{ matrix.test }}
path: typescript/rebalancer-sim/results/*.html
if-no-files-found: ignore
retention-days: 7

rebalancer-sim-test:
runs-on: ubuntu-latest
needs: [rebalancer-sim-test-matrix]
if: always()
steps:
- uses: actions/checkout@v6
- name: Check rebalancer-sim test status
uses: ./.github/actions/check-job-status
with:
job_name: 'Rebalancer Sim Test'
result: ${{ needs.rebalancer-sim-test-matrix.result }}
3 changes: 2 additions & 1 deletion .lintstagedrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"*.js": "prettier --write",
"*.ts": "prettier --write",
"*.md": "prettier --write",
"*.sol": "prettier --write"
"*.sol": "prettier --write",
"*.json": "prettier --write"
}
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ COPY typescript/keyfunder/package.json ./typescript/keyfunder/
COPY typescript/provider-sdk/package.json ./typescript/provider-sdk/
COPY typescript/radix-sdk/package.json ./typescript/radix-sdk/
COPY typescript/rebalancer/package.json ./typescript/rebalancer/
COPY typescript/rebalancer-sim/package.json ./typescript/rebalancer-sim/
COPY typescript/relayer/package.json ./typescript/relayer/
COPY typescript/sdk/package.json ./typescript/sdk/
COPY typescript/tsconfig/package.json ./typescript/tsconfig/
Expand Down
483 changes: 190 additions & 293 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions solidity/contracts/mock/MockValueTransferBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
pragma solidity ^0.8.13;

import {ITokenBridge, Quote} from "../interfaces/ITokenBridge.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract MockValueTransferBridge is ITokenBridge {
using SafeERC20 for IERC20;
address public collateral;

constructor(address _collateral) {
Expand Down Expand Up @@ -33,6 +36,13 @@ contract MockValueTransferBridge is ITokenBridge {
bytes32 _recipient,
uint256 _amountOut
) external payable virtual override returns (bytes32 transferId) {
// Pull tokens from caller (warp token) - caller must have approved this bridge
IERC20(collateral).safeTransferFrom(
msg.sender,
address(this),
_amountOut
);

emit SentTransferRemote(
uint32(block.chainid),
_destinationDomain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { $, type ProcessPromise } from 'zx';

import {
type ERC20,
ERC20Test__factory,
ERC20__factory,
HypERC20Collateral__factory,
MockValueTransferBridge__factory,
Expand Down Expand Up @@ -941,10 +942,6 @@ describe('hyperlane warp rebalancer e2e tests', async function () {
const originDomain = chain3Metadata.domainId;
const destDomain = chain2Metadata.domainId;

// Chain names
const originName = CHAIN_NAME_3;
const destName = CHAIN_NAME_2;

// RPC URLs
const originRpc = chain3Metadata.rpcUrls[0].http;
const destRpc = chain2Metadata.rpcUrls[0].http;
Expand Down Expand Up @@ -998,6 +995,15 @@ describe('hyperlane warp rebalancer e2e tests', async function () {
},
});

const originTkn = ERC20__factory.connect(originTknAddress, originProvider);
Comment thread
Mo-Hussain marked this conversation as resolved.
const destTkn = ERC20__factory.connect(destTknAddress, destProvider);

// Verify initial balances before rebalancing
let originBalance = await originTkn.balanceOf(originContractAddress);
let destBalance = await destTkn.balanceOf(destContractAddress);
expect(originBalance.toString()).to.equal(toWei(10));
expect(destBalance.toString()).to.equal(toWei(10));

// Promise that will resolve with the event that is emitted by the bridge when the rebalance transaction is sent
const listenForSentTransferRemote = new Promise<{
origin: Domain;
Expand Down Expand Up @@ -1032,27 +1038,21 @@ describe('hyperlane warp rebalancer e2e tests', async function () {
expect(sentTransferRemote.recipient).to.equal(destContractAddress);
expect(sentTransferRemote.amount).to.equal(BigInt(toWei(5)));

const originTkn = ERC20__factory.connect(originTknAddress, originProvider);
const destTkn = ERC20__factory.connect(destTknAddress, destProvider);

let originBalance = await originTkn.balanceOf(originContractAddress);
let destBalance = await destTkn.balanceOf(destContractAddress);

// Verify that the tokens are in the right place before the transfer
expect(originBalance.toString()).to.equal(toWei(10));
expect(destBalance.toString()).to.equal(toWei(10));
// Verify that the bridge pulled tokens from origin (10 - 5 = 5)
originBalance = await originTkn.balanceOf(originContractAddress);
expect(originBalance.toString()).to.equal(toWei(5));

// Simulate rebalancing by transferring tokens from destination to origin chain.
// This process locks tokens on the destination chain and unlocks them on the origin,
// effectively increasing collateral on the destination while decreasing it on the origin,
// which achieves the desired rebalancing effect.
await hyperlaneWarpSendRelay({
origin: destName,
destination: originName,
warpCorePath: warpCoreConfigPath,
relay: true,
value: sentTransferRemote.amount.toString(),
});
// Simulate bridge delivery by minting tokens to destination warp token
// In a real bridge, tokens would be delivered to the destination chain
const destSigner = new Wallet(ANVIL_KEY, destProvider);
const destCollateralToken = ERC20Test__factory.connect(
destTknAddress,
destSigner,
);
await destCollateralToken.mintTo(
destContractAddress,
sentTransferRemote.amount.toString(),
);

originBalance = await originTkn.balanceOf(originContractAddress);
destBalance = await destTkn.balanceOf(destContractAddress);
Expand Down Expand Up @@ -1241,6 +1241,18 @@ describe('hyperlane warp rebalancer e2e tests', async function () {
},
});

const originTkn = ERC20__factory.connect(
originTknAddress,
originProvider,
);
const destTkn = ERC20__factory.connect(destTknAddress, destProvider);

// Verify initial balances before rebalancing
let originBalance = await originTkn.balanceOf(originContractAddress);
let destBalance = await destTkn.balanceOf(destContractAddress);
expect(originBalance.toString()).to.equal(toWei(10));
expect(destBalance.toString()).to.equal(toWei(10));

// Promise that will resolve with the event that is emitted by the bridge when the rebalance transaction is sent
const listenForSentTransferRemote = new Promise<{
origin: Domain;
Expand Down Expand Up @@ -1284,30 +1296,22 @@ describe('hyperlane warp rebalancer e2e tests', async function () {
BigInt(toWei(manualRebalanceAmount)),
);

const originTkn = ERC20__factory.connect(
originTknAddress,
originProvider,
// Verify that the bridge pulled tokens from origin (10 - 5 = 5)
originBalance = await originTkn.balanceOf(originContractAddress);
expect(originBalance.toString()).to.equal(
toWei(10 - Number(manualRebalanceAmount)),
);
const destTkn = ERC20__factory.connect(destTknAddress, destProvider);

let originBalance = await originTkn.balanceOf(originContractAddress);
let destBalance = await destTkn.balanceOf(destContractAddress);

// Verify that the tokens are in the right place before the transfer
expect(originBalance.toString()).to.equal(toWei(10));
expect(destBalance.toString()).to.equal(toWei(10));

// Simulate rebalancing by transferring tokens from destination to origin chain.
// This process locks tokens on the destination chain and unlocks them on the origin,
// effectively increasing collateral on the destination while decreasing it on the origin,
// which achieves the desired rebalancing effect.
await hyperlaneWarpSendRelay({
origin: destName,
destination: originName,
warpCorePath: warpCoreConfigPath,
relay: true,
value: sentTransferRemote.amount.toString(),
});
// Simulate bridge delivery by minting tokens to destination warp token
const destSigner = new Wallet(ANVIL_KEY, destProvider);
const destCollateralToken = ERC20Test__factory.connect(
destTknAddress,
destSigner,
);
await destCollateralToken.mintTo(
destContractAddress,
sentTransferRemote.amount.toString(),
);

originBalance = await originTkn.balanceOf(originContractAddress);
destBalance = await destTkn.balanceOf(destContractAddress);
Expand Down
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/
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
}
Loading
Loading