diff --git a/Cargo.lock b/Cargo.lock index a80be1ec0..8d020a1e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,22 +63,20 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] -name = "alloy-chains" -version = "0.1.10" +name = "aho-corasick" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206e825321d0ed5b6e3907e6ddf70c2139cfdc6274e83a1ca2f4784bd0abc0c9" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ - "num_enum", - "serde", - "strum", + "memchr", ] [[package]] @@ -482,6 +480,9 @@ name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -832,6 +833,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.8.10", + "yaml-rust", +] + [[package]] name = "const-hex" version = "1.10.0" @@ -851,6 +872,26 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "const-random" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const_format" version = "0.2.31" @@ -1206,6 +1247,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -1489,7 +1539,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.32", - "toml", + "toml 0.7.3", "walkdir", ] @@ -2040,9 +2090,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -2402,6 +2452,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" @@ -2851,14 +2912,14 @@ dependencies = [ [[package]] name = "metrics-util" -version = "0.15.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de2ed6e491ed114b40b732e4d1659a9d53992ebd87490c44a6ffe23739d973e" +checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.13.1", + "hashbrown 0.13.2", "indexmap 1.9.3", "metrics", "num_cpus", @@ -3121,6 +3182,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "overload" version = "0.1.1" @@ -3219,12 +3290,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" @@ -3268,6 +3351,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" @@ -3774,7 +3902,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", "regex-automata 0.3.6", "regex-syntax 0.7.4", @@ -3795,7 +3923,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", "regex-syntax 0.7.4", ] @@ -3923,6 +4051,18 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.0", + "bitflags 2.4.1", + "serde", + "serde_derive", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -3945,9 +4085,9 @@ dependencies = [ name = "rundler" version = "0.1.0-beta" dependencies = [ - "alloy-chains", "anyhow", "clap", + "config", "dotenv", "ethers", "itertools 0.11.0", @@ -3955,6 +4095,7 @@ dependencies = [ "metrics-exporter-prometheus", "metrics-process", "metrics-util", + "paste", "rundler-builder", "rundler-pool", "rundler-provider", @@ -3982,7 +4123,6 @@ dependencies = [ name = "rundler-builder" version = "0.1.0-beta" dependencies = [ - "alloy-chains", "anyhow", "async-trait", "enum_dispatch", @@ -4111,7 +4251,6 @@ dependencies = [ name = "rundler-sim" version = "0.1.0-beta" dependencies = [ - "alloy-chains", "anyhow", "arrayvec", "async-trait", @@ -4176,7 +4315,6 @@ dependencies = [ name = "rundler-types" version = "0.1.0-beta" dependencies = [ - "alloy-chains", "anyhow", "chrono", "constcat", @@ -4298,6 +4436,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4609,9 +4757,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -5300,10 +5448,22 @@ checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.1", + "toml_datetime 0.6.5", "toml_edit 0.19.8", ] +[[package]] +name = "toml" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.5", + "toml_edit 0.22.5", +] + [[package]] name = "toml_datetime" version = "0.5.1" @@ -5312,9 +5472,9 @@ checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -5339,8 +5499,21 @@ dependencies = [ "indexmap 1.9.3", "serde", "serde_spanned", - "toml_datetime 0.6.1", - "winnow", + "toml_datetime 0.6.5", + "winnow 0.4.1", +] + +[[package]] +name = "toml_edit" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e68c159e8f5ba8a28c4eb7b0c0c190d77bb479047ca713270048145a9ad28a" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime 0.6.5", + "winnow 0.6.0", ] [[package]] @@ -5570,6 +5743,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" @@ -6036,6 +6215,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1dbce9e90e5404c5a52ed82b1d13fc8cfbdad85033b6f57546ffd1265f8451" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -6080,6 +6268,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/Cargo.toml b/Cargo.toml index a12fa4041..af6dc4fd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/alchemyplatform/rundler" [workspace.dependencies] -alloy-chains = "0.1.10" anyhow = "1.0.70" async-trait = "0.1.73" cargo-husky = { version = "1", default-features = false, features = ["user-hooks"] } diff --git a/bin/rundler/Cargo.toml b/bin/rundler/Cargo.toml index 8d5caa7a4..0f864602d 100644 --- a/bin/rundler/Cargo.toml +++ b/bin/rundler/Cargo.toml @@ -20,8 +20,8 @@ rundler-types = { path = "../../crates/types" } rundler-utils = { path = "../../crates/utils" } # CLI dependencies -alloy-chains.workspace = true anyhow.workspace = true +config = "0.14.0" clap = { version = "4.4.4", features = ["derive", "env"] } dotenv = "0.15.0" ethers.workspace = true @@ -30,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..8868844cd --- /dev/null +++ b/bin/rundler/chain_specs/ethereum.toml @@ -0,0 +1,4 @@ +name = "Ethereum" +id = 1 + +flashbots_enabled = true 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..57d5d2f16 --- /dev/null +++ b/bin/rundler/chain_specs/polygon.toml @@ -0,0 +1,6 @@ +name = "Polygon" +id = 137 + +priority_fee_oracle_type = "USAGE_BASED" +min_max_priority_fee_per_gas = "0x06FC23AC00" # 30_000_000_000 +bloxroute_enabled = true diff --git a/bin/rundler/chain_specs/polygon_amoy.toml b/bin/rundler/chain_specs/polygon_amoy.toml new file mode 100644 index 000000000..144ed230c --- /dev/null +++ b/bin/rundler/chain_specs/polygon_amoy.toml @@ -0,0 +1,6 @@ +base = "polygon" + +name = "Polygon Amoy" +id = 80002 + +min_max_priority_fee_per_gas = "0x59682F00" # 1_500_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 542ddc8fd..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 - .first() - .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, @@ -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..43922c1e5 --- /dev/null +++ b/bin/rundler/src/cli/chain_spec.rs @@ -0,0 +1,121 @@ +// 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, + polygon_amoy +); diff --git a/bin/rundler/src/cli/mod.rs b/bin/rundler/src/cli/mod.rs index d5f7d1515..dccd39a8b 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( @@ -298,7 +300,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 910197723..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,16 +44,25 @@ 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).into(), diff --git a/bin/rundler/src/cli/pool.rs b/bin/rundler/src/cli/pool.rs index bd359f3f1..bf3f3c148 100644 --- a/bin/rundler/src/cli/pool.rs +++ b/bin/rundler/src/cli/pool.rs @@ -13,13 +13,13 @@ use std::{collections::HashMap, net::SocketAddr, time::Duration}; -use alloy_chains::NamedChain; use anyhow::Context; use clap::Args; use ethers::types::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; @@ -141,6 +141,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 { @@ -163,69 +164,40 @@ 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, - same_sender_mempool_count: self.same_sender_mempool_count, - 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, - paymaster_tracking_enabled: self.paymaster_tracking_enabled, - reputation_tracking_enabled: self.reputation_tracking_enabled, - }) - }) - .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, + same_sender_mempool_count: self.same_sender_mempool_count, + 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, + paymaster_tracking_enabled: self.paymaster_tracking_enabled, + reputation_tracking_enabled: self.reputation_tracking_enabled, + }; Ok(PoolTaskArgs { - chain_id: common.chain_id, - chain_history_size: self - .chain_history_size - .unwrap_or_else(|| default_chain_history_size(common.chain_id)), + chain_spec, 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), }) } } -const SMALL_HISTORY_SIZE: u64 = 16; -const LARGE_HISTORY_SIZE: u64 = 128; - -// Mainnets that are known to not have large reorgs can use the small history -// size. Use the large history size for all testnets because I don't trust them. -const SMALL_HISTORY_CHAIN_IDS: &[u64] = &[ - NamedChain::Mainnet as u64, - NamedChain::Arbitrum as u64, - NamedChain::Optimism as u64, -]; - -fn default_chain_history_size(chain_id: u64) -> u64 { - if SMALL_HISTORY_CHAIN_IDS.contains(&chain_id) { - SMALL_HISTORY_SIZE - } else { - LARGE_HISTORY_SIZE - } -} - /// CLI options for the Pool server standalone #[derive(Args, Debug)] pub struct PoolCliArgs { @@ -233,11 +205,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 c7475868d..f3607b382 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/Cargo.toml b/crates/builder/Cargo.toml index 3682258bb..833a6be58 100644 --- a/crates/builder/Cargo.toml +++ b/crates/builder/Cargo.toml @@ -14,7 +14,6 @@ rundler-task = { path = "../task" } rundler-types = { path = "../types" } rundler-utils = { path = "../utils" } -alloy-chains.workspace = true anyhow.workspace = true async-trait.workspace = true enum_dispatch = "0.3.11" diff --git a/crates/builder/src/bundle_proposer.rs b/crates/builder/src/bundle_proposer.rs index ba763d4de..10a6f5215 100644 --- a/crates/builder/src/bundle_proposer.rs +++ b/crates/builder/src/bundle_proposer.rs @@ -37,8 +37,8 @@ use rundler_sim::{ SimulationResult, 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}; @@ -108,7 +108,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, @@ -242,8 +242,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, ), @@ -285,10 +285,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 @@ -412,8 +411,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, @@ -454,8 +453,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, ); @@ -526,7 +525,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, ); @@ -811,8 +810,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, ); @@ -843,7 +842,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) } } @@ -1034,23 +1033,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, ); @@ -1228,8 +1227,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, @@ -1241,7 +1240,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 @@ -1767,9 +1766,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, @@ -1808,14 +1807,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, @@ -1844,7 +1843,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 @@ -2091,7 +2090,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/sender/mod.rs b/crates/builder/src/sender/mod.rs index b79cc2cb9..3b0ac2f44 100644 --- a/crates/builder/src/sender/mod.rs +++ b/crates/builder/src/sender/mod.rs @@ -17,7 +17,6 @@ mod flashbots; mod raw; use std::{str::FromStr, sync::Arc, time::Duration}; -use alloy_chains::NamedChain; use anyhow::{bail, Context, Error}; use async_trait::async_trait; pub(crate) use bloxroute::PolygonBloxrouteTransactionSender; @@ -36,6 +35,7 @@ pub(crate) use flashbots::FlashbotsTransactionSender; use mockall::automock; pub(crate) use raw::RawTransactionSender; use rundler_sim::ExpectedStorage; +use rundler_types::chain::ChainSpec; use serde::Serialize; #[derive(Debug)] @@ -138,9 +138,9 @@ impl TransactionSenderType { pub(crate) fn into_sender( self, + chain_spec: &ChainSpec, client: Arc>, signer: S, - chain_id: u64, eth_poll_interval: Duration, bloxroute_header: &Option, ) -> std::result::Result, SenderConstructorErrors> { @@ -150,9 +150,9 @@ impl TransactionSenderType { ConditionalTransactionSender::new(client, signer), ), Self::Flashbots => { - if chain_id != NamedChain::Mainnet as u64 { + if !chain_spec.flashbots_enabled { return Err(SenderConstructorErrors::InvalidChainForSender( - chain_id, + chain_spec.id, self.into_snake_case(), )); } @@ -160,9 +160,9 @@ impl TransactionSenderType { } Self::PolygonBloxroute => { if let Some(header) = bloxroute_header { - if chain_id == NamedChain::Polygon as u64 { + if !chain_spec.bloxroute_enabled { return Err(SenderConstructorErrors::InvalidChainForSender( - chain_id, + chain_spec.id, self.into_snake_case(), )); } diff --git a/crates/builder/src/task.rs b/crates/builder/src/task.rs index 62daea0b8..d8b615c9a 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( @@ -282,9 +287,9 @@ where eth::new_provider(&self.args.submit_url, Some(self.args.eth_poll_interval))?; let transaction_sender = self.args.sender_type.into_sender( + &self.args.chain_spec, submit_provider, signer, - self.args.chain_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 911cf96f5..f66ba210a 100644 --- a/crates/pool/src/task.rs +++ b/crates/pool/src/task.rs @@ -21,8 +21,11 @@ use rundler_sim::{ Prechecker, PrecheckerImpl, SimulateValidationTracerImpl, Simulator, SimulatorImpl, }; use rundler_task::Task; -use rundler_types::contracts::{ - i_entry_point::IEntryPoint, paymaster_helper::PaymasterHelper as PaymasterHelperContract, +use rundler_types::{ + chain::ChainSpec, + contracts::{ + i_entry_point::IEntryPoint, paymaster_helper::PaymasterHelper as PaymasterHelperContract, + }, }; use rundler_utils::{emit::WithEntryPoint, eth, handle}; use tokio::{sync::broadcast, try_join}; @@ -39,14 +42,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. pub pool_configs: Vec, /// Address to bind the remote mempool server to, if any. @@ -67,13 +68,13 @@ 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); // create chain let chain_settings = chain::Settings { - history_size: self.args.chain_history_size, + history_size: self.args.chain_spec.chain_history_size, poll_interval: self.args.http_poll_interval, entry_point_addresses: self .args @@ -90,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)); } @@ -105,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(()) }), }; @@ -150,6 +160,7 @@ impl PoolTask { } async fn create_mempool( + chain_spec: ChainSpec, pool_config: &PoolConfig, event_sender: broadcast::Sender>, provider: Arc

