Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
82c996f
feat: replay preceding block transactions for accurate gas estimation…
RonTuretzky Feb 28, 2026
6d319ca
fix: remove redundant format! inside eprintln! to fix clippy warning
RonTuretzky Mar 2, 2026
60a0431
perf: build EVM once outside replay loop instead of per-transaction
RonTuretzky Mar 2, 2026
412b159
Merge branch 'main' into RonTuretzky/issue-95-spec
bagelface Apr 2, 2026
d5f4e6b
fix: correct tx bound check
rubydusa Apr 29, 2026
2eda3b7
fix: address issues by copilot and rubydusa
rubydusa Apr 29, 2026
ed5021e
fix: accept 0x-prefixed memory in parse_trace_memory
rubydusa Apr 30, 2026
ca9fcc5
test: cover blockBaseFee in SimEnvOpts fixture
rubydusa Apr 30, 2026
5d958c6
Merge remote-tracking branch 'origin/main' into RonTuretzky/issue-95-…
rubydusa Apr 30, 2026
e755c15
fix: anchor preceding-tx replay state to block N-1
rubydusa Apr 30, 2026
32e4c95
docs: generalize block-N-1 anchoring comment
rubydusa Apr 30, 2026
2c3c8ac
fix: bump simulator spec to Osaka and cap tx gas at EIP-7825 limit
rubydusa Apr 30, 2026
4ed3231
Merge pull request #122 from gas-killer/Rubydusa/fix-block-minus-one
rubydusa May 4, 2026
524f260
chore(submodule): retarget gas-killer-avs-sol to renamed solidity-sdk
rubydusa May 4, 2026
6644307
fix: derive SpecId from block, propagate access/auth lists, fix raw-g…
rubydusa May 5, 2026
e1e22b2
test: cover all seven correctness fixes from PR #96 review
rubydusa May 5, 2026
624399c
docs: rewrite test docstrings to describe invariants, not PR history
rubydusa May 5, 2026
bf233f7
chore: cargo fmt
rubydusa May 5, 2026
23a864d
chore: rename submodule itself
rubydusa May 5, 2026
f11ec21
Merge pull request #125 from gas-killer/Rubydusa/bump-solidity-sdk
bagelface May 5, 2026
251b2ce
fix: detect chain ID at build to support Sepolia alongside mainnet
rubydusa May 5, 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
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/gas-killer-avs-sol"]
path = contracts/lib/gas-killer-avs-sol
url = https://github.com/BreadchainCoop/gas-killer-avs-sol
[submodule "contracts/lib/solidity-sdk"]
path = contracts/lib/solidity-sdk
url = https://github.com/gas-killer/solidity-sdk
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion abis/SimEnvOptsCallee.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abis/SimEnvOptsStructs.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abis/SimEnvOptsTestMain.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Solidity contracts used by the Gas Analyzer for on-chain gas estimation and inte

## Contents

- **`src/StateChangeHandlerGasEstimator.sol`** — Wrapper around `StateChangeHandlerLib` from [gas-killer-avs-sol](https://github.com/BreadchainCoop/gas-killer-avs-sol). Deployed to an Anvil fork to measure the actual gas cost of replaying a transaction's state updates.
- **`src/StateChangeHandlerGasEstimator.sol`** — Wrapper around `StateChangeHandlerLib` from [solidity-sdk](https://github.com/gas-killer/solidity-sdk). Deployed to an Anvil fork to measure the actual gas cost of replaying a transaction's state updates.
- **`src/AccessControlTestContracts.sol`** — Test contracts for verifying state update extraction across access-controlled calls.
- **`src/DelegateCallTestContracts.sol`** — Test contracts for verifying correct state update extraction with `DELEGATECALL` (only top-level context changes should be captured).
- **`script/`** — Foundry deployment scripts for the test contracts.
Expand All @@ -18,7 +18,7 @@ Solidity contracts used by the Gas Analyzer for on-chain gas estimation and inte
```bash
cd contracts

# Install dependencies (forge-std, gas-killer-avs-sol)
# Install dependencies (forge-std, solidity-sdk)
forge install

# Build
Expand Down
4 changes: 2 additions & 2 deletions contracts/foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"lib/forge-std": {
"rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505"
},
"lib/gas-killer-avs-sol": {
"rev": "33ef63ec63dccf6f7de98778de1cbbf2754489a6"
"lib/solidity-sdk": {
"rev": "ca0be7078f77e414952e7723c9b92a2d4d4f8e54"
}
}
2 changes: 1 addition & 1 deletion contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ out = "out"
libs = ["lib"]

remappings = [
"gk/=lib/gas-killer-avs-sol/src/"
"gk/=lib/solidity-sdk/src/"
]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 0 additions & 1 deletion contracts/lib/gas-killer-avs-sol
Submodule gas-killer-avs-sol deleted from 33ef63
1 change: 1 addition & 0 deletions contracts/lib/solidity-sdk
Submodule solidity-sdk added at ca0be7
18 changes: 14 additions & 4 deletions contracts/src/SimEnvTestContracts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface SimEnvOptsStructs {
uint256 blockTimestamp;
uint256 blockGasLimit;
uint256 blockPrevRandao;
uint256 blockBaseFee;
}

error EnvironmentMismatch(SimEnvOpts expected, SimEnvOpts actual, string explanation);
Expand All @@ -23,15 +24,16 @@ interface SimEnvOptsStructs {
/// @notice Not covered: Blobs (BLOBHASH, BLOBBASEFEE), Block (BLOCKHASH). Possibly more
contract SimEnvOptsTestMain is SimEnvOptsStructs {
SimEnvOptsCallee simEnvOptsCallee;

constructor (
address txOrigin,
uint256 txGasPrice,
address blockCoinbase,
uint256 blockNumber,
uint256 blockTimestamp,
uint256 blockGasLimit,
uint256 blockPrevRandao
uint256 blockPrevRandao,
uint256 blockBaseFee
) {
simEnvOptsCallee = new SimEnvOptsCallee(
txOrigin,
Expand All @@ -40,7 +42,8 @@ contract SimEnvOptsTestMain is SimEnvOptsStructs {
blockNumber,
blockTimestamp,
blockGasLimit,
blockPrevRandao
blockPrevRandao,
blockBaseFee
);
}

Expand All @@ -61,7 +64,8 @@ contract SimEnvOptsCallee is SimEnvOptsStructs {
uint256 blockNumber,
uint256 blockTimestamp,
uint256 blockGasLimit,
uint256 blockPrevRandao
uint256 blockPrevRandao,
uint256 blockBaseFee
) {
simEnv.txOrigin = txOrigin;
simEnv.txGasPrice = txGasPrice;
Expand All @@ -70,6 +74,7 @@ contract SimEnvOptsCallee is SimEnvOptsStructs {
simEnv.blockTimestamp = blockTimestamp;
simEnv.blockGasLimit = blockGasLimit;
simEnv.blockPrevRandao = blockPrevRandao;
simEnv.blockBaseFee = blockBaseFee;
}

function test() external {
Expand All @@ -82,6 +87,7 @@ contract SimEnvOptsCallee is SimEnvOptsStructs {
actual.blockTimestamp = block.timestamp;
actual.blockGasLimit = block.gaslimit;
actual.blockPrevRandao = block.prevrandao;
actual.blockBaseFee = block.basefee;

bool correct = true;
string memory reason = "Mismatched fields: ";
Expand Down Expand Up @@ -113,6 +119,10 @@ contract SimEnvOptsCallee is SimEnvOptsStructs {
correct = false;
reason = string.concat(reason, "blockPrevRandao, ");
}
if (actual.blockBaseFee != expected.blockBaseFee) {
correct = false;
reason = string.concat(reason, "blockBaseFee, ");
}

if (!correct) {
revert EnvironmentMismatch(expected, actual, reason);
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/StateChangeHandlerGasEstimator.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import { StateChangeHandlerLib, StateUpdateType } from "../lib/gas-killer-avs-sol/src/StateChangeHandlerLib.sol";
import { StateChangeHandlerLib, StateUpdateType } from "../lib/solidity-sdk/src/StateChangeHandlerLib.sol";

/// @notice Gas estimator with transparent-proxy fallback.
///
Expand Down
42 changes: 37 additions & 5 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ async fn execute_command(cli_args: CliArgs) -> Result<()> {
let block_number = receipt
.block_number
.expect("couldn't retrieve block number");
let tx_index = receipt
.transaction_index
.expect("couldn't retrieve transaction index");
let gas_used = receipt.gas_used;
let original_status = receipt.status();
let tx_sender = receipt.from;
Expand Down Expand Up @@ -296,9 +299,37 @@ async fn execute_command(cli_args: CliArgs) -> Result<()> {
.build()
.await?;

// Try measured gas estimation first
match gk.estimate_state_changes_gas(contract_address, tx_sender, &state_updates)
{
// Fetch preceding transactions for mid-block state accuracy.
// We fail hard here rather than falling back to block-N-1 state:
// a silent fallback can produce a confidently wrong gas number for
// any tx that depends on mid-block state.
let preceding_txs = gas_analyzer_rpc::get_preceding_transactions(
&provider,
block_number,
tx_index,
)
.await
.map_err(|e| {
anyhow::Error::msg(format!(
"Failed to fetch preceding transactions for block {} (tx index {}): {}",
block_number, tx_index, e
))
})?;
Comment thread
rubydusa marked this conversation as resolved.

if !preceding_txs.is_empty() {
println!(
"Replaying {} preceding transaction(s) for accurate mid-block state...",
preceding_txs.len()
);
}

// Try measured gas estimation with preceding tx replay
match gk.estimate_state_changes_gas_with_preceding(
contract_address,
tx_sender,
&state_updates,
&preceding_txs,
) {
Ok(gas) => (gas + TURETZKY_UPPER_GAS_LIMIT, false),
Err(e) => {
// Fall back to heuristic estimation
Expand Down Expand Up @@ -378,9 +409,10 @@ async fn execute_command(cli_args: CliArgs) -> Result<()> {
println!("\n{}", "=== Gas Analysis ===".blue().bold());
println!("Transaction: 0x{}", hex::encode(bytes));
println!(
"Block: {} ({})",
"Block: {} ({}) | Tx Index: {}",
block_number,
receipt.block_hash.unwrap_or_default()
receipt.block_hash.unwrap_or_default(),
tx_index
);
println!("Gas used: {}", gas_used);
let estimate_type = if use_fallback {
Expand Down
37 changes: 36 additions & 1 deletion crates/core/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ pub fn copy_memory(memory: &[u8], offset: usize, length: usize) -> Vec<u8> {
}

/// Parse trace memory from Geth format (hex strings) to bytes.
///
/// Accepts entries with or without an `0x` prefix — Anvil began emitting prefixed
/// memory words after revm-inspectors v0.38.1 (Foundry v1.7.0), while geth/erigon
/// and older Anvil emit bare hex.
pub fn parse_trace_memory(memory: Vec<String>) -> Vec<u8> {
memory
.join("")
.iter()
.map(|s| s.strip_prefix("0x").unwrap_or(s))
.collect::<String>()
.chars()
.collect::<Vec<char>>()
.chunks(2)
Expand Down Expand Up @@ -253,3 +259,32 @@ pub fn compute_state_updates(

Ok((state_updates, skipped_opcodes, total_call_gas))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_trace_memory_handles_both_prefixed_and_bare_hex() {
let bare = vec![
"00000000000000000000000000000000000000000000000000000000000000ff".to_string(),
"1100000000000000000000000000000000000000000000000000000000000000".to_string(),
];
let prefixed = vec![
"0x00000000000000000000000000000000000000000000000000000000000000ff".to_string(),
"0x1100000000000000000000000000000000000000000000000000000000000000".to_string(),
];
let mixed = vec![
"0x00000000000000000000000000000000000000000000000000000000000000ff".to_string(),
"1100000000000000000000000000000000000000000000000000000000000000".to_string(),
];

let expected = parse_trace_memory(bare);
assert_eq!(expected.len(), 64);
assert_eq!(expected[31], 0xff);
assert_eq!(expected[32], 0x11);

assert_eq!(parse_trace_memory(prefixed), expected);
assert_eq!(parse_trace_memory(mixed), expected);
}
}
10 changes: 10 additions & 0 deletions crates/evmsketch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gas-analyzer-rpc = { path = "../rpc" }
gas-analyzer-estimator = { path = "../gas-estimator" }
alloy = { version = "1.0.37", features = ["rpc", "rpc-types"] }
alloy-eips = "1.0.37"
alloy-evm = "0.25"
alloy-provider = { version = "1.0.37", features = ["debug-api"] }
anyhow = "1.0.98"
tokio = { version = "1", features = ["rt"] }
Expand All @@ -17,3 +18,12 @@ reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1"
revm = { version = "31.0.1" }
sp1-cc-host-executor = { git = "https://github.com/BreadchainCoop/sp1-contract-call", branch = "cancun-v1", package = "sp1-cc-host-executor" }
sp1-cc-client-executor = { git = "https://github.com/BreadchainCoop/sp1-contract-call", branch = "cancun-v1", package = "sp1-cc-client-executor" }

[dev-dependencies]
alloy-consensus = "1"
alloy-hardforks = "0.4"
alloy-json-rpc = "1"
alloy-rpc-client = "1"
alloy-transport = "1"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
tower = "0.5"
Loading
Loading