diff --git a/Cargo.lock b/Cargo.lock index 4f20a1840..78704270d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,17 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.3" @@ -821,6 +832,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", +] + [[package]] name = "const-hex" version = "1.10.0" @@ -1189,6 +1219,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "dotenv" version = "0.15.0" @@ -1472,7 +1508,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.32", - "toml", + "toml 0.7.3", "walkdir", ] @@ -2020,6 +2056,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.7", +] [[package]] name = "hashbrown" @@ -2027,7 +2066,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" dependencies = [ - "ahash", + "ahash 0.8.3", ] [[package]] @@ -2385,6 +2424,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonrpsee" version = "0.20.1" @@ -2783,7 +2833,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa8ebbd1a9e57bbab77b9facae7f5136aea44c356943bf9a198f647da64285d6" dependencies = [ - "ahash", + "ahash 0.8.3", "metrics-macros", "portable-atomic", ] @@ -3104,6 +3154,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "overload" version = "0.1.1" @@ -3202,12 +3262,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "path-slash" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -3251,6 +3323,51 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "pest_meta" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + [[package]] name = "petgraph" version = "0.6.3" @@ -3906,6 +4023,17 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -3930,6 +4058,7 @@ version = "0.1.0-beta" dependencies = [ "anyhow", "clap", + "config", "dotenv", "ethers", "itertools 0.11.0", @@ -3937,6 +4066,7 @@ dependencies = [ "metrics-exporter-prometheus", "metrics-process", "metrics-util", + "paste", "rundler-builder", "rundler-pool", "rundler-provider", @@ -4274,6 +4404,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -5268,6 +5408,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.7.3" @@ -5546,6 +5695,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.9.5" @@ -6056,6 +6211,15 @@ version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/bin/rundler/Cargo.toml b/bin/rundler/Cargo.toml index c1c410c6b..cb930cf52 100644 --- a/bin/rundler/Cargo.toml +++ b/bin/rundler/Cargo.toml @@ -21,6 +21,7 @@ rundler-utils = { path = "../../crates/utils" } # CLI dependencies anyhow.workspace = true +config = "0.13.4" clap = { version = "4.4.4", features = ["derive", "env"] } dotenv = "0.15.0" ethers.workspace = true @@ -29,6 +30,7 @@ metrics = "0.21.0" metrics-exporter-prometheus = "0.12.0" metrics-process = "1.0.10" metrics-util = "0.15.0" +paste = "1.0" rusoto_core = { version = "0.48.0", default-features = false, features = ["rustls"] } rusoto_s3 = { version = "0.48.0", default-features = false, features = ["rustls"] } serde.workspace = true diff --git a/bin/rundler/chain_specs/arbitrum.toml b/bin/rundler/chain_specs/arbitrum.toml new file mode 100644 index 000000000..b402c0694 --- /dev/null +++ b/bin/rundler/chain_specs/arbitrum.toml @@ -0,0 +1,8 @@ +name = "Arbitrum" +id = 42161 + +calldata_pre_verification_gas = true +l1_gas_oracle_contract_type = "ARBITRUM_NITRO" +l1_gas_oracle_contract_address = "0x00000000000000000000000000000000000000C8" + +supports_eip1559 = false diff --git a/bin/rundler/chain_specs/arbitrum_goerli.toml b/bin/rundler/chain_specs/arbitrum_goerli.toml new file mode 100644 index 000000000..8ecff8b37 --- /dev/null +++ b/bin/rundler/chain_specs/arbitrum_goerli.toml @@ -0,0 +1,4 @@ +base = "arbitrum" + +name = "Arbitrum Goerli" +id = 421613 diff --git a/bin/rundler/chain_specs/arbitrum_sepolia.toml b/bin/rundler/chain_specs/arbitrum_sepolia.toml new file mode 100644 index 000000000..91a51acb1 --- /dev/null +++ b/bin/rundler/chain_specs/arbitrum_sepolia.toml @@ -0,0 +1,4 @@ +base = "arbitrum" + +name = "Arbitrum Sepolia" +id = 421614 diff --git a/bin/rundler/chain_specs/base.toml b/bin/rundler/chain_specs/base.toml new file mode 100644 index 000000000..d874c3c52 --- /dev/null +++ b/bin/rundler/chain_specs/base.toml @@ -0,0 +1,10 @@ +name = "Base" +id = 8453 + +calldata_pre_verification_gas = true +l1_gas_oracle_contract_type = "OPTIMISM_BEDROCK" +l1_gas_oracle_contract_address = "0x420000000000000000000000000000000000000F" +include_l1_gas_in_gas_limit = false + +priority_fee_oracle_type = "USAGE_BASED" +min_max_priority_fee_per_gas = "0x0186A0" # 100_000 diff --git a/bin/rundler/chain_specs/base_goerli.toml b/bin/rundler/chain_specs/base_goerli.toml new file mode 100644 index 000000000..9d213383d --- /dev/null +++ b/bin/rundler/chain_specs/base_goerli.toml @@ -0,0 +1,4 @@ +base = "base" + +name = "Base Goerli" +id = 84531 diff --git a/bin/rundler/chain_specs/base_sepolia.toml b/bin/rundler/chain_specs/base_sepolia.toml new file mode 100644 index 000000000..2c81d0a68 --- /dev/null +++ b/bin/rundler/chain_specs/base_sepolia.toml @@ -0,0 +1,4 @@ +base = "base" + +name = "Base Sepolia" +id = 84532 diff --git a/bin/rundler/chain_specs/ethereum.toml b/bin/rundler/chain_specs/ethereum.toml new file mode 100644 index 000000000..83096a0c4 --- /dev/null +++ b/bin/rundler/chain_specs/ethereum.toml @@ -0,0 +1,2 @@ +name = "Ethereum" +id = 1 diff --git a/bin/rundler/chain_specs/ethereum_goerli.toml b/bin/rundler/chain_specs/ethereum_goerli.toml new file mode 100644 index 000000000..a887aec98 --- /dev/null +++ b/bin/rundler/chain_specs/ethereum_goerli.toml @@ -0,0 +1,4 @@ +base = "ethereum" + +name = "Ethereum Goerli" +id = 5 diff --git a/bin/rundler/chain_specs/ethereum_sepolia.toml b/bin/rundler/chain_specs/ethereum_sepolia.toml new file mode 100644 index 000000000..83e9bac32 --- /dev/null +++ b/bin/rundler/chain_specs/ethereum_sepolia.toml @@ -0,0 +1,4 @@ +base = "ethereum" + +name = "Ethereum Sepolia" +id = 11155111 diff --git a/bin/rundler/chain_specs/optimism.toml b/bin/rundler/chain_specs/optimism.toml new file mode 100644 index 000000000..4abde22c7 --- /dev/null +++ b/bin/rundler/chain_specs/optimism.toml @@ -0,0 +1,10 @@ +name = "Optimism" +id = 10 + +calldata_pre_verification_gas = true +l1_gas_oracle_contract_type = "OPTIMISM_BEDROCK" +l1_gas_oracle_contract_address = "0x420000000000000000000000000000000000000F" +include_l1_gas_in_gas_limit = false + +priority_fee_oracle_type = "USAGE_BASED" +min_max_priority_fee_per_gas = "0x0186A0" # 100_000 diff --git a/bin/rundler/chain_specs/optimism_goerli.toml b/bin/rundler/chain_specs/optimism_goerli.toml new file mode 100644 index 000000000..5e579c93d --- /dev/null +++ b/bin/rundler/chain_specs/optimism_goerli.toml @@ -0,0 +1,4 @@ +base = "optimism" + +name = "Optimism Goerli" +id = 420 diff --git a/bin/rundler/chain_specs/optimism_sepolia.toml b/bin/rundler/chain_specs/optimism_sepolia.toml new file mode 100644 index 000000000..d806657ab --- /dev/null +++ b/bin/rundler/chain_specs/optimism_sepolia.toml @@ -0,0 +1,4 @@ +base = "optimism" + +name = "Optimism Sepolia" +id = 11155420 diff --git a/bin/rundler/chain_specs/polygon.toml b/bin/rundler/chain_specs/polygon.toml new file mode 100644 index 000000000..ac826b4b4 --- /dev/null +++ b/bin/rundler/chain_specs/polygon.toml @@ -0,0 +1,5 @@ +name = "Polygon" +id = 137 + +priority_fee_oracle_type = "USAGE_BASED" +min_max_priority_fee_per_gas = "0x06FC23AC00" # 30_000_000_000 diff --git a/bin/rundler/chain_specs/polygon_mumbai.toml b/bin/rundler/chain_specs/polygon_mumbai.toml new file mode 100644 index 000000000..af6d76374 --- /dev/null +++ b/bin/rundler/chain_specs/polygon_mumbai.toml @@ -0,0 +1,6 @@ +base = "polygon" + +name = "Polygon Mumbai" +id = 80001 + +min_max_priority_fee_per_gas = "0x59682F00" # 1_500_000_000 diff --git a/bin/rundler/src/cli/builder.rs b/bin/rundler/src/cli/builder.rs index 057f46082..1b9ebe260 100644 --- a/bin/rundler/src/cli/builder.rs +++ b/bin/rundler/src/cli/builder.rs @@ -26,6 +26,7 @@ use rundler_task::{ server::{connect_with_retries_shutdown, format_socket_addr}, spawn_tasks_with_shutdown, }; +use rundler_types::chain::ChainSpec; use rundler_utils::emit::{self, WithEntryPoint, EVENT_CHANNEL_CAPACITY}; use tokio::sync::broadcast; @@ -175,6 +176,7 @@ impl BuilderArgs { /// common and builder specific arguments. pub async fn to_args( &self, + chain_spec: ChainSpec, common: &CommonArgs, remote_address: Option, ) -> anyhow::Result { @@ -197,13 +199,8 @@ impl BuilderArgs { }; Ok(BuilderTaskArgs { + chain_spec, rpc_url, - entry_point_address: common - .entry_points - .get(0) - .context("should have at least one entry point")? - .parse() - .context("should parse entry point address")?, private_key: self.private_key.clone(), aws_kms_key_ids: self.aws_kms_key_ids.clone(), aws_kms_region: common @@ -212,7 +209,6 @@ impl BuilderArgs { .context("should be a valid aws region")?, redis_uri: self.redis_uri.clone(), redis_lock_ttl_millis: self.redis_lock_ttl_millis, - chain_id: common.chain_id, max_bundle_size: self.max_bundle_size, max_bundle_gas: common.max_bundle_gas, submit_url, @@ -220,7 +216,7 @@ impl BuilderArgs { priority_fee_mode, sender_type: self.sender_type, eth_poll_interval: Duration::from_millis(common.eth_poll_interval_millis), - sim_settings: common.try_into()?, + sim_settings: common.into(), mempool_configs, max_blocks_to_wait_for_mine: self.max_blocks_to_wait_for_mine, replacement_fee_percent_increase: self.replacement_fee_percent_increase, @@ -249,7 +245,11 @@ pub struct BuilderCliArgs { pool_url: String, } -pub async fn run(builder_args: BuilderCliArgs, common_args: CommonArgs) -> anyhow::Result<()> { +pub async fn run( + chain_spec: ChainSpec, + builder_args: BuilderCliArgs, + common_args: CommonArgs, +) -> anyhow::Result<()> { let BuilderCliArgs { builder: builder_args, pool_url, @@ -260,6 +260,7 @@ pub async fn run(builder_args: BuilderCliArgs, common_args: CommonArgs) -> anyho let task_args = builder_args .to_args( + chain_spec, &common_args, Some(format_socket_addr(&builder_args.host, builder_args.port).parse()?), ) diff --git a/bin/rundler/src/cli/chain_spec.rs b/bin/rundler/src/cli/chain_spec.rs new file mode 100644 index 000000000..7330ebdc7 --- /dev/null +++ b/bin/rundler/src/cli/chain_spec.rs @@ -0,0 +1,120 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +use config::{Config, Environment, File, FileFormat}; +use paste::paste; +use rundler_types::chain::ChainSpec; + +/// Resolve the chain spec from the network flag and a chain spec file +pub fn resolve_chain_spec(network: &Option, file: &Option) -> ChainSpec { + // get the base config from the hierarchy of + // - ENV + // - file + // - network flag + + let mut base_getter = Config::builder(); + if let Some(file) = &file { + base_getter = base_getter.add_source(File::with_name(file.as_str())); + } + if let Some(network) = &network { + base_getter = base_getter.add_source(File::from_str( + get_hardcoded_chain_spec(network.to_lowercase().as_str()), + FileFormat::Toml, + )); + } + let base_config = base_getter + .add_source(Environment::with_prefix("CHAIN")) + .build() + .expect("should build config"); + let base = base_config.get::("base").ok(); + + // construct the config from the hierarchy of + // - ENV + // - file + // - network flag + // - base (if defined) + // - defaults + + let default = serde_json::to_string(&ChainSpec::default()).expect("should serialize to string"); + let mut config_builder = + Config::builder().add_source(File::from_str(default.as_str(), FileFormat::Json)); + + if let Some(base) = base { + config_builder = config_builder.add_source(File::from_str( + get_hardcoded_chain_spec(base.as_str()), + FileFormat::Toml, + )); + } + if let Some(file) = &file { + config_builder = config_builder.add_source(File::with_name(file.as_str())); + } + if let Some(network) = &network { + config_builder = config_builder.add_source(File::from_str( + get_hardcoded_chain_spec(network.to_lowercase().as_str()), + FileFormat::Toml, + )); + } + let c = config_builder + .add_source(Environment::with_prefix("CHAIN")) + .build() + .expect("should build config"); + + let id = c.get::("id").ok(); + if let Some(id) = id { + if id == 0 { + panic!("chain id must be non-zero"); + } + } else { + panic!("chain id must be defined"); + } + + c.try_deserialize().expect("should deserialize config") +} + +macro_rules! define_hardcoded_chain_specs { + ($($network:ident),+) => { + paste! { + $( + const [< $network:upper _SPEC >]: &str = include_str!(concat!("../../chain_specs/", stringify!($network), ".toml")); + )+ + + fn get_hardcoded_chain_spec(network: &str) -> &'static str { + match network { + $( + stringify!($network) => [< $network:upper _SPEC >], + )+ + _ => panic!("unknown hardcoded network: {}", network), + } + } + + pub const HARDCODED_CHAIN_SPECS: &[&'static str] = &[$(stringify!($network),)+]; + } + }; +} + +define_hardcoded_chain_specs!( + ethereum, + ethereum_goerli, + ethereum_sepolia, + optimism, + optimism_goerli, + optimism_sepolia, + base, + base_goerli, + base_sepolia, + arbitrum, + arbitrum_goerli, + arbitrum_sepolia, + polygon, + polygon_mumbai +); diff --git a/bin/rundler/src/cli/mod.rs b/bin/rundler/src/cli/mod.rs index c1670be40..53d465fac 100644 --- a/bin/rundler/src/cli/mod.rs +++ b/bin/rundler/src/cli/mod.rs @@ -15,6 +15,7 @@ use anyhow::Context; use clap::{builder::PossibleValuesParser, Args, Parser, Subcommand}; mod builder; +mod chain_spec; mod json; mod metrics; mod node; @@ -48,11 +49,14 @@ pub async fn run() -> anyhow::Result<()> { ) .context("metrics server should start")?; + let cs = chain_spec::resolve_chain_spec(&opt.common.network, &opt.common.chain_spec); + tracing::info!("Chain spec: {:#?}", cs); + match opt.command { - Command::Node(args) => node::run(*args, opt.common).await?, - Command::Pool(args) => pool::run(args, opt.common).await?, - Command::Rpc(args) => rpc::run(args, opt.common).await?, - Command::Builder(args) => builder::run(args, opt.common).await?, + Command::Node(args) => node::run(cs, *args, opt.common).await?, + Command::Pool(args) => pool::run(cs, args, opt.common).await?, + Command::Rpc(args) => rpc::run(cs, args, opt.common).await?, + Command::Builder(args) => builder::run(cs, args, opt.common).await?, } tracing::info!("Shutdown, goodbye"); @@ -91,26 +95,24 @@ enum Command { #[derive(Debug, Args)] #[command(next_help_heading = "Common")] pub struct CommonArgs { - /// Entry point address to target + /// Network flag #[arg( - long = "entry_points", - name = "entry_points", - env = "ENTRY_POINTS", - default_values_t = Vec::::new(), // required or will error - value_delimiter = ',', - global = true - )] - entry_points: Vec, - - /// Chain ID to target + long = "network", + name = "network", + env = "NETWORK", + value_parser = PossibleValuesParser::new(chain_spec::HARDCODED_CHAIN_SPECS), + global = true) + ] + network: Option, + + /// Chain spec file path #[arg( - long = "chain_id", - name = "chain_id", - env = "CHAIN_ID", - default_value = "1337", + long = "chain_spec", + name = "chain_spec", + env = "CHAIN_SPEC", global = true )] - chain_id: u64, + chain_spec: Option, /// ETH Node HTTP URL to connect to #[arg( @@ -288,7 +290,6 @@ impl TryFrom<&CommonArgs> for PrecheckSettings { fn try_from(value: &CommonArgs) -> anyhow::Result { Ok(Self { - chain_id: value.chain_id, max_verification_gas: value.max_verification_gas.into(), max_total_execution_gas: value.max_bundle_gas.into(), bundle_priority_fee_overhead_percent: value.bundle_priority_fee_overhead_percent, diff --git a/bin/rundler/src/cli/node/mod.rs b/bin/rundler/src/cli/node/mod.rs index d76434c1e..f4b38ceaa 100644 --- a/bin/rundler/src/cli/node/mod.rs +++ b/bin/rundler/src/cli/node/mod.rs @@ -16,6 +16,7 @@ use rundler_builder::{BuilderEvent, BuilderTask, LocalBuilderBuilder}; use rundler_pool::{LocalPoolBuilder, PoolEvent, PoolTask}; use rundler_rpc::RpcTask; use rundler_task::spawn_tasks_with_shutdown; +use rundler_types::chain::ChainSpec; use rundler_utils::emit::{self, WithEntryPoint, EVENT_CHANNEL_CAPACITY}; use tokio::sync::broadcast; @@ -43,19 +44,28 @@ pub struct NodeCliArgs { rpc: RpcArgs, } -pub async fn run(bundler_args: NodeCliArgs, common_args: CommonArgs) -> anyhow::Result<()> { +pub async fn run( + chain_spec: ChainSpec, + bundler_args: NodeCliArgs, + common_args: CommonArgs, +) -> anyhow::Result<()> { let NodeCliArgs { pool: pool_args, builder: builder_args, rpc: rpc_args, } = bundler_args; - let pool_task_args = pool_args.to_args(&common_args, None).await?; - let builder_task_args = builder_args.to_args(&common_args, None).await?; + let pool_task_args = pool_args + .to_args(chain_spec.clone(), &common_args, None) + .await?; + let builder_task_args = builder_args + .to_args(chain_spec.clone(), &common_args, None) + .await?; let rpc_task_args = rpc_args.to_args( + chain_spec.clone(), &common_args, (&common_args).try_into()?, - (&common_args).try_into()?, + (&common_args).into(), (&common_args).try_into()?, )?; diff --git a/bin/rundler/src/cli/pool.rs b/bin/rundler/src/cli/pool.rs index 2301a0cd9..52b9a7e27 100644 --- a/bin/rundler/src/cli/pool.rs +++ b/bin/rundler/src/cli/pool.rs @@ -19,6 +19,7 @@ use ethers::types::{Chain, H256}; use rundler_pool::{LocalPoolBuilder, PoolConfig, PoolTask, PoolTaskArgs}; use rundler_sim::MempoolConfig; use rundler_task::spawn_tasks_with_shutdown; +use rundler_types::chain::ChainSpec; use rundler_utils::emit::{self, EVENT_CHANNEL_CAPACITY}; use tokio::sync::broadcast; @@ -124,6 +125,7 @@ impl PoolArgs { /// common and op pool specific arguments. pub async fn to_args( &self, + chain_spec: ChainSpec, common: &CommonArgs, remote_address: Option, ) -> anyhow::Result { @@ -146,42 +148,35 @@ impl PoolArgs { }; tracing::info!("Mempool channel configs: {:?}", mempool_channel_configs); - let pool_configs = common - .entry_points - .iter() - .map(|ep| { - let entry_point = ep.parse().context("Invalid entry_points argument")?; - Ok(PoolConfig { - entry_point, - chain_id: common.chain_id, - // Currently use the same shard count as the number of builders - num_shards: common.num_builders, - max_userops_per_sender: self.max_userops_per_sender, - min_replacement_fee_increase_percentage: self - .min_replacement_fee_increase_percentage, - max_size_of_pool_bytes: self.max_size_in_bytes, - blocklist: blocklist.clone(), - allowlist: allowlist.clone(), - precheck_settings: common.try_into()?, - sim_settings: common.try_into()?, - mempool_channel_configs: mempool_channel_configs.clone(), - throttled_entity_mempool_count: self.throttled_entity_mempool_count, - throttled_entity_live_blocks: self.throttled_entity_live_blocks, - }) - }) - .collect::>>()?; + let chain_id = chain_spec.id; + let pool_config = PoolConfig { + entry_point: chain_spec.entry_point_address, + chain_id, + // Currently use the same shard count as the number of builders + num_shards: common.num_builders, + max_userops_per_sender: self.max_userops_per_sender, + min_replacement_fee_increase_percentage: self.min_replacement_fee_increase_percentage, + max_size_of_pool_bytes: self.max_size_in_bytes, + blocklist: blocklist.clone(), + allowlist: allowlist.clone(), + precheck_settings: common.try_into()?, + sim_settings: common.into(), + mempool_channel_configs: mempool_channel_configs.clone(), + throttled_entity_mempool_count: self.throttled_entity_mempool_count, + throttled_entity_live_blocks: self.throttled_entity_live_blocks, + }; Ok(PoolTaskArgs { - chain_id: common.chain_id, + chain_spec, chain_history_size: self .chain_history_size - .unwrap_or_else(|| default_chain_history_size(common.chain_id)), + .unwrap_or_else(|| default_chain_history_size(chain_id)), http_url: common .node_http .clone() .context("pool requires node_http arg")?, http_poll_interval: Duration::from_millis(common.eth_poll_interval_millis), - pool_configs, + pool_configs: vec![pool_config], remote_address, chain_update_channel_capacity: self.chain_update_channel_capacity.unwrap_or(1024), }) @@ -214,11 +209,16 @@ pub struct PoolCliArgs { pool: PoolArgs, } -pub async fn run(pool_args: PoolCliArgs, common_args: CommonArgs) -> anyhow::Result<()> { +pub async fn run( + chain_spec: ChainSpec, + pool_args: PoolCliArgs, + common_args: CommonArgs, +) -> anyhow::Result<()> { let PoolCliArgs { pool: pool_args } = pool_args; let (event_sender, event_rx) = broadcast::channel(EVENT_CHANNEL_CAPACITY); let task_args = pool_args .to_args( + chain_spec, &common_args, Some(format!("{}:{}", pool_args.host, pool_args.port).parse()?), ) diff --git a/bin/rundler/src/cli/rpc.rs b/bin/rundler/src/cli/rpc.rs index 80980d6f9..b1d0b46f8 100644 --- a/bin/rundler/src/cli/rpc.rs +++ b/bin/rundler/src/cli/rpc.rs @@ -20,6 +20,7 @@ use rundler_pool::RemotePoolClient; use rundler_rpc::{EthApiSettings, RpcTask, RpcTaskArgs}; use rundler_sim::{EstimationSettings, PrecheckSettings}; use rundler_task::{server::connect_with_retries_shutdown, spawn_tasks_with_shutdown}; +use rundler_types::chain::ChainSpec; use super::CommonArgs; @@ -81,6 +82,7 @@ impl RpcArgs { #[allow(clippy::too_many_arguments)] pub fn to_args( &self, + chain_spec: ChainSpec, common: &CommonArgs, precheck_settings: PrecheckSettings, eth_api_settings: EthApiSettings, @@ -93,19 +95,13 @@ impl RpcArgs { .collect::, _>>()?; Ok(RpcTaskArgs { + chain_spec, port: self.port, host: self.host.clone(), - entry_points: common - .entry_points - .iter() - .map(|ep| ep.parse()) - .collect::, _>>() - .context("Invalid entry_points argument")?, rpc_url: common .node_http .clone() .context("rpc requires node_http arg")?, - chain_id: common.chain_id, api_namespaces: apis, precheck_settings, eth_api_settings, @@ -141,7 +137,11 @@ pub struct RpcCliArgs { builder_url: String, } -pub async fn run(rpc_args: RpcCliArgs, common_args: CommonArgs) -> anyhow::Result<()> { +pub async fn run( + chain_spec: ChainSpec, + rpc_args: RpcCliArgs, + common_args: CommonArgs, +) -> anyhow::Result<()> { let RpcCliArgs { rpc: rpc_args, pool_url, @@ -149,6 +149,7 @@ pub async fn run(rpc_args: RpcCliArgs, common_args: CommonArgs) -> anyhow::Resul } = rpc_args; let task_args = rpc_args.to_args( + chain_spec, &common_args, (&common_args).try_into()?, (&common_args).into(), diff --git a/crates/builder/src/bundle_proposer.rs b/crates/builder/src/bundle_proposer.rs index 10a841f06..14f6bdb62 100644 --- a/crates/builder/src/bundle_proposer.rs +++ b/crates/builder/src/bundle_proposer.rs @@ -37,8 +37,8 @@ use rundler_sim::{ SimulationSuccess, SimulationViolation, Simulator, ViolationError, }; use rundler_types::{ - Entity, EntityType, EntityUpdate, EntityUpdateType, GasFees, Timestamp, UserOperation, - UserOpsPerAggregator, + chain::ChainSpec, Entity, EntityType, EntityUpdate, EntityUpdateType, GasFees, Timestamp, + UserOperation, UserOpsPerAggregator, }; use rundler_utils::{emit::WithEntryPoint, math}; use tokio::{sync::broadcast, try_join}; @@ -104,7 +104,7 @@ where #[derive(Debug)] pub(crate) struct Settings { - pub(crate) chain_id: u64, + pub(crate) chain_spec: ChainSpec, pub(crate) max_bundle_size: u64, pub(crate) max_bundle_gas: u64, pub(crate) beneficiary: Address, @@ -229,8 +229,8 @@ where entry_point, provider: provider.clone(), fee_estimator: FeeEstimator::new( + &settings.chain_spec, provider, - settings.chain_id, settings.priority_fee_mode, settings.bundle_priority_fee_overhead_percent, ), @@ -272,10 +272,9 @@ where // Check if the pvg is enough let required_pvg = gas::calc_required_pre_verification_gas( - &op.uo, - self.entry_point.address(), + &self.settings.chain_spec, self.provider.clone(), - self.settings.chain_id, + &op.uo, base_fee, ) .await @@ -399,8 +398,8 @@ where // Skip this op if the bundle does not have enough remaining gas to execute it. let required_gas = get_gas_required_for_op( + &self.settings.chain_spec, gas_spent, - self.settings.chain_id, ov, &op, simulation.requires_post_op, @@ -441,8 +440,8 @@ where // Update the running gas that would need to be be spent to execute the bundle so far. gas_spent += gas::user_operation_execution_gas_limit( + &self.settings.chain_spec, &op, - self.settings.chain_id, false, simulation.requires_post_op, ); @@ -513,7 +512,7 @@ where // sum up the gas needed for all the ops in the bundle // and apply an overhead multiplier let gas = math::increase_by_percent( - context.get_bundle_gas_limit(self.settings.chain_id), + context.get_bundle_gas_limit(&self.settings.chain_spec), BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT, ); @@ -798,8 +797,8 @@ where // Here we use optimistic gas limits for the UOs by assuming none of the paymaster UOs use postOp calls. // This way after simulation once we have determined if each UO actually uses a postOp call or not we can still pack a full bundle let gas = gas::user_operation_execution_gas_limit( + &self.settings.chain_spec, &op.uo, - self.settings.chain_id, false, false, ); @@ -830,7 +829,7 @@ where } fn op_hash(&self, op: &UserOperation) -> H256 { - op.op_hash(self.entry_point.address(), self.settings.chain_id) + op.op_hash(self.entry_point.address(), self.settings.chain_spec.id) } } @@ -1021,23 +1020,23 @@ impl ProposalContext { .collect() } - fn get_bundle_gas_limit(&self, chain_id: u64) -> U256 { + fn get_bundle_gas_limit(&self, chain_spec: &ChainSpec) -> U256 { let ov = GasOverheads::default(); let mut gas_spent = ov.transaction_gas_overhead; let mut max_gas = U256::zero(); for op_with_sim in self.iter_ops_with_simulations() { let op = &op_with_sim.op; let required_gas = get_gas_required_for_op( + chain_spec, gas_spent, - chain_id, ov, op, op_with_sim.simulation.requires_post_op, ); max_gas = cmp::max(max_gas, required_gas); gas_spent += gas::user_operation_gas_limit( + chain_spec, op, - chain_id, false, op_with_sim.simulation.requires_post_op, ); @@ -1215,8 +1214,8 @@ impl ProposalContext { } fn get_gas_required_for_op( + chain_spec: &ChainSpec, gas_spent: U256, - chain_id: u64, ov: GasOverheads, op: &UserOperation, requires_post_op: bool, @@ -1228,7 +1227,7 @@ fn get_gas_required_for_op( }; gas_spent - + gas::user_operation_pre_verification_gas_limit(op, chain_id, false) + + gas::user_operation_pre_verification_gas_limit(chain_spec, op, false) + op.verification_gas_limit * 2 + op.call_gas_limit + post_exec_req_gas @@ -1754,9 +1753,9 @@ mod tests { #[tokio::test] async fn test_bundle_gas_limit() { + let cs = ChainSpec::default(); let op1 = op_with_gas(100_000.into(), 100_000.into(), 1_000_000.into(), false); let op2 = op_with_gas(100_000.into(), 100_000.into(), 200_000.into(), false); - let chain_id = 1; let mut groups_by_aggregator = LinkedHashMap::new(); groups_by_aggregator.insert( None, @@ -1795,14 +1794,14 @@ mod tests { + 5_000 + 21_000; - assert_eq!(context.get_bundle_gas_limit(chain_id), expected_gas_limit); + assert_eq!(context.get_bundle_gas_limit(&cs), expected_gas_limit); } #[tokio::test] async fn test_bundle_gas_limit_with_paymaster_op() { + let cs = ChainSpec::default(); let op1 = op_with_gas(100_000.into(), 100_000.into(), 1_000_000.into(), true); // has paymaster let op2 = op_with_gas(100_000.into(), 100_000.into(), 200_000.into(), false); - let chain_id = 1; let mut groups_by_aggregator = LinkedHashMap::new(); groups_by_aggregator.insert( None, @@ -1831,7 +1830,7 @@ mod tests { rejected_ops: vec![], entity_updates: BTreeMap::new(), }; - let gas_limit = context.get_bundle_gas_limit(chain_id); + let gas_limit = context.get_bundle_gas_limit(&cs); // The gas requirement from the execution of the first UO is: g >= p_1 + 3v_1 + c_1 // The gas requirement from the execution of the second UO is: g >= p_1 + 3v_1 + c_1 + p_2 + 2v_2 + c_2 + 5000 @@ -2079,7 +2078,7 @@ mod tests { entry_point, Arc::new(provider), Settings { - chain_id: 0, + chain_spec: ChainSpec::default(), max_bundle_size, max_bundle_gas: 10_000_000, beneficiary, diff --git a/crates/builder/src/task.rs b/crates/builder/src/task.rs index 62daea0b8..eab1c4c04 100644 --- a/crates/builder/src/task.rs +++ b/crates/builder/src/task.rs @@ -22,7 +22,7 @@ use anyhow::{bail, Context}; use async_trait::async_trait; use ethers::{ providers::{JsonRpcClient, Provider}, - types::{Address, H256}, + types::H256, }; use ethers_signers::Signer; use futures::future; @@ -32,7 +32,7 @@ use rundler_sim::{ MempoolConfig, PriorityFeeMode, SimulateValidationTracerImpl, SimulationSettings, SimulatorImpl, }; use rundler_task::Task; -use rundler_types::contracts::i_entry_point::IEntryPoint; +use rundler_types::{chain::ChainSpec, contracts::i_entry_point::IEntryPoint}; use rundler_utils::{emit::WithEntryPoint, eth, handle}; use rusoto_core::Region; use tokio::{ @@ -56,10 +56,10 @@ use crate::{ /// Builder task arguments #[derive(Debug)] pub struct Args { + /// Chain spec + pub chain_spec: ChainSpec, /// Full node RPC url pub rpc_url: String, - /// Address of the entry point contract this builder targets - pub entry_point_address: Address, /// Private key to use for signing transactions /// If not provided, AWS KMS will be used pub private_key: Option, @@ -72,8 +72,6 @@ pub struct Args { pub redis_uri: String, /// Redis lease TTL in milliseconds pub redis_lock_ttl_millis: u64, - /// Chain ID - pub chain_id: u64, /// Maximum bundle size in number of operations pub max_bundle_size: u64, /// Maximum bundle size in gas limit @@ -156,7 +154,7 @@ where let builder_runnder_handle = self.builder_builder.run( manual_bundling_mode, send_bundle_txs, - vec![self.args.entry_point_address], + vec![self.args.chain_spec.entry_point_address], shutdown_token.clone(), ); @@ -164,7 +162,7 @@ where Some(addr) => { spawn_remote_builder_server( addr, - self.args.chain_id, + self.args.chain_spec.id, builder_handle, shutdown_token, ) @@ -230,8 +228,12 @@ where let signer = if let Some(pk) = &self.args.private_key { info!("Using local signer"); BundlerSigner::Local( - LocalSigner::connect(Arc::clone(&provider), self.args.chain_id, pk.to_owned()) - .await?, + LocalSigner::connect( + Arc::clone(&provider), + self.args.chain_spec.id, + pk.to_owned(), + ) + .await?, ) } else { info!("Using AWS KMS signer"); @@ -243,7 +245,7 @@ where Duration::from_millis(self.args.redis_lock_ttl_millis / 10), KmsSigner::connect( Arc::clone(&provider), - self.args.chain_id, + self.args.chain_spec.id, self.args.aws_kms_region.clone(), self.args.aws_kms_key_ids.clone(), self.args.redis_uri.clone(), @@ -259,7 +261,7 @@ where }; let beneficiary = signer.address(); let proposer_settings = bundle_proposer::Settings { - chain_id: self.args.chain_id, + chain_spec: self.args.chain_spec.clone(), max_bundle_size: self.args.max_bundle_size, max_bundle_gas: self.args.max_bundle_gas, beneficiary, @@ -267,7 +269,10 @@ where bundle_priority_fee_overhead_percent: self.args.bundle_priority_fee_overhead_percent, }; - let entry_point = IEntryPoint::new(self.args.entry_point_address, Arc::clone(&provider)); + let entry_point = IEntryPoint::new( + self.args.chain_spec.entry_point_address, + Arc::clone(&provider), + ); let simulate_validation_tracer = SimulateValidationTracerImpl::new(Arc::clone(&provider), entry_point.clone()); let simulator = SimulatorImpl::new( @@ -284,7 +289,7 @@ where let transaction_sender = self.args.sender_type.into_sender( submit_provider, signer, - self.args.chain_id, + self.args.chain_spec.id, self.args.eth_poll_interval, &self.args.bloxroute_auth_header, )?; @@ -320,7 +325,7 @@ where index, manual_bundling_mode.clone(), send_bundle_rx, - self.args.chain_id, + self.args.chain_spec.id, beneficiary, proposer, entry_point, diff --git a/crates/pool/src/task.rs b/crates/pool/src/task.rs index cc317c243..1ba332e70 100644 --- a/crates/pool/src/task.rs +++ b/crates/pool/src/task.rs @@ -21,7 +21,10 @@ use rundler_sim::{ Prechecker, PrecheckerImpl, SimulateValidationTracerImpl, Simulator, SimulatorImpl, }; use rundler_task::Task; -use rundler_types::contracts::{i_entry_point::IEntryPoint, i_stake_manager::IStakeManager}; +use rundler_types::{ + chain::ChainSpec, + contracts::{i_entry_point::IEntryPoint, i_stake_manager::IStakeManager}, +}; use rundler_utils::{emit::WithEntryPoint, eth, handle}; use tokio::{sync::broadcast, try_join}; use tokio_util::sync::CancellationToken; @@ -37,12 +40,12 @@ use crate::{ /// Arguments for the pool task. #[derive(Debug)] pub struct Args { + /// Chain specification. + pub chain_spec: ChainSpec, /// HTTP URL for the full node. pub http_url: String, /// Poll interval for full node requests. pub http_poll_interval: Duration, - /// ID of the chain this pool is tracking - pub chain_id: u64, /// Number of blocks to keep in the chain history. pub chain_history_size: u64, /// Pool configurations. @@ -65,7 +68,7 @@ pub struct PoolTask { #[async_trait] impl Task for PoolTask { async fn run(mut self: Box, shutdown_token: CancellationToken) -> anyhow::Result<()> { - let chain_id = self.args.chain_id; + let chain_id = self.args.chain_spec.id; tracing::info!("Chain id: {chain_id}"); tracing::info!("Http url: {:?}", self.args.http_url); @@ -88,10 +91,14 @@ impl Task for PoolTask { // create mempools let mut mempools = HashMap::new(); for pool_config in &self.args.pool_configs { - let pool = - PoolTask::create_mempool(pool_config, self.event_sender.clone(), provider.clone()) - .await - .context("should have created mempool")?; + let pool = PoolTask::create_mempool( + self.args.chain_spec.clone(), + pool_config, + self.event_sender.clone(), + provider.clone(), + ) + .await + .context("should have created mempool")?; mempools.insert(pool_config.entry_point, Arc::new(pool)); } @@ -103,8 +110,13 @@ impl Task for PoolTask { let remote_handle = match self.args.remote_address { Some(addr) => { - spawn_remote_mempool_server(self.args.chain_id, pool_handle, addr, shutdown_token) - .await? + spawn_remote_mempool_server( + self.args.chain_spec.id, + pool_handle, + addr, + shutdown_token, + ) + .await? } None => tokio::spawn(async { Ok(()) }), }; @@ -148,6 +160,7 @@ impl PoolTask { } async fn create_mempool( + chain_spec: ChainSpec, pool_config: &PoolConfig, event_sender: broadcast::Sender>, provider: Arc

, @@ -169,6 +182,7 @@ impl PoolTask { let simulate_validation_tracer = SimulateValidationTracerImpl::new(Arc::clone(&provider), i_entry_point.clone()); let prechecker = PrecheckerImpl::new( + chain_spec, Arc::clone(&provider), i_entry_point.clone(), pool_config.precheck_settings, diff --git a/crates/rpc/src/eth/api.rs b/crates/rpc/src/eth/api.rs index fd88a5ff6..dc835be2c 100644 --- a/crates/rpc/src/eth/api.rs +++ b/crates/rpc/src/eth/api.rs @@ -34,6 +34,7 @@ use rundler_sim::{ GasEstimatorImpl, PrecheckSettings, UserOperationOptionalGas, }; use rundler_types::{ + chain::ChainSpec, contracts::i_entry_point::{ IEntryPointCalls, UserOperationEventFilter, UserOperationRevertReasonFilter, }, @@ -72,7 +73,7 @@ where E: EntryPoint, { fn new( - chain_id: u64, + chain_spec: ChainSpec, provider: Arc

, entry_point: E, estimation_settings: EstimationSettings, @@ -82,7 +83,7 @@ where E: Clone, // Add Clone trait bound for E { let gas_estimator = GasEstimatorImpl::new( - chain_id, + chain_spec, provider, entry_point.clone(), estimation_settings, @@ -96,7 +97,7 @@ where pub(crate) struct EthApi { contexts_by_entry_point: HashMap>, provider: Arc

, - chain_id: u64, + chain_spec: ChainSpec, pool: PS, settings: Settings, } @@ -108,9 +109,9 @@ where PS: PoolServer, { pub(crate) fn new( + chain_spec: ChainSpec, provider: Arc

, entry_points: Vec, - chain_id: u64, pool: PS, settings: Settings, estimation_settings: EstimationSettings, @@ -125,13 +126,13 @@ where ( entry_point.address(), EntryPointContext::new( - chain_id, + chain_spec.clone(), Arc::clone(&provider), entry_point, estimation_settings, FeeEstimator::new( + &chain_spec, Arc::clone(&provider), - chain_id, precheck_settings.priority_fee_mode, precheck_settings.bundle_priority_fee_overhead_percent, ), @@ -144,7 +145,7 @@ where settings, contexts_by_entry_point, provider, - chain_id, + chain_spec, pool, } } @@ -238,7 +239,7 @@ where let user_operation = if self.contexts_by_entry_point.contains_key(&to) { self.get_user_operations_from_tx_data(tx.input) .into_iter() - .find(|op| op.op_hash(to, self.chain_id) == hash) + .find(|op| op.op_hash(to, self.chain_spec.id) == hash) .context("matching user operation should be found in tx data")? } else { self.trace_find_user_operation(transaction_hash, hash) @@ -333,7 +334,7 @@ where } pub(crate) async fn chain_id(&self) -> EthResult { - Ok(self.chain_id.into()) + Ok(self.chain_spec.id.into()) } async fn get_user_operation_event_by_hash(&self, hash: H256) -> EthResult> { @@ -493,7 +494,7 @@ where if let Some(uo) = self .get_user_operations_from_tx_data(call_frame.input) .into_iter() - .find(|op| op.op_hash(*to, self.chain_id) == user_op_hash) + .find(|op| op.op_hash(*to, self.chain_spec.id) == user_op_hash) { return Ok(Some(uo)); } diff --git a/crates/rpc/src/rundler.rs b/crates/rpc/src/rundler.rs index 82c0c050d..c3449eb08 100644 --- a/crates/rpc/src/rundler.rs +++ b/crates/rpc/src/rundler.rs @@ -18,6 +18,7 @@ use ethers::types::U256; use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::error::INTERNAL_ERROR_CODE}; use rundler_provider::Provider; use rundler_sim::{FeeEstimator, PrecheckSettings}; +use rundler_types::chain::ChainSpec; use crate::error::rpc_err; @@ -36,11 +37,15 @@ impl

RundlerApi

where P: Provider, { - pub(crate) fn new(provider: Arc

, chain_id: u64, settings: PrecheckSettings) -> Self { + pub(crate) fn new( + chain_spec: &ChainSpec, + provider: Arc

, + settings: PrecheckSettings, + ) -> Self { Self { fee_estimator: FeeEstimator::new( + chain_spec, provider, - chain_id, settings.priority_fee_mode, settings.bundle_priority_fee_overhead_percent, ), diff --git a/crates/rpc/src/task.rs b/crates/rpc/src/task.rs index f5f84324e..5272a91b5 100644 --- a/crates/rpc/src/task.rs +++ b/crates/rpc/src/task.rs @@ -15,10 +15,7 @@ use std::{net::SocketAddr, sync::Arc, time::Duration}; use anyhow::bail; use async_trait::async_trait; -use ethers::{ - providers::{Http, Provider, RetryClient}, - types::Address, -}; +use ethers::providers::{Http, Provider, RetryClient}; use jsonrpsee::{ server::{middleware::ProxyGetRequestLayer, ServerBuilder}, RpcModule, @@ -31,7 +28,7 @@ use rundler_task::{ server::{format_socket_addr, HealthCheck}, Task, }; -use rundler_types::contracts::i_entry_point::IEntryPoint; +use rundler_types::{chain::ChainSpec, contracts::i_entry_point::IEntryPoint}; use rundler_utils::eth; use tokio_util::sync::CancellationToken; use tracing::info; @@ -48,14 +45,12 @@ use crate::{ /// RPC server arguments. #[derive(Debug)] pub struct Args { + /// Chain spec + pub chain_spec: ChainSpec, /// Port to listen on. pub port: u16, /// Host to listen on. pub host: String, - /// List of supported entry points. - pub entry_points: Vec

, - /// Chain ID. - pub chain_id: u64, /// List of API namespaces to enable. pub api_namespaces: Vec, /// Full node RPC URL to use. @@ -90,20 +85,12 @@ where let addr: SocketAddr = format_socket_addr(&self.args.host, self.args.port).parse()?; tracing::info!("Starting rpc server on {}", addr); - if self.args.entry_points.is_empty() { - bail!("No entry points provided"); - } - let provider = eth::new_provider(&self.args.rpc_url, None)?; - let entry_points = self - .args - .entry_points - .iter() - .map(|addr| IEntryPoint::new(*addr, provider.clone())) - .collect(); + let entry_point = + IEntryPoint::new(self.args.chain_spec.entry_point_address, provider.clone()); let mut module = RpcModule::new(()); - self.attach_namespaces(provider, entry_points, &mut module)?; + self.attach_namespaces(provider, entry_point, &mut module)?; let servers: Vec> = vec![Box::new(self.pool.clone()), Box::new(self.builder.clone())]; @@ -162,16 +149,17 @@ where fn attach_namespaces( &self, provider: Arc>>, - entry_points: Vec, + entry_point: E, module: &mut RpcModule<()>, ) -> anyhow::Result<()> { for api in &self.args.api_namespaces { match api { ApiNamespace::Eth => module.merge( EthApi::new( + self.args.chain_spec.clone(), provider.clone(), - entry_points.clone(), - self.args.chain_id, + // TODO: support multiple entry points + vec![entry_point.clone()], self.pool.clone(), self.args.eth_api_settings, self.args.estimation_settings, @@ -183,8 +171,8 @@ where .merge(DebugApi::new(self.pool.clone(), self.builder.clone()).into_rpc())?, ApiNamespace::Rundler => module.merge( RundlerApi::new( + &self.args.chain_spec, provider.clone(), - self.args.chain_id, self.args.precheck_settings, ) .into_rpc(), diff --git a/crates/sim/src/estimation/estimation.rs b/crates/sim/src/estimation/estimation.rs index e0f8e4ae4..d2abf307a 100644 --- a/crates/sim/src/estimation/estimation.rs +++ b/crates/sim/src/estimation/estimation.rs @@ -25,6 +25,7 @@ use mockall::automock; use rand::Rng; use rundler_provider::{EntryPoint, Provider}; use rundler_types::{ + chain::ChainSpec, contracts::{ call_gas_estimation_proxy::{ EstimateCallGasArgs, EstimateCallGasCall, EstimateCallGasContinuation, @@ -97,7 +98,7 @@ pub trait GasEstimator: Send + Sync + 'static { /// Gas estimator implementation #[derive(Debug)] pub struct GasEstimatorImpl { - chain_id: u64, + chain_spec: ChainSpec, provider: Arc

, entry_point: E, settings: Settings, @@ -175,14 +176,14 @@ impl GasEstimator for GasEstimatorImpl { impl GasEstimatorImpl { /// Create a new gas estimator pub fn new( - chain_id: u64, + chain_spec: ChainSpec, provider: Arc

, entry_point: E, settings: Settings, fee_estimator: FeeEstimator

, ) -> Self { Self { - chain_id, + chain_spec, provider, entry_point, settings, @@ -388,11 +389,10 @@ impl GasEstimatorImpl { gas_price: U256, ) -> Result { Ok(gas::estimate_pre_verification_gas( + &self.chain_spec, + self.provider.clone(), &op.max_fill(&self.settings), &op.random_fill(&self.settings), - self.entry_point.address(), - self.provider.clone(), - self.chain_id, gas_price, ) .await?) @@ -416,7 +416,10 @@ mod tests { utils::hex, }; use rundler_provider::{MockEntryPoint, MockProvider, ProviderError}; - use rundler_types::contracts::{get_gas_used::GasUsedResult, i_entry_point::ExecutionResult}; + use rundler_types::{ + chain::L1GasOracleContractType, + contracts::{get_gas_used::GasUsedResult, i_entry_point::ExecutionResult}, + }; use super::*; use crate::PriorityFeeMode; @@ -438,7 +441,12 @@ mod tests { } fn create_fee_estimator(provider: Arc) -> FeeEstimator { - FeeEstimator::new(provider, 0, PriorityFeeMode::BaseFeePercent(0), 0) + FeeEstimator::new( + &ChainSpec::default(), + provider, + PriorityFeeMode::BaseFeePercent(0), + 0, + ) } fn create_estimator( @@ -452,7 +460,7 @@ mod tests { }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - 0, + ChainSpec::default(), provider.clone(), entry, settings, @@ -553,9 +561,15 @@ mod tests { }; // Chose arbitrum + let cs = ChainSpec { + id: Chain::Arbitrum as u64, + calldata_pre_verification_gas: true, + l1_gas_oracle_contract_type: L1GasOracleContractType::ArbitrumNitro, + ..Default::default() + }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - Chain::Arbitrum as u64, + cs, provider.clone(), entry, settings, @@ -605,9 +619,15 @@ mod tests { }; // Chose OP + let cs = ChainSpec { + id: Chain::Optimism as u64, + calldata_pre_verification_gas: true, + l1_gas_oracle_contract_type: L1GasOracleContractType::OptimismBedrock, + ..Default::default() + }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - Chain::Optimism as u64, + cs, provider.clone(), entry, settings, @@ -1251,7 +1271,7 @@ mod tests { let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - 0, + ChainSpec::default(), provider.clone(), entry, settings, diff --git a/crates/sim/src/gas/gas.rs b/crates/sim/src/gas/gas.rs index cddddb6d1..2aaea827b 100644 --- a/crates/sim/src/gas/gas.rs +++ b/crates/sim/src/gas/gas.rs @@ -14,13 +14,10 @@ use std::{cmp, fmt::Debug, sync::Arc}; use anyhow::Context; -use ethers::{ - abi::AbiEncode, - types::{Address, Chain, U256}, -}; +use ethers::{abi::AbiEncode, types::U256}; use rundler_provider::Provider; use rundler_types::{ - chain::{ARBITRUM_CHAIN_IDS, OP_BEDROCK_CHAIN_IDS, POLYGON_CHAIN_IDS}, + chain::{self, ChainSpec, L1GasOracleContractType}, GasFees, UserOperation, }; use rundler_utils::math; @@ -70,28 +67,31 @@ impl Default for GasOverheads { /// Networks that require dynamic pre_verification_gas are typically those that charge extra calldata fees /// that can scale based on dynamic gas prices. pub async fn estimate_pre_verification_gas( + chain_spec: &ChainSpec, + provider: Arc

, full_op: &UserOperation, random_op: &UserOperation, - entry_point: Address, - provider: Arc

, - chain_id: u64, gas_price: U256, ) -> anyhow::Result { let static_gas = calc_static_pre_verification_gas(full_op, true); - let dynamic_gas = match chain_id { - _ if ARBITRUM_CHAIN_IDS.contains(&chain_id) => { + if !chain_spec.calldata_pre_verification_gas { + return Ok(static_gas); + } + + let dynamic_gas = match chain_spec.l1_gas_oracle_contract_type { + L1GasOracleContractType::None => panic!("Chain spec requires calldata pre_verification_gas but no l1_gas_oracle_contract_type is set"), + L1GasOracleContractType::ArbitrumNitro => { provider .clone() - .calc_arbitrum_l1_gas(entry_point, random_op.clone()) + .calc_arbitrum_l1_gas(chain_spec.entry_point_address, random_op.clone()) .await? - } - _ if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) => { + }, + L1GasOracleContractType::OptimismBedrock => { provider .clone() - .calc_optimism_l1_gas(entry_point, random_op.clone(), gas_price) + .calc_optimism_l1_gas(chain_spec.entry_point_address, random_op.clone(), gas_price) .await? - } - _ => U256::zero(), + }, }; Ok(static_gas + dynamic_gas) @@ -101,29 +101,32 @@ pub async fn estimate_pre_verification_gas( /// /// The effective gas price is calculated as min(base_fee + max_priority_fee_per_gas, max_fee_per_gas) pub async fn calc_required_pre_verification_gas( - op: &UserOperation, - entry_point: Address, + chain_spec: &ChainSpec, provider: Arc

, - chain_id: u64, + op: &UserOperation, base_fee: U256, ) -> anyhow::Result { let static_gas = calc_static_pre_verification_gas(op, true); - let dynamic_gas = match chain_id { - _ if ARBITRUM_CHAIN_IDS.contains(&chain_id) => { + if !chain_spec.calldata_pre_verification_gas { + return Ok(static_gas); + } + + let dynamic_gas = match chain_spec.l1_gas_oracle_contract_type { + L1GasOracleContractType::None => panic!("Chain spec requires calldata pre_verification_gas but no l1_gas_oracle_contract_type is set"), + L1GasOracleContractType::ArbitrumNitro => { provider .clone() - .calc_arbitrum_l1_gas(entry_point, op.clone()) + .calc_arbitrum_l1_gas(chain_spec.entry_point_address, op.clone()) .await? - } - _ if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) => { + }, + L1GasOracleContractType::OptimismBedrock => { let gas_price = cmp::min(base_fee + op.max_priority_fee_per_gas, op.max_fee_per_gas); provider .clone() - .calc_optimism_l1_gas(entry_point, op.clone(), gas_price) + .calc_optimism_l1_gas(chain_spec.entry_point_address, op.clone(), gas_price) .await? - } - _ => U256::zero(), + }, }; Ok(static_gas + dynamic_gas) @@ -147,12 +150,12 @@ pub async fn calc_required_pre_verification_gas( /// Returns the gas limit for the user operation that applies to bundle transaction's limit pub fn user_operation_gas_limit( + chain_spec: &ChainSpec, uo: &UserOperation, - chain_id: u64, assume_single_op_bundle: bool, paymaster_post_op: bool, ) -> U256 { - user_operation_pre_verification_gas_limit(uo, chain_id, assume_single_op_bundle) + user_operation_pre_verification_gas_limit(chain_spec, uo, assume_single_op_bundle) + uo.call_gas_limit + uo.verification_gas_limit * verification_gas_limit_multiplier(assume_single_op_bundle, paymaster_post_op) @@ -160,12 +163,12 @@ pub fn user_operation_gas_limit( /// Returns the gas limit for the user operation that applies to bundle transaction's execution limit pub fn user_operation_execution_gas_limit( + chain_spec: &ChainSpec, uo: &UserOperation, - chain_id: u64, assume_single_op_bundle: bool, paymaster_post_op: bool, ) -> U256 { - user_operation_pre_verification_execution_gas_limit(uo, chain_id, assume_single_op_bundle) + user_operation_pre_verification_execution_gas_limit(chain_spec, uo, assume_single_op_bundle) + uo.call_gas_limit + uo.verification_gas_limit * verification_gas_limit_multiplier(assume_single_op_bundle, paymaster_post_op) @@ -173,14 +176,14 @@ pub fn user_operation_execution_gas_limit( /// Returns the static pre-verification gas cost of a user operation pub fn user_operation_pre_verification_execution_gas_limit( + chain_spec: &ChainSpec, uo: &UserOperation, - chain_id: u64, include_fixed_gas_overhead: bool, ) -> U256 { // On some chains (OP bedrock, Arbitrum) the L1 gas fee is charged via pre_verification_gas // but this not part of the EXECUTION gas limit of the transaction. // In such cases we only consider the static portion of the pre_verification_gas in the gas limit. - if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) | ARBITRUM_CHAIN_IDS.contains(&chain_id) { + if chain_spec.calldata_pre_verification_gas { calc_static_pre_verification_gas(uo, include_fixed_gas_overhead) } else { uo.pre_verification_gas @@ -189,14 +192,14 @@ pub fn user_operation_pre_verification_execution_gas_limit( /// Returns the gas limit for the user operation that applies to bundle transaction's limit pub fn user_operation_pre_verification_gas_limit( + chain_spec: &ChainSpec, uo: &UserOperation, - chain_id: u64, include_fixed_gas_overhead: bool, ) -> U256 { // On some chains (OP bedrock) the L1 gas fee is charged via pre_verification_gas // but this not part of the execution TOTAL limit of the transaction. // In such cases we only consider the static portion of the pre_verification_gas in the gas limit. - if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) { + if chain_spec.calldata_pre_verification_gas && !chain_spec.include_l1_gas_in_gas_limit { calc_static_pre_verification_gas(uo, include_fixed_gas_overhead) } else { uo.pre_verification_gas @@ -327,8 +330,8 @@ impl FeeEstimator

{ /// `bundle_priority_fee_overhead_percent` is used to determine the overhead percentage to add /// to the network returned priority fee to ensure the bundle priority fee is high enough. pub fn new( + chain_spec: &ChainSpec, provider: Arc

, - chain_id: u64, priority_fee_mode: PriorityFeeMode, bundle_priority_fee_overhead_percent: u64, ) -> Self { @@ -336,7 +339,7 @@ impl FeeEstimator

{ provider: provider.clone(), priority_fee_mode, bundle_priority_fee_overhead_percent, - fee_oracle: get_fee_oracle(chain_id, provider), + fee_oracle: get_fee_oracle(chain_spec, provider), } } @@ -392,42 +395,23 @@ impl FeeEstimator

{ } } -// TODO move all of this to ChainSpec -/// ETHEREUM_MAINNET_MAX_PRIORITY_FEE_MIN -pub const ETHEREUM_MAINNET_MAX_PRIORITY_FEE_MIN: u64 = 100_000_000; -/// Polygon Mumbai max priority fee min -pub const POLYGON_MUMBAI_MAX_PRIORITY_FEE_MIN: u64 = 1_500_000_000; -/// Polygon Mainnet max priority fee min -pub const POLYGON_MAINNET_MAX_PRIORITY_FEE_MIN: u64 = 30_000_000_000; -/// Optimism Bedrock chains max priority fee min -pub const OPTIMISM_BEDROCK_MAX_PRIORITY_FEE_MIN: u64 = 100_000; - -/// Returns the minimum max priority fee per gas for the given chain id. -pub fn get_min_max_priority_fee_per_gas(chain_id: u64) -> U256 { - match chain_id { - x if x == Chain::Mainnet as u64 => ETHEREUM_MAINNET_MAX_PRIORITY_FEE_MIN.into(), - x if x == Chain::Polygon as u64 => POLYGON_MAINNET_MAX_PRIORITY_FEE_MIN.into(), - x if x == Chain::PolygonMumbai as u64 => POLYGON_MUMBAI_MAX_PRIORITY_FEE_MIN.into(), - x if OP_BEDROCK_CHAIN_IDS.contains(&x) => OPTIMISM_BEDROCK_MAX_PRIORITY_FEE_MIN.into(), - _ => U256::zero(), - } -} - -fn get_fee_oracle

(chain_id: u64, provider: Arc

) -> Arc> +fn get_fee_oracle

(chain_spec: &ChainSpec, provider: Arc

) -> Arc> where P: Provider + Debug, { - let minimum_fee = get_min_max_priority_fee_per_gas(chain_id); - - if ARBITRUM_CHAIN_IDS.contains(&chain_id) { - Arc::new(Box::new(ConstantOracle::new(U256::zero()))) - } else if OP_BEDROCK_CHAIN_IDS.contains(&chain_id) || POLYGON_CHAIN_IDS.contains(&chain_id) { - let config = UsageBasedFeeOracleConfig { - minimum_fee, - ..Default::default() - }; - Arc::new(Box::new(UsageBasedFeeOracle::new(provider, config))) - } else { - Arc::new(Box::new(ProviderOracle::new(provider))) + if !chain_spec.eip1559_enabled { + return Arc::new(Box::new(ConstantOracle::new(U256::zero()))); + } + + match chain_spec.priority_fee_oracle_type { + chain::PriorityFeeOracleType::Provider => Arc::new(Box::new(ProviderOracle::new(provider))), + chain::PriorityFeeOracleType::UsageBased => { + let config = UsageBasedFeeOracleConfig { + minimum_fee: chain_spec.min_max_priority_fee_per_gas, + maximum_fee: chain_spec.max_max_priority_fee_per_gas, + ..Default::default() + }; + Arc::new(Box::new(UsageBasedFeeOracle::new(provider, config))) + } } } diff --git a/crates/sim/src/precheck.rs b/crates/sim/src/precheck.rs index 7111f2f17..f2a0b29dc 100644 --- a/crates/sim/src/precheck.rs +++ b/crates/sim/src/precheck.rs @@ -19,13 +19,10 @@ use ethers::types::{Address, U256}; #[cfg(feature = "test-utils")] use mockall::automock; use rundler_provider::{EntryPoint, Provider}; -use rundler_types::{GasFees, UserOperation}; +use rundler_types::{chain::ChainSpec, GasFees, UserOperation}; use rundler_utils::math; -use crate::{ - gas::{self, get_min_max_priority_fee_per_gas}, - types::ViolationError, -}; +use crate::{gas, types::ViolationError}; /// The min cost of a `CALL` with nonzero value, as required by the spec. pub const MIN_CALL_GAS_LIMIT: U256 = U256([9100, 0, 0, 0]); @@ -47,6 +44,7 @@ pub type PrecheckError = ViolationError; /// Prechecker implementation #[derive(Debug)] pub struct PrecheckerImpl { + chain_spec: ChainSpec, provider: Arc

, entry_point: E, settings: Settings, @@ -58,8 +56,6 @@ pub struct PrecheckerImpl { /// Precheck settings #[derive(Copy, Clone, Debug)] pub struct Settings { - /// Chain ID - pub chain_id: u64, /// Maximum verification gas allowed for a user operation pub max_verification_gas: U256, /// Maximum total execution gas allowed for a user operation @@ -83,7 +79,6 @@ impl Default for Settings { bundle_priority_fee_overhead_percent: 0, priority_fee_mode: gas::PriorityFeeMode::BaseFeePercent(0), max_total_execution_gas: 10_000_000.into(), - chain_id: 1, base_fee_accept_percent: 50, pre_verification_gas_accept_percent: 100, } @@ -140,17 +135,25 @@ impl Prechecker for PrecheckerImpl { impl PrecheckerImpl { /// Create a new prechecker - pub fn new(provider: Arc

, entry_point: E, settings: Settings) -> Self { + pub fn new( + chain_spec: ChainSpec, + provider: Arc

, + entry_point: E, + settings: Settings, + ) -> Self { + let fee_estimator = gas::FeeEstimator::new( + &chain_spec, + provider.clone(), + settings.priority_fee_mode, + settings.bundle_priority_fee_overhead_percent, + ); + Self { - provider: provider.clone(), + chain_spec, + provider, entry_point, settings, - fee_estimator: gas::FeeEstimator::new( - provider, - settings.chain_id, - settings.priority_fee_mode, - settings.bundle_priority_fee_overhead_percent, - ), + fee_estimator, cache: RwLock::new(AsyncDataCache { fees: None }), } } @@ -194,7 +197,6 @@ impl PrecheckerImpl { async_data: AsyncData, ) -> ArrayVec { let Settings { - chain_id, max_verification_gas, max_total_execution_gas, .. @@ -215,7 +217,7 @@ impl PrecheckerImpl { // compute the worst case total gas limit by assuming the UO is in its own bundle and has a postOp call. // This is conservative and potentially may invalidate some very large UOs that would otherwise be valid. - let gas_limit = gas::user_operation_execution_gas_limit(op, chain_id, true, true); + let gas_limit = gas::user_operation_execution_gas_limit(&self.chain_spec, op, true, true); if gas_limit > max_total_execution_gas { violations.push(PrecheckViolation::TotalGasLimitTooHigh( gas_limit, @@ -241,7 +243,7 @@ impl PrecheckerImpl { let min_priority_fee = self.settings.priority_fee_mode.minimum_priority_fee( base_fee, self.settings.base_fee_accept_percent, - get_min_max_priority_fee_per_gas(self.settings.chain_id), + self.chain_spec.min_max_priority_fee_per_gas, ); let min_max_fee = min_base_fee + min_priority_fee; @@ -380,10 +382,9 @@ impl PrecheckerImpl { base_fee: U256, ) -> anyhow::Result { gas::calc_required_pre_verification_gas( - &op, - self.entry_point.address(), + &self.chain_spec, self.provider.clone(), - self.settings.chain_id, + &op, base_fee, ) .await @@ -451,8 +452,12 @@ mod tests { use super::*; - fn create_base_config() -> (MockProvider, MockEntryPoint) { - (MockProvider::new(), MockEntryPoint::new()) + fn create_base_config() -> (ChainSpec, MockProvider, MockEntryPoint) { + ( + ChainSpec::default(), + MockProvider::new(), + MockEntryPoint::new(), + ) } fn get_test_async_data() -> AsyncData { @@ -468,8 +473,9 @@ mod tests { #[tokio::test] async fn test_check_init_code() { - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, Settings::default()); + let (cs, provider, entry_point) = create_base_config(); + let prechecker = + PrecheckerImpl::new(cs, Arc::new(provider), entry_point, Settings::default()); let op = UserOperation { sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), nonce: 100.into(), @@ -498,9 +504,8 @@ mod tests { #[tokio::test] async fn test_check_gas() { - let (provider, entry_point) = create_base_config(); + let (cs, provider, entry_point) = create_base_config(); let test_settings = Settings { - chain_id: 1, max_verification_gas: 5_000_000.into(), max_total_execution_gas: 10_000_000.into(), bundle_priority_fee_overhead_percent: 0, @@ -508,7 +513,7 @@ mod tests { base_fee_accept_percent: 100, pre_verification_gas_accept_percent: 100, }; - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, test_settings); + let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, test_settings); let op = UserOperation { sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), nonce: 100.into(), @@ -540,8 +545,9 @@ mod tests { #[tokio::test] async fn test_check_payer_paymaster_deposit_too_low() { - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, Settings::default()); + let (cs, provider, entry_point) = create_base_config(); + let prechecker = + PrecheckerImpl::new(cs, Arc::new(provider), entry_point, Settings::default()); let op = UserOperation { sender: Address::from_str("0x3f8a2b6c4d5e1079286fa1b3c0d4e5f6902b7c8d").unwrap(), nonce: 100.into(), @@ -572,19 +578,19 @@ mod tests { #[tokio::test] async fn test_check_fees() { let settings = Settings { - chain_id: Chain::Optimism as u64, base_fee_accept_percent: 80, priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, settings); + let (mut cs, provider, entry_point) = create_base_config(); + cs.id = Chain::Optimism as u64; + let mintip = cs.min_max_priority_fee_per_gas; + let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); let mut async_data = get_test_async_data(); async_data.base_fee = 5_000.into(); async_data.min_pre_verification_gas = 1_000.into(); - let mintip = get_min_max_priority_fee_per_gas(Chain::Optimism as u64); let op = UserOperation { max_fee_per_gas: U256::from(math::percent(5000, settings.base_fee_accept_percent)) + mintip, @@ -605,13 +611,12 @@ mod tests { #[tokio::test] async fn test_check_fees_too_low() { let settings = Settings { - chain_id: 10000000, base_fee_accept_percent: 80, priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, settings); + let (cs, provider, entry_point) = create_base_config(); + let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); let mut async_data = get_test_async_data(); async_data.base_fee = 5_000.into(); @@ -638,19 +643,20 @@ mod tests { #[tokio::test] async fn test_check_fees_min() { let settings = Settings { - chain_id: Chain::Optimism as u64, base_fee_accept_percent: 100, priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, settings); + let (mut cs, provider, entry_point) = create_base_config(); + cs.id = Chain::Optimism as u64; + cs.min_max_priority_fee_per_gas = 100_000.into(); + let mintip = cs.min_max_priority_fee_per_gas; + let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); let mut async_data = get_test_async_data(); async_data.base_fee = 5_000.into(); async_data.min_pre_verification_gas = 1_000.into(); - let mintip = get_min_max_priority_fee_per_gas(Chain::Optimism as u64); let undertip = mintip - U256::from(1); let op = UserOperation { @@ -664,8 +670,8 @@ mod tests { let res = prechecker.check_gas(&op, async_data); let mut expected = ArrayVec::::new(); expected.push(PrecheckViolation::MaxPriorityFeePerGasTooLow( - get_min_max_priority_fee_per_gas(Chain::Optimism as u64) - U256::from(1), - get_min_max_priority_fee_per_gas(Chain::Optimism as u64), + mintip - U256::from(1), + mintip, )); assert_eq!(res, expected); @@ -674,13 +680,12 @@ mod tests { #[tokio::test] async fn test_pvg_too_low() { let settings = Settings { - chain_id: 10000000, base_fee_accept_percent: 80, priority_fee_mode: gas::PriorityFeeMode::PriorityFeeIncreasePercent(0), ..Default::default() }; - let (provider, entry_point) = create_base_config(); - let prechecker = PrecheckerImpl::new(Arc::new(provider), entry_point, settings); + let (cs, provider, entry_point) = create_base_config(); + let prechecker = PrecheckerImpl::new(cs, Arc::new(provider), entry_point, settings); let mut async_data = get_test_async_data(); async_data.base_fee = 5_000.into(); diff --git a/crates/sim/src/simulation/simulation.rs b/crates/sim/src/simulation/simulation.rs index 8951cb17a..4be19c39a 100644 --- a/crates/sim/src/simulation/simulation.rs +++ b/crates/sim/src/simulation/simulation.rs @@ -1155,7 +1155,7 @@ mod tests { assert!(matches!( res, Err(SimulationError { violation_error: ViolationError::Violations(violations), entity_infos: None}) if matches!( - violations.get(0), + violations.first(), Some(&SimulationViolation::UnintendedRevertWithMessage( EntityType::Paymaster, ref reason, diff --git a/crates/types/src/chain.rs b/crates/types/src/chain.rs index 357291b0e..457b769ed 100644 --- a/crates/types/src/chain.rs +++ b/crates/types/src/chain.rs @@ -11,41 +11,93 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -//! Grouped/Labeled chain IDs for various networks - -use ethers::types::Chain; - -/// Known chain IDs that use the Optimism Bedrock stack -pub const OP_BEDROCK_CHAIN_IDS: &[u64] = &[ - Chain::Optimism as u64, - Chain::OptimismGoerli as u64, - 11155420, // OptimismSepolia - Chain::Base as u64, - Chain::BaseGoerli as u64, - 84532, // BaseSepolia -]; - -// TODO use chain from ethers types once my PR is merged into ethers -// https://github.com/gakonst/ethers-rs/pull/2657 -/// Known chain IDs for the Base ecosystem -pub const ARBITRUM_CHAIN_IDS: &[u64] = &[ - Chain::Arbitrum as u64, - Chain::ArbitrumGoerli as u64, - 421614, /* ArbitrumSepolia */ - Chain::ArbitrumNova as u64, -]; - -/// Known chain IDs for the Base ecosystem -pub const BASE_CHAIN_IDS: &[u64] = &[ - Chain::Base as u64, - Chain::BaseGoerli as u64, - 84532, /* BaseSepolia */ -]; - -/// Known chain IDs for the Polygon ecosystem -pub const POLYGON_CHAIN_IDS: &[u64] = &[Chain::Polygon as u64, Chain::PolygonMumbai as u64]; - -/// Return true if the chain ID has a dynamic preVerificationGas field -pub fn is_dynamic_pvg(chain_id: u64) -> bool { - ARBITRUM_CHAIN_IDS.contains(&chain_id) || OP_BEDROCK_CHAIN_IDS.contains(&chain_id) +//! Chain specification for Rundler + +use std::str::FromStr; + +use ethers::types::{Address, U256}; +use serde::{Deserialize, Serialize}; + +const ENTRY_POINT_ADDRESS_V6_0: &str = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + +/// Chain specification for Rundler +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChainSpec { + /* + * Chain constants + */ + /// name for logging purposes, e.g. "Ethereum", no logic is performed on this + pub name: String, + /// chain id + pub id: u64, + /// entry point address + pub entry_point_address: Address, + + /* + * Gas estimation + */ + /// true if calldata is priced in preVerificationGas + pub calldata_pre_verification_gas: bool, + /// type of gas oracle contract for pricing calldata in preVerificationGas + /// If calldata_pre_verification_gas is true, this must not be None + pub l1_gas_oracle_contract_type: L1GasOracleContractType, + /// address of gas oracle contract for pricing calldata in preVerificationGas + pub l1_gas_oracle_contract_address: Address, + /// true if L1 calldata gas should be included in the gas limit + /// only applies when calldata_pre_verification_gas is true + pub include_l1_gas_in_gas_limit: bool, + + /* + * Fee estimation + */ + /// true if eip1559 is enabled, and thus priority fees are used + pub eip1559_enabled: bool, + /// Type of oracle for estimating priority fees + pub priority_fee_oracle_type: PriorityFeeOracleType, + /// Minimum max priority fee per gas for the network + pub min_max_priority_fee_per_gas: U256, + /// Maximum max priority fee per gas for the network + pub max_max_priority_fee_per_gas: U256, +} + +/// Type of gas oracle contract for pricing calldata in preVerificationGas +#[derive(Clone, Copy, Debug, Deserialize, Default, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum L1GasOracleContractType { + /// No gas oracle contract + #[default] + None, + /// Arbitrum Nitro type gas oracle contract + ArbitrumNitro, + /// Optimism Bedrock type gas oracle contract + OptimismBedrock, +} + +/// Type of oracle for estimating priority fees +#[derive(Clone, Debug, Deserialize, Default, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PriorityFeeOracleType { + /// Use eth_maxPriorityFeePerGas on the provider + #[default] + Provider, + /// Use the usage based oracle + UsageBased, +} + +impl Default for ChainSpec { + fn default() -> Self { + Self { + name: "Unknown".to_string(), + id: 0, + entry_point_address: Address::from_str(ENTRY_POINT_ADDRESS_V6_0).unwrap(), + eip1559_enabled: true, + calldata_pre_verification_gas: false, + l1_gas_oracle_contract_type: L1GasOracleContractType::default(), + l1_gas_oracle_contract_address: Address::zero(), + include_l1_gas_in_gas_limit: true, + priority_fee_oracle_type: PriorityFeeOracleType::default(), + min_max_priority_fee_per_gas: U256::zero(), + max_max_priority_fee_per_gas: U256::MAX, + } + } } diff --git a/test/spec-tests/local/.env b/test/spec-tests/local/.env index 2d5fd2bc1..db87d1393 100644 --- a/test/spec-tests/local/.env +++ b/test/spec-tests/local/.env @@ -1,4 +1,6 @@ -ENTRY_POINTS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 +CHAIN_BASE=ethereum +CHAIN_NAME=dev +CHAIN_ID=1337 NODE_HTTP=http://localhost:8545 RUST_LOG=debug RPC_API=eth,debug diff --git a/test/spec-tests/remote/docker-compose.yml b/test/spec-tests/remote/docker-compose.yml index 953244f0b..16b2c2ff3 100644 --- a/test/spec-tests/remote/docker-compose.yml +++ b/test/spec-tests/remote/docker-compose.yml @@ -33,7 +33,9 @@ services: command: /usr/local/bin/rundler pool environment: - RUST_LOG=debug - - ENTRY_POINTS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 + - CHAIN_BASE=ethereum + - CHAIN_NAME=dev + - CHAIN_ID=1337 - NODE_HTTP=http://geth:8545 - MIN_UNSTAKE_DELAY=2 - POOL_HOST=0.0.0.0 @@ -45,7 +47,9 @@ services: command: /usr/local/bin/rundler builder environment: - RUST_LOG=debug - - ENTRY_POINTS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 + - CHAIN_BASE=ethereum + - CHAIN_NAME=dev + - CHAIN_ID=1337 - NODE_HTTP=http://geth:8545 - MIN_UNSTAKE_DELAY=2 - BUILDER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 @@ -63,7 +67,9 @@ services: command: /usr/local/bin/rundler rpc environment: - RUST_LOG=debug - - ENTRY_POINTS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 + - CHAIN_BASE=ethereum + - CHAIN_NAME=dev + - CHAIN_ID=1337 - NODE_HTTP=http://geth:8545 - RPC_API=eth,debug - RPC_POOL_URL=https://rundler-pool:50051