, @@ -179,6 +190,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 a0ab48929..4ec65b905 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,14 +73,14 @@ where E: EntryPoint, { fn new( - chain_id: u64, + chain_spec: ChainSpec, provider: Arc

, entry_point: E, estimation_settings: EstimationSettings, fee_estimator: FeeEstimator

, ) -> Self { let gas_estimator = GasEstimatorImpl::new( - chain_id, + chain_spec, provider, entry_point, estimation_settings, @@ -93,7 +94,7 @@ where pub(crate) struct EthApi { contexts_by_entry_point: HashMap>, provider: Arc

, - chain_id: u64, + chain_spec: ChainSpec, pool: PS, settings: Settings, } @@ -105,9 +106,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, @@ -122,13 +123,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, ), @@ -141,7 +142,7 @@ where settings, contexts_by_entry_point, provider, - chain_id, + chain_spec, pool, } } @@ -306,7 +307,7 @@ where } pub(crate) async fn chain_id(&self) -> EthResult { - Ok(self.chain_id.into()) + Ok(self.chain_spec.id.into()) } async fn get_mined_user_operation_by_hash( @@ -345,7 +346,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) @@ -542,7 +543,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)); } @@ -856,10 +857,14 @@ mod tests { ) -> EthApi { let mut contexts_by_entry_point = HashMap::new(); let provider = Arc::new(provider); + let chain_spec = ChainSpec { + id: 1, + ..Default::default() + }; contexts_by_entry_point.insert( ep.address(), EntryPointContext::new( - 1, + chain_spec.clone(), Arc::clone(&provider), ep, EstimationSettings { @@ -869,8 +874,8 @@ mod tests { validation_estimation_gas_fee: 1_000_000_000_000, }, FeeEstimator::new( + &chain_spec, Arc::clone(&provider), - 1, PriorityFeeMode::BaseFeePercent(0), 0, ), @@ -879,7 +884,7 @@ mod tests { EthApi { contexts_by_entry_point, provider, - chain_id: 1, + chain_spec, pool, settings: Settings::new(None), } 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 e5014fbd0..90f22d320 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; @@ -49,14 +46,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. @@ -91,20 +86,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())]; @@ -163,16 +150,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, @@ -185,8 +173,8 @@ where ApiNamespace::Admin => module.merge(AdminApi::new(self.pool.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/Cargo.toml b/crates/sim/Cargo.toml index 3201b76ed..7e6ba4faf 100644 --- a/crates/sim/Cargo.toml +++ b/crates/sim/Cargo.toml @@ -11,7 +11,6 @@ rundler-provider = { path = "../provider" } rundler-types = { path = "../types" } rundler-utils = { path = "../utils" } -alloy-chains.workspace = true anyhow.workspace = true arrayvec = "0.7.2" async-trait.workspace = true diff --git a/crates/sim/src/estimation/estimation.rs b/crates/sim/src/estimation/estimation.rs index a6388b14e..dde482f17 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, @@ -93,7 +94,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, @@ -169,14 +170,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, @@ -410,11 +411,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?) @@ -431,7 +431,6 @@ fn estimation_proxy_bytecode_with_target(target: Address) -> Bytes { #[cfg(test)] mod tests { - use alloy_chains::NamedChain; use ethers::{ abi::{AbiEncode, Address}, providers::JsonRpcError, @@ -439,7 +438,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; @@ -461,7 +463,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( @@ -476,7 +483,7 @@ mod tests { }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - 0, + ChainSpec::default(), provider.clone(), entry, settings, @@ -578,9 +585,15 @@ mod tests { }; // Chose arbitrum + let cs = ChainSpec { + id: 42161, + calldata_pre_verification_gas: true, + l1_gas_oracle_contract_type: L1GasOracleContractType::ArbitrumNitro, + ..Default::default() + }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - NamedChain::Arbitrum as u64, + cs, provider.clone(), entry, settings, @@ -631,9 +644,15 @@ mod tests { }; // Chose OP + let cs = ChainSpec { + id: 10, + calldata_pre_verification_gas: true, + l1_gas_oracle_contract_type: L1GasOracleContractType::OptimismBedrock, + ..Default::default() + }; let provider = Arc::new(provider); let estimator: GasEstimatorImpl = GasEstimatorImpl::new( - NamedChain::Optimism as u64, + cs, provider.clone(), entry, settings, @@ -1296,7 +1315,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 39d412445..874aefe28 100644 --- a/crates/sim/src/gas/gas.rs +++ b/crates/sim/src/gas/gas.rs @@ -13,17 +13,11 @@ use std::{cmp, fmt::Debug, sync::Arc}; -use alloy_chains::NamedChain; use anyhow::Context; -use ethers::{ - abi::AbiEncode, - types::{Address, 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, POLYGON_TESTNET_CHAIN_IDS, - }, + chain::{self, ChainSpec, L1GasOracleContractType}, GasFees, UserOperation, }; use rundler_utils::math; @@ -73,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) @@ -104,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) @@ -150,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) @@ -163,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) @@ -176,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 @@ -192,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 @@ -323,8 +323,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 { @@ -332,7 +332,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), } } @@ -388,42 +388,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 == NamedChain::Mainnet as u64 => ETHEREUM_MAINNET_MAX_PRIORITY_FEE_MIN.into(), - x if x == NamedChain::Polygon as u64 => POLYGON_MAINNET_MAX_PRIORITY_FEE_MIN.into(), - x if POLYGON_TESTNET_CHAIN_IDS.contains(&x) => 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 1d8436e8a..0558b0d8d 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 @@ -446,14 +447,17 @@ pub enum PrecheckViolation { mod tests { use std::str::FromStr; - use alloy_chains::NamedChain; use ethers::types::Bytes; use rundler_provider::{MockEntryPoint, MockProvider}; 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 { @@ -469,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(), @@ -499,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, @@ -509,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(), @@ -541,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(), @@ -573,19 +578,19 @@ mod tests { #[tokio::test] async fn test_check_fees() { let settings = Settings { - chain_id: NamedChain::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 = 10; + 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(NamedChain::Optimism as u64); let op = UserOperation { max_fee_per_gas: U256::from(math::percent(5000, settings.base_fee_accept_percent)) + mintip, @@ -606,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(); @@ -639,19 +643,20 @@ mod tests { #[tokio::test] async fn test_check_fees_min() { let settings = Settings { - chain_id: NamedChain::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 = 10; + 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(NamedChain::Optimism as u64); let undertip = mintip - U256::from(1); let op = UserOperation { @@ -665,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(NamedChain::Optimism as u64) - U256::from(1), - get_min_max_priority_fee_per_gas(NamedChain::Optimism as u64), + mintip - U256::from(1), + mintip, )); assert_eq!(res, expected); @@ -675,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/types/Cargo.toml b/crates/types/Cargo.toml index 7dcb32b9f..f973b7219 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true [dependencies] rundler-utils = { path = "../utils" } -alloy-chains.workspace = true anyhow.workspace = true chrono = "0.4.24" constcat = "0.4.1" diff --git a/crates/types/src/chain.rs b/crates/types/src/chain.rs index 75421b971..d3a4cf361 100644 --- a/crates/types/src/chain.rs +++ b/crates/types/src/chain.rs @@ -11,52 +11,110 @@ // 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 alloy_chains::NamedChain; -use constcat::concat_slices; - -/// Known chain IDs that use the Optimism Bedrock stack -pub const OP_BEDROCK_CHAIN_IDS: &[u64] = &[ - NamedChain::Optimism as u64, - NamedChain::OptimismGoerli as u64, - NamedChain::OptimismSepolia as u64, - NamedChain::Base as u64, - NamedChain::BaseGoerli as u64, - NamedChain::BaseSepolia as u64, -]; - -// 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] = &[ - NamedChain::Arbitrum as u64, - NamedChain::ArbitrumGoerli as u64, - NamedChain::ArbitrumSepolia as u64, - NamedChain::ArbitrumNova as u64, -]; - -/// Known chain IDs for the Base ecosystem -pub const BASE_CHAIN_IDS: &[u64] = &[ - NamedChain::Base as u64, - NamedChain::BaseGoerli as u64, - NamedChain::BaseSepolia as u64, -]; - -/// Known chain IDs for the Polygon ecosystem -pub const POLYGON_CHAIN_IDS: &[u64] = - concat_slices!([u64]: POLYGON_TESTNET_CHAIN_IDS, POLYGON_MAINNET_CHAIN_IDS); - -/// Known chain IDs for the Polygon ecosystem -pub const POLYGON_TESTNET_CHAIN_IDS: &[u64] = &[ - NamedChain::PolygonMumbai as u64, - 80002, // PolygonAmoy - Change to named chain once there is a new release on alloy-rs/chains -]; - -/// Known chain IDs for the Polygon ecosystem -pub const POLYGON_MAINNET_CHAIN_IDS: &[u64] = &[NamedChain::Polygon 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, + + /* + * Senders + */ + /// True if the flashbots sender is enabled on this chain + pub flashbots_enabled: bool, + /// True if the bloxroute sender is enabled on this chain + pub bloxroute_enabled: bool, + + /* + * Pool + */ + /// Size of the chain history to keep to handle reorgs + pub chain_history_size: u64, +} + +/// 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, + flashbots_enabled: false, + bloxroute_enabled: false, + chain_history_size: 64, + } + } } diff --git a/test/spec-tests/local/.env b/test/spec-tests/local/.env index 3809eb981..4ceaacd38 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 7bb07bf62..e364a0ece 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 - PRIORITY_FEE_MODE_KIND=base_fee_percent @@ -47,7 +49,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 - RPC_API=eth,debug - MIN_UNSTAKE_DELAY=2 @@ -68,7 +72,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 - PRIORITY_FEE_MODE_KIND=base_fee_percent - PRIORITY_FEE_MODE_VALUE=0