From 409d76c7d651784b409dc7c48ab9ac41af48e8d9 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Wed, 10 Jan 2024 16:29:57 -0500 Subject: [PATCH] wip --- Cargo.lock | 46 ++++- bin/tools/Cargo.toml | 4 + bin/tools/src/bin/load_chain_spec.rs | 88 ++++++++++ chain_spec.yaml | 4 + crates/types/Cargo.toml | 2 + crates/types/src/chain_spec.rs | 247 +++++++++++++++++++++++++++ crates/types/src/lib.rs | 3 + 7 files changed, 390 insertions(+), 4 deletions(-) create mode 100644 bin/tools/src/bin/load_chain_spec.rs create mode 100644 chain_spec.yaml create mode 100644 crates/types/src/chain_spec.rs diff --git a/Cargo.lock b/Cargo.lock index 4f20a1840..b27089d3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,6 +1298,15 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -3202,6 +3211,12 @@ 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" @@ -4140,13 +4155,16 @@ dependencies = [ "anyhow", "clap", "dotenv", + "envy", "ethers", "ethers-signers", "rundler-dev", "rundler-rpc", + "rundler-types", "rusoto_core", "rusoto_kms", "serde_json", + "serde_yaml", "tokio", ] @@ -4158,6 +4176,7 @@ dependencies = [ "chrono", "ethers", "parse-display", + "paste", "rundler-utils", "serde", "serde_json", @@ -4554,18 +4573,18 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -4633,6 +4652,19 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "serde_yaml" +version = "0.9.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +dependencies = [ + "indexmap 2.0.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -5603,6 +5635,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/bin/tools/Cargo.toml b/bin/tools/Cargo.toml index a812724b0..7bd09d109 100644 --- a/bin/tools/Cargo.toml +++ b/bin/tools/Cargo.toml @@ -12,6 +12,7 @@ Rundler tools [dependencies] rundler-dev = { path = "../../crates/dev" } rundler-rpc = { path = "../../crates/rpc" } +rundler-types = { path = "../../crates/types" } anyhow = "1.0.70" clap = { version = "4.2.4", features = ["derive", "env"] } @@ -22,3 +23,6 @@ rusoto_core = { version = "0.48.0", default-features = false, features = ["rustl rusoto_kms = { version = "0.48.0", default-features = false, features = ["rustls"] } serde_json = "1.0.96" tokio.workspace = true + +envy = "0.4" +serde_yaml = "0.9" diff --git a/bin/tools/src/bin/load_chain_spec.rs b/bin/tools/src/bin/load_chain_spec.rs new file mode 100644 index 000000000..33e4dbbad --- /dev/null +++ b/bin/tools/src/bin/load_chain_spec.rs @@ -0,0 +1,88 @@ +// 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 std::fs::File; + +use rundler_types::{ChainSpec, OptionalChainSpec}; + +#[tokio::main] +async fn main() { + let chain_spec = resolve_chain_spec( + Some("arbitrum".to_string()), + Some("chain_spec.yaml".to_string()), + ); + println!("{:?}", chain_spec); +} + +// Chain spec configuration precedence: +// 1. env vars (CHAIN_* prefixed) +// 2. file (--chain-spec-file) +// 3. preset network (--network) +// +// `ChainSpec` resolution precedence: +// 1. existing +// 2. base (if defined) +// 3. defaults + +fn resolve_chain_spec(network: Option, file: Option) -> ChainSpec { + let network_spec = get_network_spec(&network); + + let file_spec = match file { + Some(file) => { + let file = File::open(file).expect("should open file"); + serde_yaml::from_reader(file).expect("should parse yaml") + } + None => OptionalChainSpec::default(), + }; + + let env_spec = match envy::prefixed("CHAIN_").from_env::() { + Ok(config) => config, + Err(error) => panic!("{:#?}", error), + }; + + let spec = env_spec.merge(file_spec).merge(network_spec); + let base_spec = get_network_spec(&spec.base); + spec.merge(base_spec).into() +} + +fn get_network_spec(network: &Option) -> OptionalChainSpec { + match network.as_deref() { + Some("arbitrum") => serde_yaml::from_str::(ARBITRUM_SPEC) + .expect("should parse ARBITRUM_SPEC yaml"), + Some("polygon") => serde_yaml::from_str::(POLYGON_SPEC) + .expect("should parse POLYGON_SPEC yaml"), + Some("polygon_mumbai") => serde_yaml::from_str::(POLYGON_MUMBAI_SPEC) + .expect("should parse POLYGON_MUMBAI_SPEC yaml"), + _ => OptionalChainSpec::default(), + } +} + +const ARBITRUM_SPEC: &str = " +id: 42161 +supports_eip1559: false +l1_gas_oracle_contract_type: ArbitrumNitro +l1_gas_oracle_contract_address: 0x00000000000000000000000000000000000000C8 +"; + +const POLYGON_SPEC: &str = " +id: 137 +use_fee_history_oracle: true +fee_history_oracle_blocks: 30 +fee_history_oracle_percentile: 33.3 +max_with_provider_fee: true +"; + +const POLYGON_MUMBAI_SPEC: &str = " +base: polygon +id: 80001 +"; diff --git a/chain_spec.yaml b/chain_spec.yaml new file mode 100644 index 000000000..0f1e70042 --- /dev/null +++ b/chain_spec.yaml @@ -0,0 +1,4 @@ +base: polygon + +id: 421613 +min_max_priority_fee_per_gas: 10 diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 153e8e570..87e8f5f05 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -17,5 +17,7 @@ serde.workspace = true serde_json.workspace = true strum.workspace = true +paste = "1.0" + [build-dependencies] ethers.workspace = true diff --git a/crates/types/src/chain_spec.rs b/crates/types/src/chain_spec.rs new file mode 100644 index 000000000..23117e482 --- /dev/null +++ b/crates/types/src/chain_spec.rs @@ -0,0 +1,247 @@ +// 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 std::str::FromStr; + +use ethers::types::Address; +use paste::paste; +use serde::Deserialize; + +// Generate chain spec with optional fields and a merge function. +// TODO should this be a derive macro? +macro_rules! make_optional_spec { + ( $(#[$attr:meta])* $vis:vis struct $struct_name:ident { $(#[$doc:meta] $fv:vis $field:ident : $ty:ty),* $(,)? }) => { + + $(#[$attr])* + $vis struct $struct_name { + $( #[$doc] $fv $field: $ty ),* + } + + paste! { + $(#[$attr])* + #[derive(Deserialize, Default)] + $vis struct [] { + /// The base chain spec to use + pub base: Option, + $( $field: Option<$ty> ),* + } + + impl [] { + /// Merge two optional $struct_name structs, preferring the first value if both are present. + $vis fn merge(self, other: Self) -> Self { + Self { + base: self.base.or(other.base), + $( $field: self.$field.or(other.$field) ),* + } + } + } + + impl From<[]> for $struct_name { + fn from(optional: []) -> Self { + let default = $struct_name::default(); + $struct_name { + $( $field: optional.$field.unwrap_or(default.$field) ),* + } + } + } + } + } +} + +make_optional_spec!( + /// asdf + #[derive(Debug)] + pub struct ChainSpec { + /* + * Chain constants + */ + /// asdf + pub id: u64, + /// asdf + pub entry_point_address: Address, + /// asdf + pub supports_eip1559: bool, + + /* + * Fee estimation + */ + /// asdf + pub min_max_priority_fee_per_gas: u64, + /// asdf + pub max_max_priority_fee_per_gas: u64, + /// asdf + pub use_fee_history_oracle: bool, + /// asdf + pub fee_history_oracle_blocks: u64, + /// asdf + pub fee_history_oracle_percentile: f64, + /// asdf + pub max_with_provider_fee: bool, + + /* + * Gas estimation + */ + /// asdf + pub gas_estimation_accuracy_rounding: u64, + /// asdf + pub gas_estimation_error_margin: f64, + /// asdf + pub gas_estimation_verification_gas_buffer_percent: u64, + /// asdf + pub gas_estimation_fee_transfer_cost: u64, + /// asdf + pub min_call_gas_limit: u64, + /// asdf + pub l1_gas_oracle_contract_type: L1GasOracleContractType, + /// asdf + pub l1_gas_oracle_contract_address: Address, + + /* + * Pool and validation + */ + /// asdf + pub bundle_invalidation_ops_seen_staked_penalty: u64, + /// asdf + pub bundle_invalidation_ops_seen_unstaked_penalty: u64, + /// asdf + pub same_unstaked_entity_mempool_count: u64, + /// asdf + pub min_inclusion_rate_denominator: u64, + /// asdf + pub inclusion_rate_factor: u64, + /// asdf + pub throttling_slack: u64, + /// asdf + pub ban_slack: u64, + /// asdf + pub throttled_entity_mempool_count: u64, + /// asdf + pub throttled_entity_live_blocks: u64, + /// asdf + pub max_user_ops_per_unstaked_sender: usize, + /// asdf + pub min_replacement_fee_increase_percentage: u64, + /// asdf + pub min_unstake_delay_seconds: u64, + /// asdf + pub min_stake_value: u128, + } +); + +impl ChainSpec { + /* + * Gas constants + */ + /// + pub const ZERO_BYTE_GAS: u64 = 4; + /// + pub const NON_ZERO_BYTE_GAS: u64 = 16; + /// + pub const PER_USER_OP_WORD_GAS: u64 = 4; + /// + pub const PER_USER_OP_GAS: u64 = 18300; + /// + pub const TRANSACTION_GAS_OVERHEAD: u64 = 21000; + /// + pub const BUNDLE_TRANSACTION_GAS_BUFFER: u64 = 5000; +} + +impl Default for ChainSpec { + fn default() -> Self { + Self { + id: 0, + supports_eip1559: true, + entry_point_address: Address::from_str("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") + .unwrap(), + min_max_priority_fee_per_gas: 0, + max_max_priority_fee_per_gas: u64::MAX, + use_fee_history_oracle: false, + fee_history_oracle_blocks: 0, + fee_history_oracle_percentile: 0.0, + max_with_provider_fee: false, + min_call_gas_limit: 9100, + l1_gas_oracle_contract_type: L1GasOracleContractType::None, + l1_gas_oracle_contract_address: Address::zero(), + gas_estimation_accuracy_rounding: 4096, + gas_estimation_error_margin: 0.1, + gas_estimation_verification_gas_buffer_percent: 10, + gas_estimation_fee_transfer_cost: 30000, + bundle_invalidation_ops_seen_staked_penalty: 10000, + bundle_invalidation_ops_seen_unstaked_penalty: 1000, + same_unstaked_entity_mempool_count: 10, + min_inclusion_rate_denominator: 10, + inclusion_rate_factor: 10, + throttling_slack: 10, + ban_slack: 50, + throttled_entity_mempool_count: 4, + throttled_entity_live_blocks: 4, + max_user_ops_per_unstaked_sender: 4, + min_replacement_fee_increase_percentage: 10, + min_unstake_delay_seconds: 84600, // 1 day + min_stake_value: 1000000000000000000, // 1 ETH + } + } +} + +/// +#[derive(Clone, Copy, Debug, Deserialize, Default)] +pub enum L1GasOracleContractType { + /// + #[default] + None, + /// + ArbitrumNitro, + /// + OptimismBedrock, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_arbitrum_goerli() { + let spec: ChainSpec = arbitrum_goerli().into(); + assert_eq!(spec.id, 421613); + assert!(!spec.supports_eip1559); + assert!(matches!( + spec.l1_gas_oracle_contract_type, + L1GasOracleContractType::ArbitrumNitro + )); + assert_eq!( + spec.l1_gas_oracle_contract_address, + Address::from_str("0x00000000000000000000000000000000000000C8").unwrap() + ); + } + + fn arbitrum() -> OptionalChainSpec { + OptionalChainSpec { + id: Some(42161), + supports_eip1559: Some(false), + l1_gas_oracle_contract_type: Some(L1GasOracleContractType::ArbitrumNitro), + l1_gas_oracle_contract_address: Some( + Address::from_str("0x00000000000000000000000000000000000000C8").unwrap(), // TODO + ), + ..Default::default() + } + } + + fn arbitrum_goerli() -> OptionalChainSpec { + let base = arbitrum(); + let overrides = OptionalChainSpec { + id: Some(421613), + ..Default::default() + }; + overrides.merge(base) + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index e7930de9b..f4116eaa4 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -44,3 +44,6 @@ pub use user_operation::UserOperationId; mod storage; pub use storage::StorageSlot; + +mod chain_spec; +pub use chain_spec::{ChainSpec, OptionalChainSpec};