Skip to content

feat(simulator): inject AetherExecutor bytecode into revm CacheDB for shadow runs - #165

Merged
0xfandom merged 3 commits into
developfrom
feat/sim-aether-executor-bytecode-inject
May 26, 2026
Merged

feat(simulator): inject AetherExecutor bytecode into revm CacheDB for shadow runs#165
0xfandom merged 3 commits into
developfrom
feat/sim-aether-executor-bytecode-inject

Conversation

@0xfandom

Copy link
Copy Markdown
Collaborator

Summary

  • ValidatorParams gains an executor_bytecode: Option<Bytes> field
  • validate_backrun_inner injects the bytecode into the revm CacheDB at arb.to before running the arb leg when Some
  • BackrunValidatorConfig threads the value through; build_backrun_validator_config reads AETHER_EXECUTOR_BYTECODE_PATH env var, expects a forge artifact JSON, extracts deployedBytecode.object and decodes
  • 2 new simulator unit tests cover injection path + None no-op

Why

Demo / shadow-mode runs target a forked mainnet RPC where AetherExecutor is not deployed. Without injection every backrun sim hits empty-account revert at arb.to, so the validator path never produces a real flashloan-included profit measurement — the demo's end-to-end flow stops short of step 6 (flashloan sim).

Three alternatives considered before going with bytecode injection:

Option Reason rejected
Deploy AetherExecutor to mainnet Costs real ETH, exposes the contract before it's audited
Anvil with periodic re-fork loop Brittle, contract address can drift across forks, race conditions during fork swap
Reth full sync (already in compose) Mainnet sync takes 24h+ — not viable for tomorrow's demo

Bytecode injection is the production-shaped fix: the binary stays the same, ops just sets one env var pointing at the forge artifact when running against a fork. Production sets nothing and the cache resolves on-chain bytecode as before.

Files Changed

File Purpose
crates/simulator/src/mempool_backrun.rs ValidatorParams.executor_bytecode field; validate_backrun_inner injects via CacheDB::insert_account_info with Bytecode::new_raw + computed code_hash before EVM context is built. 2 new tests: executor_bytecode_injection_makes_arb_to_execute_real_code (injected REVERT propagates as ArbReverted, not NegativeAfterGas) and executor_bytecode_none_preserves_pre_existing_arb_to_state (None case respects whatever bytecode the cache already holds)
crates/grpc-server/src/mempool_pipeline.rs BackrunValidatorConfig.executor_bytecode field; clone into ValidatorParams on every sim attempt. Test fixtures default to None
crates/grpc-server/src/main.rs build_backrun_validator_config reads AETHER_EXECUTOR_BYTECODE_PATH. load_executor_runtime_bytecode helper parses the forge artifact's deployedBytecode.object hex string. Failure modes (env unset, file unreadable, malformed JSON, non-hex bytes) all fall through to None so a typo can't break the production path

Implementation notes

  • Bytecode::new_raw + hash_slow. revm's AccountInfo needs code_hash to match the bytecode for storage lookups. Bytecode::hash_slow computes the keccak — one extra hash per sim, negligible vs the EVM execution cost.
  • db: CacheDB<DB>mut db. The injection mutates the cache before it's moved into Context::new. No allocation on the None path — the if let Some(...) branch elides.
  • load_executor_runtime_bytecode is fail-open. Any error returns None (treated as no override). Logging the failure path is a follow-up — for now the env var is opt-in and operators know whether they set it.
  • No production behaviour change. Unset env → executor_bytecode = None → injection branch skipped → byte-identical to the pre-PR validate_backrun_inner.

Acceptance criteria

  • ValidatorParams.executor_bytecode = Some(bytecode) results in the bytecode being executable at arb.to inside the sim
  • executor_bytecode = None preserves any pre-existing cache state at arb.to
  • BackrunValidatorConfig carries the field; pipeline threads it on every sim
  • AETHER_EXECUTOR_BYTECODE_PATH env reads + decodes forge artifact
  • Malformed / missing artifact falls through to None (no panic)
  • Live smoke: demo.sh with the env set, forked mainnet, no on-chain deploy — confirm validate_backrun_inner produces non-zero gross_profit_wei on a profitable victim

Test plan

  • cargo build --release clean
  • cargo clippy --workspace --all-targets --release -- -D warnings clean
  • cargo test --workspace --release all green (654 passed, 0 failed)
    • 2 new mempool_backrun::tests::executor_bytecode_* tests
  • go build ./... + go test ./... -count=1 green (regression check, no Go changes)
  • forge build + forge test green (regression check, no Solidity changes — 59 passed, 0 failed, 2 skipped)
  • Demo smoke post-merge — run demo.sh for 30 min, confirm bundles rows accumulate under is_shadow = true with real-looking profit_eth values (not zeros)

0xfandom added 3 commits May 27, 2026 00:37
ValidatorParams gains an optional executor_bytecode field. When Some,
validate_backrun_inner inserts the bytecode at arb.to before running
the arb leg so the executeArb call hits real code even when the
contract is not deployed on the forked chain (demo / shadow runs
against an Alchemy fork without a mainnet deploy). None preserves
the production path - the cache resolves arb.to against on-chain
bytecode. Two unit tests cover the injection path and the no-op None
case.
BackrunValidatorConfig gains executor_bytecode: Option<Bytes>. The
field is cloned into ValidatorParams on every sim attempt so the
simulator can inject the AetherExecutor runtime bytecode into the
revm CacheDB before running the arb leg. Test fixtures (dummy_backrun_cfg
+ the SimContext::with_backrun_validator test) default to None,
preserving existing analytical-only behaviour.
…fact

build_backrun_validator_config reads AETHER_EXECUTOR_BYTECODE_PATH,
expects a forge artifact JSON (contracts/out/AetherExecutor.sol/
AetherExecutor.json), and extracts deployedBytecode.object into
BackrunValidatorConfig.executor_bytecode. Unset env or unreadable
file or malformed JSON falls back to None - production runs leave
the var unset and the cache resolves the address on-chain.
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aether Ready Ready Preview, Comment May 26, 2026 7:10pm
aether-63xv Ready Ready Preview, Comment May 26, 2026 7:10pm

@0xfandom
0xfandom merged commit 704f6aa into develop May 26, 2026
3 checks passed
0xfandom added a commit that referenced this pull request May 27, 2026
- replace declare -A with parallel arrays (bash 3.2 / macOS)
- PSQL() wrapper falls back to docker compose exec when host psql missing
- inline migrations loop (drop sqlx-cli dependency)
- AETHER_EXECUTOR_ADDRESS defaults to UniV3 SwapRouter02 (real bytecode for executor's eth_getCode startup check; revm overrides per-sim via #165)
- Postgres default port 5432 (matches compose)
- omit Alertmanager from demo profile (no SLACK_WEBHOOK_URL in shadow)
- build each Go binary independently so reconciler/monitor never get skipped
- skip pooldiscovery: protect curated config/pools.toml from --output clobber
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant