feat(simulator): inject AetherExecutor bytecode into revm CacheDB for shadow runs - #165
Merged
Merged
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
13 tasks
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ValidatorParamsgains anexecutor_bytecode: Option<Bytes>fieldvalidate_backrun_innerinjects the bytecode into the revmCacheDBatarb.tobefore running the arb leg whenSomeBackrunValidatorConfigthreads the value through;build_backrun_validator_configreadsAETHER_EXECUTOR_BYTECODE_PATHenv var, expects a forge artifact JSON, extractsdeployedBytecode.objectand decodesWhy
Demo / shadow-mode runs target a forked mainnet RPC where
AetherExecutoris not deployed. Without injection every backrun sim hits empty-account revert atarb.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:
AetherExecutorto mainnetBytecode 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
crates/simulator/src/mempool_backrun.rsValidatorParams.executor_bytecodefield;validate_backrun_innerinjects viaCacheDB::insert_account_infowithBytecode::new_raw+ computedcode_hashbefore EVM context is built. 2 new tests:executor_bytecode_injection_makes_arb_to_execute_real_code(injected REVERT propagates asArbReverted, notNegativeAfterGas) andexecutor_bytecode_none_preserves_pre_existing_arb_to_state(None case respects whatever bytecode the cache already holds)crates/grpc-server/src/mempool_pipeline.rsBackrunValidatorConfig.executor_bytecodefield; clone intoValidatorParamson every sim attempt. Test fixtures default toNonecrates/grpc-server/src/main.rsbuild_backrun_validator_configreadsAETHER_EXECUTOR_BYTECODE_PATH.load_executor_runtime_bytecodehelper parses the forge artifact'sdeployedBytecode.objecthex string. Failure modes (env unset, file unreadable, malformed JSON, non-hex bytes) all fall through toNoneso a typo can't break the production pathImplementation notes
Bytecode::new_raw+hash_slow. revm'sAccountInfoneedscode_hashto match the bytecode for storage lookups.Bytecode::hash_slowcomputes 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 intoContext::new. No allocation on theNonepath — theif let Some(...)branch elides.load_executor_runtime_bytecodeis fail-open. Any error returnsNone(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.executor_bytecode = None→ injection branch skipped → byte-identical to the pre-PRvalidate_backrun_inner.Acceptance criteria
ValidatorParams.executor_bytecode = Some(bytecode)results in the bytecode being executable atarb.toinside the simexecutor_bytecode = Nonepreserves any pre-existing cache state atarb.toBackrunValidatorConfigcarries the field; pipeline threads it on every simAETHER_EXECUTOR_BYTECODE_PATHenv reads + decodes forge artifactvalidate_backrun_innerproduces non-zerogross_profit_weion a profitable victimTest plan
cargo build --releasecleancargo clippy --workspace --all-targets --release -- -D warningscleancargo test --workspace --releaseall green (654 passed, 0 failed)mempool_backrun::tests::executor_bytecode_*testsgo build ./...+go test ./... -count=1green (regression check, no Go changes)forge build+forge testgreen (regression check, no Solidity changes — 59 passed, 0 failed, 2 skipped)demo.shfor 30 min, confirmbundlesrows accumulate underis_shadow = truewith real-lookingprofit_ethvalues (not zeros)