From 89eccaa4f64a11797e376d53c46a4bbfa5022cef Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 14:30:39 +0100 Subject: [PATCH 01/81] feat: add ev-deployer CLI for genesis contract allocation --- Cargo.lock | 81 +++++++- Cargo.toml | 1 + bin/ev-deployer/Cargo.toml | 23 +++ bin/ev-deployer/examples/devnet.toml | 17 ++ bin/ev-deployer/src/config.rs | 183 +++++++++++++++++++ bin/ev-deployer/src/contracts/admin_proxy.rs | 47 +++++ bin/ev-deployer/src/contracts/fee_vault.rs | 139 ++++++++++++++ bin/ev-deployer/src/contracts/mod.rs | 17 ++ bin/ev-deployer/src/genesis.rs | 169 +++++++++++++++++ bin/ev-deployer/src/main.rs | 117 ++++++++++++ bin/ev-deployer/src/output.rs | 25 +++ contracts/foundry.lock | 5 + justfile | 8 + 13 files changed, 823 insertions(+), 9 deletions(-) create mode 100644 bin/ev-deployer/Cargo.toml create mode 100644 bin/ev-deployer/examples/devnet.toml create mode 100644 bin/ev-deployer/src/config.rs create mode 100644 bin/ev-deployer/src/contracts/admin_proxy.rs create mode 100644 bin/ev-deployer/src/contracts/fee_vault.rs create mode 100644 bin/ev-deployer/src/contracts/mod.rs create mode 100644 bin/ev-deployer/src/genesis.rs create mode 100644 bin/ev-deployer/src/main.rs create mode 100644 bin/ev-deployer/src/output.rs create mode 100644 contracts/foundry.lock diff --git a/Cargo.lock b/Cargo.lock index 80530029..c1411c51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2415,7 +2415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2917,6 +2917,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "ev-deployer" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "clap", + "eyre", + "serde", + "serde_json", + "tempfile", + "toml 0.8.23", +] + [[package]] name = "ev-dev" version = "0.1.0" @@ -5848,7 +5861,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -6589,7 +6602,7 @@ dependencies = [ "tar", "tokio", "tokio-stream", - "toml", + "toml 0.9.12+spec-1.1.0", "tracing", "url", "zstd", @@ -6667,7 +6680,7 @@ dependencies = [ "reth-stages-types", "reth-static-file-types", "serde", - "toml", + "toml 0.9.12+spec-1.1.0", "url", ] @@ -7998,7 +8011,7 @@ dependencies = [ "shellexpand", "strum", "thiserror 2.0.18", - "toml", + "toml 0.9.12+spec-1.1.0", "tracing", "url", "vergen", @@ -9798,6 +9811,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -10454,6 +10476,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "0.9.12+spec-1.1.0" @@ -10462,13 +10496,22 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -10478,6 +10521,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -10485,7 +10542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.13.0", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] @@ -10499,6 +10556,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -10784,7 +10847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f7c95348f20c1c913d72157b3c6dee6ea3e30b3d19502c5a7f6d3f160dacbf" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a1131a78..622984de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "bin/ev-deployer", "bin/ev-dev", "bin/ev-reth", "crates/common", diff --git a/bin/ev-deployer/Cargo.toml b/bin/ev-deployer/Cargo.toml new file mode 100644 index 00000000..b80d21a8 --- /dev/null +++ b/bin/ev-deployer/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ev-deployer" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +authors.workspace = true + +[dependencies] +alloy-primitives = { workspace = true, features = ["serde"] } +clap = { workspace = true, features = ["derive", "env"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +toml = "0.8" +eyre = { workspace = true } + +[dev-dependencies] +tempfile = { workspace = true } + +[lints] +workspace = true diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml new file mode 100644 index 00000000..f332b1ad --- /dev/null +++ b/bin/ev-deployer/examples/devnet.toml @@ -0,0 +1,17 @@ +[chain] +chain_id = 1234 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.fee_vault] +address = "0x000000000000000000000000000000000000FE00" +owner = "0x000000000000000000000000000000000000Ad00" +destination_domain = 0 +recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" +minimum_amount = 0 +call_fee = 0 +bridge_share_bps = 10000 +other_recipient = "0x0000000000000000000000000000000000000000" +hyp_native_minter = "0x0000000000000000000000000000000000000000" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs new file mode 100644 index 00000000..6bb60482 --- /dev/null +++ b/bin/ev-deployer/src/config.rs @@ -0,0 +1,183 @@ +//! TOML config types, parsing, and validation. + +use alloy_primitives::{Address, B256}; +use serde::Deserialize; +use std::path::Path; + +/// Top-level deploy configuration. +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub(crate) struct DeployConfig { + /// Chain configuration. + pub chain: ChainConfig, + /// Contract configurations. + pub contracts: ContractsConfig, +} + +/// Chain-level settings. +#[derive(Debug, Deserialize)] +#[allow(dead_code)] +pub(crate) struct ChainConfig { + /// The chain ID. + pub chain_id: u64, +} + +/// All contract configurations. +#[derive(Debug, Deserialize)] +pub(crate) struct ContractsConfig { + /// `AdminProxy` contract config (optional). + pub admin_proxy: Option, + /// `FeeVault` contract config (optional). + pub fee_vault: Option, +} + +/// `AdminProxy` configuration. +#[derive(Debug, Deserialize)] +pub(crate) struct AdminProxyConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + pub owner: Address, +} + +/// `FeeVault` configuration. +#[derive(Debug, Deserialize)] +pub(crate) struct FeeVaultConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + pub owner: Address, + /// Hyperlane destination domain. + #[serde(default)] + pub destination_domain: u32, + /// Hyperlane recipient address (bytes32). + #[serde(default)] + pub recipient_address: B256, + /// Minimum amount for bridging. + #[serde(default)] + pub minimum_amount: u64, + /// Call fee for sendToCelestia. + #[serde(default)] + pub call_fee: u64, + /// Basis points for bridge share (0-10000). 0 defaults to 10000. + #[serde(default)] + pub bridge_share_bps: u64, + /// Other recipient for split accounting. + #[serde(default)] + pub other_recipient: Address, + /// `HypNativeMinter` address. + #[serde(default)] + pub hyp_native_minter: Address, +} + +impl DeployConfig { + /// Load and validate config from a TOML file. + pub(crate) fn load(path: &Path) -> eyre::Result { + let content = std::fs::read_to_string(path)?; + let config: Self = toml::from_str(&content)?; + config.validate()?; + Ok(config) + } + + /// Validate config values. + fn validate(&self) -> eyre::Result<()> { + if let Some(ref ap) = self.contracts.admin_proxy { + eyre::ensure!( + !ap.owner.is_zero(), + "admin_proxy.owner must not be the zero address" + ); + } + + if let Some(ref fv) = self.contracts.fee_vault { + eyre::ensure!( + !fv.owner.is_zero(), + "fee_vault.owner must not be the zero address" + ); + eyre::ensure!( + fv.bridge_share_bps <= 10000, + "fee_vault.bridge_share_bps must be 0-10000, got {}", + fv.bridge_share_bps + ); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_full_config() { + let toml = r#" +[chain] +chain_id = 1234 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.fee_vault] +address = "0x000000000000000000000000000000000000FE00" +owner = "0x000000000000000000000000000000000000Ad00" +destination_domain = 0 +recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" +minimum_amount = 0 +call_fee = 0 +bridge_share_bps = 10000 +other_recipient = "0x0000000000000000000000000000000000000000" +hyp_native_minter = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert_eq!(config.chain.chain_id, 1234); + assert!(config.contracts.admin_proxy.is_some()); + assert!(config.contracts.fee_vault.is_some()); + config.validate().unwrap(); + } + + #[test] + fn reject_zero_owner() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + + #[test] + fn reject_bps_over_10000() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.fee_vault] +address = "0x000000000000000000000000000000000000FE00" +owner = "0x000000000000000000000000000000000000Ad00" +bridge_share_bps = 10001 +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + + #[test] + fn admin_proxy_only() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.admin_proxy.is_some()); + assert!(config.contracts.fee_vault.is_none()); + } +} diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs new file mode 100644 index 00000000..7f824505 --- /dev/null +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -0,0 +1,47 @@ +//! `AdminProxy` bytecode and storage encoding. + +use crate::config::AdminProxyConfig; +use crate::contracts::GenesisContract; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `AdminProxy` runtime bytecode extracted from devnet-genesis.json. +const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c565806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840191505092915050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056fea26469706673582212201029704c8e76cc8133cedd39a8adbebfe979b8809644c7f5e9cff417e23119d464736f6c634300081e0033"); + +/// Build a genesis alloc entry for `AdminProxy`. +pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { + let mut storage = BTreeMap::new(); + + // Slot 0: owner (address left-padded to 32 bytes) + let owner_value = B256::from(U256::from_be_bytes( + config.owner.into_word().0, + )); + storage.insert(B256::ZERO, owner_value); + + GenesisContract { + address: config.address, + code: Bytes::from_static(ADMIN_PROXY_BYTECODE), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + + #[test] + fn golden_admin_proxy_storage() { + let config = AdminProxyConfig { + address: address!("000000000000000000000000000000000000Ad00"), + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }; + let contract = build(&config); + + let expected_slot0: B256 = + "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + .parse() + .unwrap(); + assert_eq!(contract.storage[&B256::ZERO], expected_slot0); + } +} diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs new file mode 100644 index 00000000..a3b800d3 --- /dev/null +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -0,0 +1,139 @@ +//! `FeeVault` bytecode and storage encoding. + +use crate::config::FeeVaultConfig; +use crate::contracts::GenesisContract; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `FeeVault` runtime bytecode extracted via `forge inspect FeeVault deployedBytecode`. +const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056fea2646970667358221220d8a8f8f514d9029e1bd2de81595378461d338d87b12ff03d5b032f7d66a03f4664736f6c63430008210033"); + +/// Build a genesis alloc entry for `FeeVault`. +pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { + let mut storage = BTreeMap::new(); + + // Apply constructor default: bps 0 -> 10000 + let effective_bps = if config.bridge_share_bps == 0 { 10000 } else { config.bridge_share_bps }; + + // Slot 0: hypNativeMinter (address) + storage.insert( + B256::ZERO, + B256::from(U256::from_be_bytes(config.hyp_native_minter.into_word().0)), + ); + + // Slot 1: owner (lower 160 bits) + destinationDomain (shifted left 160 bits) + let owner_u256 = U256::from_be_bytes(config.owner.into_word().0); + let domain_u256 = U256::from(config.destination_domain) << 160; + storage.insert(B256::with_last_byte(1), B256::from(owner_u256 | domain_u256)); + + // Slot 2: recipientAddress (bytes32) + storage.insert(B256::with_last_byte(2), config.recipient_address); + + // Slot 3: minimumAmount + storage.insert( + B256::with_last_byte(3), + B256::from(U256::from(config.minimum_amount)), + ); + + // Slot 4: callFee + storage.insert( + B256::with_last_byte(4), + B256::from(U256::from(config.call_fee)), + ); + + // Slot 5: otherRecipient (address) + storage.insert( + B256::with_last_byte(5), + B256::from(U256::from_be_bytes(config.other_recipient.into_word().0)), + ); + + // Slot 6: bridgeShareBps + storage.insert( + B256::with_last_byte(6), + B256::from(U256::from(effective_bps)), + ); + + GenesisContract { + address: config.address, + code: Bytes::from_static(FEE_VAULT_BYTECODE), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, Address}; + + #[test] + fn fee_vault_storage_encoding() { + let config = FeeVaultConfig { + address: address!("000000000000000000000000000000000000FE00"), + owner: address!("000000000000000000000000000000000000Ad00"), + destination_domain: 0, + recipient_address: B256::ZERO, + minimum_amount: 0, + call_fee: 0, + bridge_share_bps: 10000, + other_recipient: Address::ZERO, + hyp_native_minter: Address::ZERO, + }; + let contract = build(&config); + + // Slot 0: hypNativeMinter = zero + assert_eq!(contract.storage[&B256::ZERO], B256::ZERO); + + // Slot 1: owner packed with domain + let expected_slot1: B256 = + "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&B256::with_last_byte(1)], expected_slot1); + + // Slot 6: bridgeShareBps = 10000 + let expected_slot6 = B256::from(U256::from(10000u64)); + assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); + } + + #[test] + fn bps_zero_defaults_to_10000() { + let config = FeeVaultConfig { + address: address!("000000000000000000000000000000000000FE00"), + owner: address!("000000000000000000000000000000000000Ad00"), + destination_domain: 0, + recipient_address: B256::ZERO, + minimum_amount: 0, + call_fee: 0, + bridge_share_bps: 0, + other_recipient: Address::ZERO, + hyp_native_minter: Address::ZERO, + }; + let contract = build(&config); + + let expected_slot6 = B256::from(U256::from(10000u64)); + assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); + } + + #[test] + fn slot1_packing_with_nonzero_domain() { + let config = FeeVaultConfig { + address: address!("000000000000000000000000000000000000FE00"), + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + destination_domain: 42, + recipient_address: B256::ZERO, + minimum_amount: 0, + call_fee: 0, + bridge_share_bps: 10000, + other_recipient: Address::ZERO, + hyp_native_minter: Address::ZERO, + }; + let contract = build(&config); + + // slot1 = (42 << 160) | owner + let owner_u256 = U256::from_be_bytes( + address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266").into_word().0, + ); + let expected = B256::from((U256::from(42u32) << 160) | owner_u256); + assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs new file mode 100644 index 00000000..8ef01558 --- /dev/null +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -0,0 +1,17 @@ +//! Contract bytecode and storage encoding. + +pub(crate) mod admin_proxy; +pub(crate) mod fee_vault; + +use alloy_primitives::{Address, Bytes, B256}; +use std::collections::BTreeMap; + +/// A contract ready to be placed in genesis alloc. +pub(crate) struct GenesisContract { + /// The address to deploy at. + pub address: Address, + /// Runtime bytecode. + pub code: Bytes, + /// Storage slot values. + pub storage: BTreeMap, +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs new file mode 100644 index 00000000..f434ac5f --- /dev/null +++ b/bin/ev-deployer/src/genesis.rs @@ -0,0 +1,169 @@ +//! Genesis alloc JSON builder. + +use crate::config::DeployConfig; +use crate::contracts::{self, GenesisContract}; +use alloy_primitives::B256; +use serde_json::{Map, Value}; +use std::path::Path; + +/// Build the alloc JSON from config. +pub(crate) fn build_alloc(config: &DeployConfig) -> Value { + let mut alloc = Map::new(); + + if let Some(ref ap_config) = config.contracts.admin_proxy { + let contract = contracts::admin_proxy::build(ap_config); + insert_contract(&mut alloc, &contract); + } + + if let Some(ref fv_config) = config.contracts.fee_vault { + let contract = contracts::fee_vault::build(fv_config); + insert_contract(&mut alloc, &contract); + } + + Value::Object(alloc) +} + +/// Build alloc and merge into an existing genesis JSON file. +pub(crate) fn merge_into( + config: &DeployConfig, + genesis_path: &Path, + force: bool, +) -> eyre::Result { + let content = std::fs::read_to_string(genesis_path)?; + let mut genesis: Value = serde_json::from_str(&content)?; + + let alloc = build_alloc(config); + + let genesis_alloc = genesis + .get_mut("alloc") + .and_then(|v| v.as_object_mut()) + .ok_or_else(|| eyre::eyre!("genesis JSON missing 'alloc' object"))?; + + let new_alloc = alloc.as_object().unwrap(); + for (addr, entry) in new_alloc { + if genesis_alloc.contains_key(addr) && !force { + eyre::bail!( + "address collision at {addr}; use --force to overwrite" + ); + } + genesis_alloc.insert(addr.clone(), entry.clone()); + } + + Ok(genesis) +} + +fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { + // Address key without 0x prefix, using checksummed format + let addr_hex = format!("{}", contract.address); + let addr_key = addr_hex.strip_prefix("0x").unwrap_or(&addr_hex); + + let mut storage_map = Map::new(); + for (slot, value) in &contract.storage { + let slot_key = format_slot_key(slot); + storage_map.insert(slot_key, Value::String(format!("{value}"))); + } + + let mut entry = Map::new(); + entry.insert("balance".to_string(), Value::String("0x0".to_string())); + entry.insert( + "code".to_string(), + Value::String(format!("0x{}", alloy_primitives::hex::encode(&contract.code))), + ); + entry.insert("storage".to_string(), Value::Object(storage_map)); + + alloc.insert(addr_key.to_string(), Value::Object(entry)); +} + +/// Format a storage slot key in the compact form used by existing genesis files. +/// `B256::ZERO` -> "0x0", `B256::with_last_byte(1)` -> "0x1", etc. +fn format_slot_key(slot: &B256) -> String { + let u = alloy_primitives::U256::from_be_bytes(slot.0); + if u.is_zero() { + "0x0".to_string() + } else { + format!("0x{u:x}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::*; + use alloy_primitives::address; + + fn test_config() -> DeployConfig { + DeployConfig { + chain: ChainConfig { chain_id: 1234 }, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: address!("000000000000000000000000000000000000Ad00"), + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + fee_vault: None, + }, + } + } + + #[test] + fn alloc_json_structure() { + let alloc = build_alloc(&test_config()); + let obj = alloc.as_object().unwrap(); + assert!(obj.contains_key("000000000000000000000000000000000000Ad00")); + + let entry = obj + .get("000000000000000000000000000000000000Ad00") + .unwrap() + .as_object() + .unwrap(); + assert_eq!(entry["balance"], "0x0"); + assert!(entry["code"].as_str().unwrap().starts_with("0x")); + assert!(entry.contains_key("storage")); + } + + #[test] + fn alloc_golden_value() { + let alloc = build_alloc(&test_config()); + let storage = alloc + .as_object() + .unwrap() + .get("000000000000000000000000000000000000Ad00") + .unwrap() + .get("storage") + .unwrap() + .as_object() + .unwrap(); + + assert_eq!( + storage["0x0"], + "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ); + } + + #[test] + fn slot_key_formatting() { + assert_eq!(format_slot_key(&B256::ZERO), "0x0"); + assert_eq!(format_slot_key(&B256::with_last_byte(1)), "0x1"); + assert_eq!(format_slot_key(&B256::with_last_byte(6)), "0x6"); + } + + #[test] + fn merge_detects_collision() { + let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + let tmp = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(tmp.path(), genesis).unwrap(); + + let result = merge_into(&test_config(), tmp.path(), false); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("address collision")); + } + + #[test] + fn merge_force_overwrites() { + let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + let tmp = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(tmp.path(), genesis).unwrap(); + + let result = merge_into(&test_config(), tmp.path(), true); + assert!(result.is_ok()); + } +} diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs new file mode 100644 index 00000000..d2b4c30a --- /dev/null +++ b/bin/ev-deployer/src/main.rs @@ -0,0 +1,117 @@ +//! EV Deployer — genesis alloc generator for ev-reth contracts. + +mod config; +mod contracts; +mod genesis; +mod output; + +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +/// EV Deployer: generate genesis alloc entries for ev-reth contracts. +#[derive(Parser)] +#[command(name = "ev-deployer", about = "Generate genesis alloc for ev-reth contracts")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Generate genesis alloc JSON from a deploy config. + Genesis { + /// Path to the deploy TOML config. + #[arg(long)] + config: PathBuf, + + /// Write alloc JSON to this file instead of stdout. + #[arg(long)] + output: Option, + + /// Merge alloc entries into an existing genesis JSON file. + #[arg(long)] + merge_into: Option, + + /// Allow overwriting existing addresses when merging. + #[arg(long, default_value_t = false)] + force: bool, + + /// Write an address manifest to this file. + #[arg(long)] + addresses_out: Option, + }, + /// Compute the address for a configured contract. + ComputeAddress { + /// Path to the deploy TOML config. + #[arg(long)] + config: PathBuf, + + /// Contract name (`admin_proxy` or `fee_vault`). + #[arg(long)] + contract: String, + }, +} + +fn main() -> eyre::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Command::Genesis { + config: config_path, + output, + merge_into, + force, + addresses_out, + } => { + let cfg = config::DeployConfig::load(&config_path)?; + + let result = if let Some(ref genesis_path) = merge_into { + genesis::merge_into(&cfg, genesis_path, force)? + } else { + genesis::build_alloc(&cfg) + }; + + let json = serde_json::to_string_pretty(&result)?; + + if let Some(ref out_path) = output { + std::fs::write(out_path, &json)?; + eprintln!("Wrote alloc to {}", out_path.display()); + } else { + println!("{json}"); + } + + if let Some(ref addr_path) = addresses_out { + let manifest = output::build_manifest(&cfg); + let manifest_json = serde_json::to_string_pretty(&manifest)?; + std::fs::write(addr_path, &manifest_json)?; + eprintln!("Wrote address manifest to {}", addr_path.display()); + } + } + Command::ComputeAddress { + config: config_path, + contract, + } => { + let cfg = config::DeployConfig::load(&config_path)?; + + let address = match contract.as_str() { + "admin_proxy" => cfg + .contracts + .admin_proxy + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, + "fee_vault" => cfg + .contracts + .fee_vault + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("fee_vault not configured"))?, + other => eyre::bail!("unknown contract: {other}"), + }; + + println!("{address}"); + } + } + + Ok(()) +} diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs new file mode 100644 index 00000000..22bf063c --- /dev/null +++ b/bin/ev-deployer/src/output.rs @@ -0,0 +1,25 @@ +//! Address manifest output. + +use crate::config::DeployConfig; +use serde_json::{Map, Value}; + +/// Build an address manifest JSON from config. +pub(crate) fn build_manifest(config: &DeployConfig) -> Value { + let mut manifest = Map::new(); + + if let Some(ref ap) = config.contracts.admin_proxy { + manifest.insert( + "admin_proxy".to_string(), + Value::String(format!("{}", ap.address)), + ); + } + + if let Some(ref fv) = config.contracts.fee_vault { + manifest.insert( + "fee_vault".to_string(), + Value::String(format!("{}", fv.address)), + ); + } + + Value::Object(manifest) +} diff --git a/contracts/foundry.lock b/contracts/foundry.lock new file mode 100644 index 00000000..aee2c9a8 --- /dev/null +++ b/contracts/foundry.lock @@ -0,0 +1,5 @@ +{ + "lib/forge-std": { + "rev": "887e87251562513a7b5ab1ea517c039fe6ee0984" + } +} \ No newline at end of file diff --git a/justfile b/justfile index 935c3a03..757a5b96 100644 --- a/justfile +++ b/justfile @@ -34,6 +34,10 @@ build-maxperf: build-all: {{cargo}} build --workspace --release +# Build the ev-deployer binary in release mode +build-deployer: + {{cargo}} build --release --bin ev-deployer + # Testing ────────────────────────────────────────────── # Run all tests @@ -64,6 +68,10 @@ test-evolve: test-common: {{cargo}} test -p ev-common +# Test the deployer crate +test-deployer: + {{cargo}} test -p ev-deployer + # Development ────────────────────────────────────────── # Run the ev-reth node with default settings From 0c8f54eb2b23ece0a91b2bd679d117b167128fb3 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 17:38:19 +0100 Subject: [PATCH 02/81] test: add bytecode verification tests for ev-deployer contracts --- .github/workflows/ev_deployer.yml | 41 ++++++++++++++++++++ bin/ev-deployer/src/contracts/admin_proxy.rs | 39 ++++++++++++++++++- bin/ev-deployer/src/contracts/fee_vault.rs | 39 ++++++++++++++++++- contracts/foundry.toml | 3 ++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/ev_deployer.yml diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml new file mode 100644 index 00000000..3af3a9ce --- /dev/null +++ b/.github/workflows/ev_deployer.yml @@ -0,0 +1,41 @@ +name: EV Deployer CI + +on: + push: + paths: + - 'contracts/src/**' + - 'contracts/foundry.toml' + - 'bin/ev-deployer/**' + pull_request: + paths: + - 'contracts/src/**' + - 'contracts/foundry.toml' + - 'bin/ev-deployer/**' + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + verify-bytecodes: + name: Verify contract bytecodes + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run bytecode verification tests + run: cargo test -p ev-deployer -- --ignored + + - name: Run unit tests + run: cargo test -p ev-deployer diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index 7f824505..dc2d9085 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -5,8 +5,9 @@ use crate::contracts::GenesisContract; use alloy_primitives::{hex, Bytes, B256, U256}; use std::collections::BTreeMap; -/// `AdminProxy` runtime bytecode extracted from devnet-genesis.json. -const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c565806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840191505092915050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056fea26469706673582212201029704c8e76cc8133cedd39a8adbebfe979b8809644c7f5e9cff417e23119d464736f6c634300081e0033"); +/// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (cbor_metadata=false). +/// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` +const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); /// Build a genesis alloc entry for `AdminProxy`. pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { @@ -29,6 +30,8 @@ pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { mod tests { use super::*; use alloy_primitives::address; + use std::path::PathBuf; + use std::process::Command; #[test] fn golden_admin_proxy_storage() { @@ -44,4 +47,36 @@ mod tests { .unwrap(); assert_eq!(contract.storage[&B256::ZERO], expected_slot0); } + + #[test] + #[ignore = "requires forge CLI"] + fn admin_proxy_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts"); + + let output = Command::new("forge") + .args(["inspect", "AdminProxy", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!(output.status.success(), "forge inspect failed: {}", String::from_utf8_lossy(&output.stderr)); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(ADMIN_PROXY_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "AdminProxy bytecode mismatch! Update the constant with: cd contracts && forge inspect AdminProxy deployedBytecode" + ); + } } diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs index a3b800d3..57fbf4ae 100644 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -5,8 +5,9 @@ use crate::contracts::GenesisContract; use alloy_primitives::{hex, Bytes, B256, U256}; use std::collections::BTreeMap; -/// `FeeVault` runtime bytecode extracted via `forge inspect FeeVault deployedBytecode`. -const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056fea2646970667358221220d8a8f8f514d9029e1bd2de81595378461d338d87b12ff03d5b032f7d66a03f4664736f6c63430008210033"); +/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (cbor_metadata=false). +/// Regenerate with: `cd contracts && forge inspect FeeVault deployedBytecode` +const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); /// Build a genesis alloc entry for `FeeVault`. pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { @@ -64,6 +65,8 @@ pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { mod tests { use super::*; use alloy_primitives::{address, Address}; + use std::path::PathBuf; + use std::process::Command; #[test] fn fee_vault_storage_encoding() { @@ -136,4 +139,36 @@ mod tests { let expected = B256::from((U256::from(42u32) << 160) | owner_u256); assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); } + + #[test] + #[ignore = "requires forge CLI"] + fn fee_vault_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts"); + + let output = Command::new("forge") + .args(["inspect", "FeeVault", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!(output.status.success(), "forge inspect failed: {}", String::from_utf8_lossy(&output.stderr)); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(FEE_VAULT_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "FeeVault bytecode mismatch! Update the constant with: cd contracts && forge inspect FeeVault deployedBytecode" + ); + } } diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 25b918f9..7bfd1700 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -2,5 +2,8 @@ src = "src" out = "out" libs = ["lib"] +solc_version = "0.8.33" +cbor_metadata = false +bytecode_hash = "none" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options From 2ba2b8033a336c44bbc296ee0b373f88e6ca8791 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 18:13:17 +0100 Subject: [PATCH 03/81] docs: add ev-deployer README with config and usage guide --- bin/ev-deployer/README.md | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 bin/ev-deployer/README.md diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md new file mode 100644 index 00000000..b6bd48cc --- /dev/null +++ b/bin/ev-deployer/README.md @@ -0,0 +1,134 @@ +# EV Deployer + +CLI tool for generating genesis alloc entries for ev-reth contracts. It reads a declarative TOML config and produces the JSON needed to embed contracts into a chain's genesis state. + +## Building + +```bash +just build-deployer +``` + +The binary is output to `target/release/ev-deployer`. + +## Configuration + +EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example. + +```toml +[chain] +chain_id = 1234 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.fee_vault] +address = "0x000000000000000000000000000000000000FE00" +owner = "0x000000000000000000000000000000000000Ad00" +destination_domain = 0 +recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" +minimum_amount = 0 +call_fee = 0 +bridge_share_bps = 10000 +other_recipient = "0x0000000000000000000000000000000000000000" +hyp_native_minter = "0x0000000000000000000000000000000000000000" +``` + +Both contracts are optional — include only the sections you need. + +### Config reference + +#### `[chain]` + +| Field | Type | Description | +|------------|------|-------------| +| `chain_id` | u64 | Chain ID | + +#### `[contracts.admin_proxy]` + +| Field | Type | Description | +|-----------|---------|---------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner (must not be zero) | + +#### `[contracts.fee_vault]` + +| Field | Type | Default | Description | +|----------------------|---------|---------|------------------------------------------------| +| `address` | address | — | Address to deploy at | +| `owner` | address | — | Owner (must not be zero) | +| `destination_domain` | u32 | 0 | Hyperlane destination domain | +| `recipient_address` | bytes32 | 0x0…0 | Hyperlane recipient | +| `minimum_amount` | u64 | 0 | Minimum amount for bridging | +| `call_fee` | u64 | 0 | Fee for sendToCelestia | +| `bridge_share_bps` | u64 | 0 | Bridge share in basis points (0–10000). 0 maps to 10000 | +| `other_recipient` | address | 0x0…0 | Split accounting recipient | +| `hyp_native_minter` | address | 0x0…0 | HypNativeMinter address | + +## Usage + +### Generate genesis alloc + +Print alloc JSON to stdout: + +```bash +ev-deployer genesis --config deploy.toml +``` + +Write to a file: + +```bash +ev-deployer genesis --config deploy.toml --output alloc.json +``` + +### Merge into an existing genesis file + +Insert the generated entries into an existing `genesis.json`. This modifies the `alloc` field in-place and writes the full result: + +```bash +ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json +``` + +If an address already exists in the genesis, the command fails. Use `--force` to overwrite: + +```bash +ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json --force +``` + +### Export address manifest + +Write a JSON mapping of contract names to their configured addresses: + +```bash +ev-deployer genesis --config deploy.toml --addresses-out addresses.json +``` + +Output: + +```json +{ + "admin_proxy": "0x000000000000000000000000000000000000Ad00", + "fee_vault": "0x000000000000000000000000000000000000FE00" +} +``` + +### Look up a contract address + +```bash +ev-deployer compute-address --config deploy.toml --contract admin_proxy +``` + +## Contracts + +| Contract | Description | +|----------------|-----------------------------------------------------| +| `admin_proxy` | Proxy contract with owner-based access control | +| `fee_vault` | Fee vault with Hyperlane bridge integration | + +Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. + +## Testing + +```bash +just test-deployer +``` From a5408583ac8e08ea4997091a09451aefee85e8a2 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 18:49:58 +0100 Subject: [PATCH 04/81] fix(ci): serialize bytecode verification tests to avoid solc race condition --- .github/workflows/ev_deployer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml index 3af3a9ce..7ed86ce7 100644 --- a/.github/workflows/ev_deployer.yml +++ b/.github/workflows/ev_deployer.yml @@ -35,7 +35,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: Run bytecode verification tests - run: cargo test -p ev-deployer -- --ignored + run: cargo test -p ev-deployer -- --ignored --test-threads=1 - name: Run unit tests run: cargo test -p ev-deployer From b9e26706d11a9a810f2e7f421abd06337c192851 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 18:51:04 +0100 Subject: [PATCH 05/81] style: apply cargo fmt to ev-deployer --- bin/ev-deployer/src/contracts/admin_proxy.rs | 10 ++++++---- bin/ev-deployer/src/contracts/fee_vault.rs | 21 ++++++++++++++++---- bin/ev-deployer/src/genesis.rs | 14 ++++++++----- bin/ev-deployer/src/main.rs | 5 ++++- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index dc2d9085..f50ccd70 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -14,9 +14,7 @@ pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { let mut storage = BTreeMap::new(); // Slot 0: owner (address left-padded to 32 bytes) - let owner_value = B256::from(U256::from_be_bytes( - config.owner.into_word().0, - )); + let owner_value = B256::from(U256::from_be_bytes(config.owner.into_word().0)); storage.insert(B256::ZERO, owner_value); GenesisContract { @@ -63,7 +61,11 @@ mod tests { .output() .expect("forge not found"); - assert!(output.status.success(), "forge inspect failed: {}", String::from_utf8_lossy(&output.stderr)); + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); let forge_hex = String::from_utf8(output.stdout) .unwrap() diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs index 57fbf4ae..1341c3d6 100644 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -14,7 +14,11 @@ pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { let mut storage = BTreeMap::new(); // Apply constructor default: bps 0 -> 10000 - let effective_bps = if config.bridge_share_bps == 0 { 10000 } else { config.bridge_share_bps }; + let effective_bps = if config.bridge_share_bps == 0 { + 10000 + } else { + config.bridge_share_bps + }; // Slot 0: hypNativeMinter (address) storage.insert( @@ -25,7 +29,10 @@ pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { // Slot 1: owner (lower 160 bits) + destinationDomain (shifted left 160 bits) let owner_u256 = U256::from_be_bytes(config.owner.into_word().0); let domain_u256 = U256::from(config.destination_domain) << 160; - storage.insert(B256::with_last_byte(1), B256::from(owner_u256 | domain_u256)); + storage.insert( + B256::with_last_byte(1), + B256::from(owner_u256 | domain_u256), + ); // Slot 2: recipientAddress (bytes32) storage.insert(B256::with_last_byte(2), config.recipient_address); @@ -134,7 +141,9 @@ mod tests { // slot1 = (42 << 160) | owner let owner_u256 = U256::from_be_bytes( - address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266").into_word().0, + address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + .into_word() + .0, ); let expected = B256::from((U256::from(42u32) << 160) | owner_u256); assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); @@ -155,7 +164,11 @@ mod tests { .output() .expect("forge not found"); - assert!(output.status.success(), "forge inspect failed: {}", String::from_utf8_lossy(&output.stderr)); + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); let forge_hex = String::from_utf8(output.stdout) .unwrap() diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index f434ac5f..38da8f6a 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -42,9 +42,7 @@ pub(crate) fn merge_into( let new_alloc = alloc.as_object().unwrap(); for (addr, entry) in new_alloc { if genesis_alloc.contains_key(addr) && !force { - eyre::bail!( - "address collision at {addr}; use --force to overwrite" - ); + eyre::bail!("address collision at {addr}; use --force to overwrite"); } genesis_alloc.insert(addr.clone(), entry.clone()); } @@ -67,7 +65,10 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { entry.insert("balance".to_string(), Value::String("0x0".to_string())); entry.insert( "code".to_string(), - Value::String(format!("0x{}", alloy_primitives::hex::encode(&contract.code))), + Value::String(format!( + "0x{}", + alloy_primitives::hex::encode(&contract.code) + )), ); entry.insert("storage".to_string(), Value::Object(storage_map)); @@ -154,7 +155,10 @@ mod tests { let result = merge_into(&test_config(), tmp.path(), false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("address collision")); + assert!(result + .unwrap_err() + .to_string() + .contains("address collision")); } #[test] diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index d2b4c30a..42ad6a4a 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -10,7 +10,10 @@ use std::path::PathBuf; /// EV Deployer: generate genesis alloc entries for ev-reth contracts. #[derive(Parser)] -#[command(name = "ev-deployer", about = "Generate genesis alloc for ev-reth contracts")] +#[command( + name = "ev-deployer", + about = "Generate genesis alloc for ev-reth contracts" +)] struct Cli { #[command(subcommand)] command: Command, From 18ed817d228ff80e6d0e407bbe07285526af41b2 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 18:58:19 +0100 Subject: [PATCH 06/81] ci(ev-deployer): split workflow into separate bytecode and unit test jobs --- .github/workflows/ev_deployer.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml index 7ed86ce7..329e90ef 100644 --- a/.github/workflows/ev_deployer.yml +++ b/.github/workflows/ev_deployer.yml @@ -18,7 +18,7 @@ env: jobs: verify-bytecodes: - name: Verify contract bytecodes + name: EV Deployer bytecode verification runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -37,5 +37,17 @@ jobs: - name: Run bytecode verification tests run: cargo test -p ev-deployer -- --ignored --test-threads=1 + unit-tests: + name: EV Deployer unit tests + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Run unit tests run: cargo test -p ev-deployer From f7d0e71ed3e072748352d60593c89061ab402b46 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 16 Mar 2026 12:08:16 +0100 Subject: [PATCH 07/81] style: fix fmt and clippy lint errors in ev-deployer --- bin/ev-deployer/src/contracts/admin_proxy.rs | 8 +++----- bin/ev-deployer/src/contracts/fee_vault.rs | 8 +++----- bin/ev-deployer/src/genesis.rs | 6 ++++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index f50ccd70..51d5ce59 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -1,11 +1,10 @@ //! `AdminProxy` bytecode and storage encoding. -use crate::config::AdminProxyConfig; -use crate::contracts::GenesisContract; +use crate::{config::AdminProxyConfig, contracts::GenesisContract}; use alloy_primitives::{hex, Bytes, B256, U256}; use std::collections::BTreeMap; -/// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (cbor_metadata=false). +/// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). /// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); @@ -28,8 +27,7 @@ pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { mod tests { use super::*; use alloy_primitives::address; - use std::path::PathBuf; - use std::process::Command; + use std::{path::PathBuf, process::Command}; #[test] fn golden_admin_proxy_storage() { diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs index 1341c3d6..c99bd39b 100644 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -1,11 +1,10 @@ //! `FeeVault` bytecode and storage encoding. -use crate::config::FeeVaultConfig; -use crate::contracts::GenesisContract; +use crate::{config::FeeVaultConfig, contracts::GenesisContract}; use alloy_primitives::{hex, Bytes, B256, U256}; use std::collections::BTreeMap; -/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (cbor_metadata=false). +/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). /// Regenerate with: `cd contracts && forge inspect FeeVault deployedBytecode` const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); @@ -72,8 +71,7 @@ pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { mod tests { use super::*; use alloy_primitives::{address, Address}; - use std::path::PathBuf; - use std::process::Command; + use std::{path::PathBuf, process::Command}; #[test] fn fee_vault_storage_encoding() { diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 38da8f6a..9b930af8 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -1,7 +1,9 @@ //! Genesis alloc JSON builder. -use crate::config::DeployConfig; -use crate::contracts::{self, GenesisContract}; +use crate::{ + config::DeployConfig, + contracts::{self, GenesisContract}, +}; use alloy_primitives::B256; use serde_json::{Map, Value}; use std::path::Path; From 46ea9a6f06faf93ebe2800e595de4eb92937e887 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 18 Mar 2026 15:04:06 +0100 Subject: [PATCH 08/81] feat(ev-deployer): add MerkleTreeHook contract with immutable bytecode patching Add Hyperlane monorepo as git submodule pinned to @hyperlane-xyz/core@11.0.3. Implement bytecode patching utility for Solidity immutable variables and MerkleTreeHook genesis alloc generation with mailbox/localDomain/deployedBlock patching. This is the foundation for embedding Hyperlane contracts at genesis. --- .gitmodules | 3 + bin/ev-deployer/examples/devnet.toml | 5 + bin/ev-deployer/src/config.rs | 53 ++++ bin/ev-deployer/src/contracts/immutables.rs | 127 +++++++++ .../src/contracts/merkle_tree_hook.rs | 254 ++++++++++++++++++ bin/ev-deployer/src/contracts/mod.rs | 2 + bin/ev-deployer/src/genesis.rs | 7 + bin/ev-deployer/src/main.rs | 6 + bin/ev-deployer/src/output.rs | 7 + contracts/lib/hyperlane-monorepo | 1 + 10 files changed, 465 insertions(+) create mode 100644 bin/ev-deployer/src/contracts/immutables.rs create mode 100644 bin/ev-deployer/src/contracts/merkle_tree_hook.rs create mode 160000 contracts/lib/hyperlane-monorepo diff --git a/.gitmodules b/.gitmodules index c65a5965..735b8dc8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "contracts/lib/forge-std"] path = contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "contracts/lib/hyperlane-monorepo"] + path = contracts/lib/hyperlane-monorepo + url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index f332b1ad..87bafe09 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -15,3 +15,8 @@ call_fee = 0 bridge_share_bps = 10000 other_recipient = "0x0000000000000000000000000000000000000000" hyp_native_minter = "0x0000000000000000000000000000000000000000" + +[contracts.merkle_tree_hook] +address = "0x0000000000000000000000000000000000001100" +owner = "0x000000000000000000000000000000000000Ad00" +mailbox = "0x0000000000000000000000000000000000001200" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 6bb60482..bed1e625 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -29,6 +29,8 @@ pub(crate) struct ContractsConfig { pub admin_proxy: Option, /// `FeeVault` contract config (optional). pub fee_vault: Option, + /// `MerkleTreeHook` contract config (optional). + pub merkle_tree_hook: Option, } /// `AdminProxy` configuration. @@ -70,6 +72,18 @@ pub(crate) struct FeeVaultConfig { pub hyp_native_minter: Address, } +/// `MerkleTreeHook` configuration (Hyperlane required hook). +#[derive(Debug, Deserialize)] +pub(crate) struct MerkleTreeHookConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address (for post-genesis hook/ISM changes). + #[serde(default)] + pub owner: Address, + /// Mailbox address (patched into bytecode as immutable). + pub mailbox: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -100,6 +114,13 @@ impl DeployConfig { ); } + if let Some(ref mth) = self.contracts.merkle_tree_hook { + eyre::ensure!( + !mth.mailbox.is_zero(), + "merkle_tree_hook.mailbox must not be the zero address" + ); + } + Ok(()) } } @@ -165,6 +186,38 @@ bridge_share_bps = 10001 assert!(config.validate().is_err()); } + #[test] + fn parse_merkle_tree_hook_config() { + let toml = r#" +[chain] +chain_id = 1234 + +[contracts.merkle_tree_hook] +address = "0x0000000000000000000000000000000000001100" +owner = "0x000000000000000000000000000000000000ad00" +mailbox = "0x0000000000000000000000000000000000001200" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.merkle_tree_hook.is_some()); + let mth = config.contracts.merkle_tree_hook.unwrap(); + assert!(!mth.mailbox.is_zero()); + } + + #[test] + fn reject_zero_mailbox_merkle_tree_hook() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.merkle_tree_hook] +address = "0x0000000000000000000000000000000000001100" +mailbox = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + #[test] fn admin_proxy_only() { let toml = r#" diff --git a/bin/ev-deployer/src/contracts/immutables.rs b/bin/ev-deployer/src/contracts/immutables.rs new file mode 100644 index 00000000..40c75f48 --- /dev/null +++ b/bin/ev-deployer/src/contracts/immutables.rs @@ -0,0 +1,127 @@ +//! Bytecode patching for Solidity immutable variables. +//! +//! Solidity `immutable` values are embedded in the **runtime bytecode** by the +//! compiler, not in storage. When compiling with placeholder values (e.g. +//! `address(0)`, `uint32(0)`), the compiler leaves zero-filled regions at known +//! byte offsets. This module replaces those regions with the actual values from +//! the deploy config at genesis-generation time. + +use alloy_primitives::{Address, B256, U256}; + +/// A single immutable reference inside a bytecode blob. +#[derive(Debug, Clone, Copy)] +pub(crate) struct ImmutableRef { + /// Byte offset into the **runtime** bytecode. + pub start: usize, + /// Number of bytes (always 32 for EVM words). + pub length: usize, +} + +/// Patch a mutable bytecode slice, writing `value` at every listed offset. +/// +/// # Panics +/// +/// Panics if any reference extends past the end of `bytecode`. +pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) { + for r in refs { + assert!( + r.start + r.length <= bytecode.len(), + "immutable ref out of bounds: start={} length={} bytecode_len={}", + r.start, + r.length, + bytecode.len() + ); + bytecode[r.start..r.start + r.length].copy_from_slice(value); + } +} + +/// Convenience: patch with an ABI-encoded `address` (left-padded to 32 bytes). +pub(crate) fn patch_address(bytecode: &mut [u8], refs: &[ImmutableRef], addr: Address) { + let word: B256 = B256::from(U256::from_be_bytes(addr.into_word().0)); + patch_bytes(bytecode, refs, &word.0); +} + +/// Convenience: patch with an ABI-encoded `uint32` (left-padded to 32 bytes). +pub(crate) fn patch_u32(bytecode: &mut [u8], refs: &[ImmutableRef], val: u32) { + let word = B256::from(U256::from(val)); + patch_bytes(bytecode, refs, &word.0); +} + +/// Convenience: patch with an ABI-encoded `uint256`. +pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { + let word = B256::from(val); + patch_bytes(bytecode, refs, &word.0); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn patch_single_ref() { + let mut bytecode = vec![0u8; 64]; + let refs = [ImmutableRef { + start: 10, + length: 32, + }]; + let value = B256::from(U256::from(42u64)); + patch_bytes(&mut bytecode, &refs, &value.0); + + assert_eq!(bytecode[41], 42); + // bytes before are untouched + assert_eq!(bytecode[9], 0); + // bytes after are untouched + assert_eq!(bytecode[42], 0); + } + + #[test] + fn patch_multiple_refs() { + let mut bytecode = vec![0u8; 128]; + let refs = [ + ImmutableRef { + start: 0, + length: 32, + }, + ImmutableRef { + start: 64, + length: 32, + }, + ]; + let addr = Address::repeat_byte(0xAB); + patch_address(&mut bytecode, &refs, addr); + + // Both locations should have the address (last 20 bytes of the 32-byte word) + assert_eq!(bytecode[12..32], [0xAB; 20]); + assert_eq!(bytecode[76..96], [0xAB; 20]); + // Padding bytes should be zero + assert_eq!(bytecode[0..12], [0u8; 12]); + assert_eq!(bytecode[64..76], [0u8; 12]); + } + + #[test] + fn patch_u32_value() { + let mut bytecode = vec![0u8; 64]; + let refs = [ImmutableRef { + start: 0, + length: 32, + }]; + patch_u32(&mut bytecode, &refs, 1234); + + // uint32 1234 = 0x04D2, left-padded to 32 bytes + assert_eq!(bytecode[30], 0x04); + assert_eq!(bytecode[31], 0xD2); + assert_eq!(bytecode[0..30], [0u8; 30]); + } + + #[test] + #[should_panic(expected = "immutable ref out of bounds")] + fn patch_out_of_bounds_panics() { + let mut bytecode = vec![0u8; 16]; + let refs = [ImmutableRef { + start: 0, + length: 32, + }]; + let value = [0u8; 32]; + patch_bytes(&mut bytecode, &refs, &value); + } +} diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs new file mode 100644 index 00000000..ea52cfbd --- /dev/null +++ b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs @@ -0,0 +1,254 @@ +//! `MerkleTreeHook` bytecode and storage encoding. +//! +//! `MerkleTreeHook` is a Hyperlane post-dispatch hook that maintains an +//! incremental Merkle tree of dispatched message IDs. Validators sign +//! checkpoints against this tree to attest to the messages. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offsets | +//! |---------------|---------|---------------------| +//! | `mailbox` | address | [904, 3300] | +//! | `localDomain` | uint32 | [644] | +//! | `deployedBlock`| uint256| [578] | +//! +//! ## Storage layout (from `forge inspect MerkleTreeHook storageLayout`) +//! +//! | Slot | Variable | Type | +//! |------|-----------------------------|---------| +//! | 0 | `_initialized` + `_initializing` | uint8 + bool | +//! | 1-50 | `__gap` (Initializable) | — | +//! | 51 | `_owner` | address | +//! | 52-100| `__gap` (Ownable) | — | +//! | 101 | `hook` | address | +//! | 102 | `_interchainSecurityModule` | address | +//! | 103-150| `__GAP` (MailboxClient) | — | +//! | 151-182| `_tree.branch[0..31]` | bytes32[32] | +//! | 183 | `_tree.count` | uint256 | + +use crate::{ + config::MerkleTreeHookConfig, + contracts::{ + immutables::{patch_address, patch_u256, patch_u32, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `MerkleTreeHook` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode +/// ``` +const MERKLE_TREE_HOOK_BYTECODE: &[u8] = &hex!("6080604052600436106101445760003560e01c8063907c0f92116100c0578063e445e7dd11610074578063ebf0c71711610059578063ebf0c7171461042c578063f2fde38b14610441578063fd54b2281461046157600080fd5b8063e445e7dd146103d5578063e5320bb9146103fc57600080fd5b8063aaccd230116100a5578063aaccd23014610356578063d5438eae14610376578063de523cf3146103aa57600080fd5b8063907c0f92146102d157806393c448471461030057600080fd5b8063715018a61161011757806382ea7bfe116100fc57806382ea7bfe146102305780638d3638f4146102725780638da5cb5b146102a657600080fd5b8063715018a6146101c95780637f5a7c7b146101de57600080fd5b806306661abd14610149578063086011b9146101745780630e72cc06146101895780633dfd3873146101a9575b600080fd5b34801561015557600080fd5b5060b7545b60405163ffffffff90911681526020015b60405180910390f35b6101876101823660046114c2565b610483565b005b34801561019557600080fd5b506101876101a436600461152e565b610530565b3480156101b557600080fd5b506101876101c436600461152e565b610679565b3480156101d557600080fd5b506101876107ba565b3480156101ea57600080fd5b5060655461020b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161016b565b34801561023c57600080fd5b506102647f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200161016b565b34801561027e57600080fd5b5061015a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102b257600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156102dd57600080fd5b506102e66107ce565b6040805192835263ffffffff90911660208301520161016b565b34801561030c57600080fd5b506103496040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161016b919061156b565b34801561036257600080fd5b506102646103713660046114c2565b6107f6565b34801561038257600080fd5b5061020b7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103b657600080fd5b5060665473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156103e157600080fd5b506103ea610899565b60405160ff909116815260200161016b565b34801561040857600080fd5b5061041c6104173660046115d8565b6108a3565b604051901515815260200161016b565b34801561043857600080fd5b506102646108c8565b34801561044d57600080fd5b5061018761045c36600461152e565b6108d4565b34801561046d57600080fd5b5061047661098b565b60405161016b919061161a565b61048d84846108a3565b61051e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b61052a848484846109da565b50505050565b8073ffffffffffffffffffffffffffffffffffffffff81163b15158061056a575073ffffffffffffffffffffffffffffffffffffffff8116155b6105f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b6105fe610b78565b606680547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527fc47cbcc588c67679e52261c45cc315e56562f8d0ccaba16facb9093ff9498799906020015b60405180910390a15050565b8073ffffffffffffffffffffffffffffffffffffffff81163b1515806106b3575073ffffffffffffffffffffffffffffffffffffffff8116155b61073f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b610747610b78565b606580547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527f4eab7b127c764308788622363ad3e9532de3dfba7845bd4f84c125a22544255a9060200161066d565b6107c2610b78565b6107cc6000610bf9565b565b6000806107d96108c8565b60016107e460b75490565b6107ee919061168c565b915091509091565b600061080285856108a3565b61088e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e7400000000000000000000000000006064820152608401610515565b600095945050505050565b600060035b905090565b60008115806108bf575060016108b98484610c70565b61ffff16145b90505b92915050565b600061089e6097610cc1565b6108dc610b78565b73ffffffffffffffffffffffffffffffffffffffff811661097f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610515565b61098881610bf9565b50565b61099361143a565b60408051610440810180835290916097918391820190839060209082845b8154815260200190600101908083116109b1575050509183525050602091820154910152919050565b3415610a68576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d65726b6c6554726565486f6f6b3a206e6f2076616c7565206578706563746560448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610515565b6000610aa983838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610cd492505050565b9050610ab481610cdf565b610b1a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6d657373616765206e6f74206469737061746368696e670000000000000000006044820152606401610515565b6000610b2560b75490565b9050610b32609783610d78565b6040805183815263ffffffff831660208201527f253a3a04cab70d47c1504809242d9350cd81627b4f1d50753e159cf8cd76ed33910160405180910390a1505050505050565b60335473ffffffffffffffffffffffffffffffffffffffff1633146107cc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610515565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000610c7d8160026116b0565b60ff16821015610c8f575060006108c2565b82600083610c9e8260026116b0565b60ff1692610cae939291906116c9565b610cb7916116f3565b60f01c9392505050565b60006108c282610ccf610eb2565b611373565b805160209091012090565b6000817f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663134fbb4f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d4d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d719190611739565b1492915050565b6001610d8660206002611872565b610d90919061187e565b826020015410610dfc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6d65726b6c6520747265652066756c6c000000000000000000000000000000006044820152606401610515565b6001826020016000828254610e119190611891565b9091555050602082015460005b6020811015610ea45781600116600103610e4d5782848260208110610e4557610e456118a4565b015550505050565b838160208110610e5f57610e5f6118a4565b01546040805160208101929092528101849052606001604051602081830303815290604052805190602001209250600282610e9a91906118d3565b9150600101610e1e565b50610ead61190e565b505050565b610eba61145a565b600081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560208201527fb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3060408201527f21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba8560608201527fe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a1934460808201527f0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d60a08201527f887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a196860c08201527fffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f8360e08201527f9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af6101008201527fcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e06101208201527ff9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a56101408201527ff8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8926101608201527f3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c6101808201527fc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb6101a08201527f5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc6101c08201527fda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d26101e08201527f2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f6102008201527fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a6102208201527f5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a06102408201527fb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa06102608201527fc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e26102808201527ff4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd96102a08201527f5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3776102c08201527f4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee6526102e08201527fcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef6103008201527f0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6103208201527fb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d06103408201527f838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e6103608201527f662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e6103808201527f388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea3226103a08201527f93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7356103c08201527f8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a96103e082015290565b6020820154600090815b602081101561143257600182821c1660008683602081106113a0576113a06118a4565b01549050816001036113dd576040805160208101839052908101869052606001604051602081830303815290604052805190602001209450611428565b848684602081106113f0576113f06118a4565b602002015160405160200161140f929190918252602082015260400190565b6040516020818303038152906040528051906020012094505b505060010161137d565b505092915050565b604051806040016040528061144d61145a565b8152602001600081525090565b6040518061040001604052806020906020820280368337509192915050565b60008083601f84011261148b57600080fd5b50813567ffffffffffffffff8111156114a357600080fd5b6020830191508360208285010111156114bb57600080fd5b9250929050565b600080600080604085870312156114d857600080fd5b843567ffffffffffffffff808211156114f057600080fd5b6114fc88838901611479565b9096509450602087013591508082111561151557600080fd5b5061152287828801611479565b95989497509550505050565b60006020828403121561154057600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461156457600080fd5b9392505050565b60006020808352835180602085015260005b818110156115995785810183015185820160400152820161157d565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b600080602083850312156115eb57600080fd5b823567ffffffffffffffff81111561160257600080fd5b61160e85828601611479565b90969095509350505050565b81516104208201908260005b60208082106116355750611649565b835183529283019290910190600101611626565b505050602083015161040083015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff8281168282160390808211156116a9576116a961165d565b5092915050565b60ff81811683821601908111156108c2576108c261165d565b600080858511156116d957600080fd5b838611156116e657600080fd5b5050820193919092039150565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156114325760029490940360031b84901b1690921692915050565b60006020828403121561174b57600080fd5b5051919050565b600181815b808511156117ab57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156117915761179161165d565b8085161561179e57918102915b93841c9390800290611757565b509250929050565b6000826117c2575060016108c2565b816117cf575060006108c2565b81600181146117e557600281146117ef5761180b565b60019150506108c2565b60ff8411156118005761180061165d565b50506001821b6108c2565b5060208310610133831016604e8410600b841016171561182e575081810a6108c2565b6118388383611752565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561186a5761186a61165d565b029392505050565b60006108bf83836117b3565b818103818111156108c2576108c261165d565b808201808211156108c2576108c261165d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082611909577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd"); + +// ── Immutable reference offsets (from `forge inspect MerkleTreeHook immutableReferences`) ── + +/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. +const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 578, + length: 32, +}]; + +/// `mailbox` (address) — from `MailboxClient.sol`. +const MAILBOX_REFS: &[ImmutableRef] = &[ + ImmutableRef { + start: 904, + length: 32, + }, + ImmutableRef { + start: 3300, + length: 32, + }, +]; + +/// `localDomain` (uint32) — from `MailboxClient.sol`. +const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 644, + length: 32, +}]; + +/// Build a genesis alloc entry for `MerkleTreeHook`. +pub(crate) fn build(config: &MerkleTreeHookConfig, local_domain: u32) -> GenesisContract { + let mut bytecode = MERKLE_TREE_HOOK_BYTECODE.to_vec(); + + // Patch immutables + patch_address(&mut bytecode, MAILBOX_REFS, config.mailbox); + patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); + patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); + + let mut storage = BTreeMap::new(); + + // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false + // byte layout: [_initialized (1 byte)] [_initializing (1 byte)] [... 30 zero bytes] + // Packed at slot 0: value = 0x01 (just _initialized = 1) + storage.insert(B256::ZERO, B256::from(U256::from(1u8))); + + // Slot 51: _owner — set the owner so the contract can be administered post-genesis + if !config.owner.is_zero() { + storage.insert( + B256::from(U256::from(51u64)), + B256::from(U256::from_be_bytes(config.owner.into_word().0)), + ); + } + + // All other storage starts at zero: + // - hook (slot 101): zero = use mailbox default + // - _interchainSecurityModule (slot 102): zero = use mailbox default + // - _tree.branch[0..31] (slots 151-182): all zero (empty tree) + // - _tree.count (slot 183): 0 + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> MerkleTreeHookConfig { + MerkleTreeHookConfig { + address: address!("0000000000000000000000000000000000001100"), + owner: address!("000000000000000000000000000000000000ad00"), + mailbox: address!("0000000000000000000000000000000000001200"), + } + } + + #[test] + fn storage_has_initialized_flag() { + let contract = build(&test_config(), 1234); + assert_eq!( + contract.storage[&B256::ZERO], + B256::from(U256::from(1u8)), + "_initialized should be 1" + ); + } + + #[test] + fn storage_has_owner() { + let contract = build(&test_config(), 1234); + let owner_slot = B256::from(U256::from(51u64)); + let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&owner_slot], expected); + } + + #[test] + fn zero_owner_omits_slot() { + let config = MerkleTreeHookConfig { + address: address!("0000000000000000000000000000000000001100"), + owner: Address::ZERO, + mailbox: address!("0000000000000000000000000000000000001200"), + }; + let contract = build(&config, 1234); + let owner_slot = B256::from(U256::from(51u64)); + assert!( + !contract.storage.contains_key(&owner_slot), + "zero owner should not produce a storage entry" + ); + } + + #[test] + fn bytecode_is_patched_with_mailbox() { + let config = test_config(); + let contract = build(&config, 1234); + let code = contract.code.to_vec(); + + // Check that mailbox address is patched at both offsets + for &offset in &[904, 3300] { + let word = &code[offset..offset + 32]; + // Address is in the last 20 bytes of the 32-byte word + let addr_bytes = &word[12..32]; + assert_eq!( + addr_bytes, + config.mailbox.as_slice(), + "mailbox not patched at offset {offset}" + ); + } + } + + #[test] + fn bytecode_is_patched_with_local_domain() { + let config = test_config(); + let contract = build(&config, 42); + let code = contract.code.to_vec(); + + let word = &code[644..644 + 32]; + // uint32(42) in big-endian, left-padded: ...00 00 00 2a + assert_eq!(word[31], 42); + assert_eq!(word[0..31], [0u8; 31]); + } + + #[test] + fn bytecode_has_zero_deployed_block() { + let config = test_config(); + let contract = build(&config, 1234); + let code = contract.code.to_vec(); + + let word = &code[578..578 + 32]; + assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); + } + + #[test] + fn only_two_storage_slots_for_standard_config() { + let contract = build(&test_config(), 1234); + // Should have exactly 2 storage entries: _initialized (slot 0) and _owner (slot 51) + assert_eq!(contract.storage.len(), 2); + } + + #[test] + #[ignore = "requires forge CLI"] + fn merkle_tree_hook_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "MerkleTreeHook", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(MERKLE_TREE_HOOK_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "MerkleTreeHook bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 8ef01558..c142cc0c 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -2,6 +2,8 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; +pub(crate) mod immutables; +pub(crate) mod merkle_tree_hook; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 9b930af8..08e033ec 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -22,6 +22,12 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref mth_config) = config.contracts.merkle_tree_hook { + let local_domain = config.chain.chain_id as u32; + let contract = contracts::merkle_tree_hook::build(mth_config, local_domain); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -103,6 +109,7 @@ mod tests { owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), fee_vault: None, + merkle_tree_hook: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 42ad6a4a..a5c9d30e 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -109,6 +109,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("fee_vault not configured"))?, + "merkle_tree_hook" => cfg + .contracts + .merkle_tree_hook + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 22bf063c..59ae29af 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -21,5 +21,12 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref mth) = config.contracts.merkle_tree_hook { + manifest.insert( + "merkle_tree_hook".to_string(), + Value::String(format!("{}", mth.address)), + ); + } + Value::Object(manifest) } diff --git a/contracts/lib/hyperlane-monorepo b/contracts/lib/hyperlane-monorepo new file mode 160000 index 00000000..bc401f7a --- /dev/null +++ b/contracts/lib/hyperlane-monorepo @@ -0,0 +1 @@ +Subproject commit bc401f7a64f9e43aa25265dba12d80a33a19de21 From 7e19222c05508a985e555994bcdd9e862f975948 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 10:47:19 +0100 Subject: [PATCH 09/81] ci(ev-deployer): add e2e genesis test to CI workflow --- .github/workflows/ev_deployer.yml | 20 ++++ bin/ev-deployer/src/genesis.rs | 28 +++-- bin/ev-deployer/tests/e2e_genesis.sh | 168 +++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 12 deletions(-) create mode 100755 bin/ev-deployer/tests/e2e_genesis.sh diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml index 329e90ef..b40aacbf 100644 --- a/.github/workflows/ev_deployer.yml +++ b/.github/workflows/ev_deployer.yml @@ -51,3 +51,23 @@ jobs: - name: Run unit tests run: cargo test -p ev-deployer + + e2e-genesis: + name: EV Deployer e2e genesis test + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run e2e genesis test + run: bash bin/ev-deployer/tests/e2e_genesis.sh diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 9b930af8..9b200769 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -77,15 +77,10 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { alloc.insert(addr_key.to_string(), Value::Object(entry)); } -/// Format a storage slot key in the compact form used by existing genesis files. -/// `B256::ZERO` -> "0x0", `B256::with_last_byte(1)` -> "0x1", etc. +/// Format a storage slot key as a full 32-byte hex string. +/// `B256::ZERO` -> "0x0000000000000000000000000000000000000000000000000000000000000000" fn format_slot_key(slot: &B256) -> String { - let u = alloy_primitives::U256::from_be_bytes(slot.0); - if u.is_zero() { - "0x0".to_string() - } else { - format!("0x{u:x}") - } + format!("{slot}") } #[cfg(test)] @@ -137,16 +132,25 @@ mod tests { .unwrap(); assert_eq!( - storage["0x0"], + storage["0x0000000000000000000000000000000000000000000000000000000000000000"], "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" ); } #[test] fn slot_key_formatting() { - assert_eq!(format_slot_key(&B256::ZERO), "0x0"); - assert_eq!(format_slot_key(&B256::with_last_byte(1)), "0x1"); - assert_eq!(format_slot_key(&B256::with_last_byte(6)), "0x6"); + assert_eq!( + format_slot_key(&B256::ZERO), + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + assert_eq!( + format_slot_key(&B256::with_last_byte(1)), + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert_eq!( + format_slot_key(&B256::with_last_byte(6)), + "0x0000000000000000000000000000000000000000000000000000000000000006" + ); } #[test] diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh new file mode 100755 index 00000000..79900e52 --- /dev/null +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# End-to-end test: generate genesis with ev-deployer, boot ev-reth, verify contracts via RPC. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +DEPLOYER="$REPO_ROOT/target/release/ev-deployer" +EV_RETH="$REPO_ROOT/target/release/ev-reth" +CONFIG="$REPO_ROOT/bin/ev-deployer/examples/devnet.toml" +BASE_GENESIS="$REPO_ROOT/bin/ev-dev/assets/devnet-genesis.json" + +RPC_PORT=18545 +RPC_URL="http://127.0.0.1:$RPC_PORT" +NODE_PID="" +TMPDIR_PATH="" + +cleanup() { + if [[ -n "$NODE_PID" ]]; then + kill "$NODE_PID" 2>/dev/null || true + wait "$NODE_PID" 2>/dev/null || true + fi + if [[ -n "$TMPDIR_PATH" ]]; then + rm -rf "$TMPDIR_PATH" + fi +} +trap cleanup EXIT + +# ── Helpers ────────────────────────────────────────────── + +fail() { echo "FAIL: $1" >&2; exit 1; } +pass() { echo "PASS: $1"; } + +rpc_call() { + local method="$1" + local params="$2" + curl -s -X POST "$RPC_URL" \ + -H "Content-Type: application/json" \ + -d "{\"jsonrpc\":\"2.0\",\"method\":\"$method\",\"params\":$params,\"id\":1}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['result'])" +} + +wait_for_rpc() { + local max_attempts=30 + for i in $(seq 1 $max_attempts); do + if curl -s -X POST "$RPC_URL" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + 2>/dev/null | grep -q result; then + return 0 + fi + sleep 1 + done + fail "node did not become ready after ${max_attempts}s" +} + +# ── Step 1: Build ──────────────────────────────────────── + +echo "=== Building ev-deployer and ev-reth ===" +cargo build --release --bin ev-deployer --bin ev-reth --manifest-path "$REPO_ROOT/Cargo.toml" \ + 2>&1 | tail -3 + +[[ -x "$DEPLOYER" ]] || fail "ev-deployer binary not found" +[[ -x "$EV_RETH" ]] || fail "ev-reth binary not found" + +# ── Step 2: Generate genesis ───────────────────────────── + +TMPDIR_PATH="$(mktemp -d)" +GENESIS="$TMPDIR_PATH/genesis.json" +DATADIR="$TMPDIR_PATH/data" + +echo "=== Generating genesis with ev-deployer ===" +"$DEPLOYER" genesis \ + --config "$CONFIG" \ + --merge-into "$BASE_GENESIS" \ + --output "$GENESIS" \ + --force + +echo "Genesis written to $GENESIS" + +# Quick sanity: addresses should be in the alloc +grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ + || fail "AdminProxy address not found in genesis" +grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ + || fail "FeeVault address not found in genesis" + +pass "genesis contains both contract addresses" + +# ── Step 3: Start ev-reth ──────────────────────────────── + +echo "=== Starting ev-reth node ===" +"$EV_RETH" node \ + --dev \ + --chain "$GENESIS" \ + --datadir "$DATADIR" \ + --http \ + --http.addr 127.0.0.1 \ + --http.port "$RPC_PORT" \ + --http.api eth,net,web3 \ + --disable-discovery \ + --no-persist-peers \ + --port 0 \ + --log.stdout.filter error \ + & +NODE_PID=$! + +echo "Node PID: $NODE_PID, waiting for RPC..." +wait_for_rpc +pass "node is up and responding to RPC" + +# ── Step 4: Verify AdminProxy ──────────────────────────── + +ADMIN_PROXY="0x000000000000000000000000000000000000Ad00" +ADMIN_OWNER="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +echo "=== Verifying AdminProxy at $ADMIN_PROXY ===" + +# Check code is present +admin_code=$(rpc_call "eth_getCode" "[\"$ADMIN_PROXY\", \"latest\"]") +[[ "$admin_code" != "0x" && "$admin_code" != "0x0" && ${#admin_code} -gt 10 ]] \ + || fail "AdminProxy has no bytecode (got: $admin_code)" +pass "AdminProxy has bytecode (${#admin_code} hex chars)" + +# Check owner in slot 0 +admin_slot0=$(rpc_call "eth_getStorageAt" "[\"$ADMIN_PROXY\", \"0x0\", \"latest\"]") +# Owner should be in the lower 20 bytes, left-padded to 32 bytes +expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" +[[ "$(echo "$admin_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner_slot" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" +pass "AdminProxy owner slot 0 = $ADMIN_OWNER" + +# ── Step 5: Verify FeeVault ────────────────────────────── + +FEE_VAULT="0x000000000000000000000000000000000000FE00" +FEE_VAULT_OWNER="0x000000000000000000000000000000000000Ad00" + +echo "=== Verifying FeeVault at $FEE_VAULT ===" + +# Check code is present +fv_code=$(rpc_call "eth_getCode" "[\"$FEE_VAULT\", \"latest\"]") +[[ "$fv_code" != "0x" && "$fv_code" != "0x0" && ${#fv_code} -gt 10 ]] \ + || fail "FeeVault has no bytecode (got: $fv_code)" +pass "FeeVault has bytecode (${#fv_code} hex chars)" + +# Slot 0: hypNativeMinter (should be zero) +fv_slot0=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x0\", \"latest\"]") +expected_zero="0x0000000000000000000000000000000000000000000000000000000000000000" +[[ "$(echo "$fv_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_zero" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "FeeVault slot 0 (hypNativeMinter) should be zero, got $fv_slot0" +pass "FeeVault slot 0 (hypNativeMinter) = zero" + +# Slot 1: owner (lower 160 bits) + destinationDomain (upper bits) +# With domain=0 and owner=0x...Ad00, it's just the owner padded +fv_slot1=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x1\", \"latest\"]") +expected_slot1="0x000000000000000000000000000000000000000000000000000000000000ad00" +[[ "$(echo "$fv_slot1" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot1" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "FeeVault slot 1 (owner|domain) mismatch: got $fv_slot1, expected $expected_slot1" +pass "FeeVault slot 1 (owner|domain) correct" + +# Slot 6: bridgeShareBps = 10000 = 0x2710 +fv_slot6=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x6\", \"latest\"]") +expected_slot6="0x0000000000000000000000000000000000000000000000000000000000002710" +[[ "$(echo "$fv_slot6" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot6" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" +pass "FeeVault slot 6 (bridgeShareBps) = 10000" + +# ── Done ───────────────────────────────────────────────── + +echo "" +echo "=== All checks passed ===" From 946026d6933611c34c251dc892651e1a5053327c Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:05:06 +0100 Subject: [PATCH 10/81] ci(ev-deployer): install soldeer deps before bytecode verification The MerkleTreeHook bytecode test needs OpenZeppelin dependencies from the Hyperlane monorepo, which are managed by soldeer. --- .github/workflows/ev_deployer.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml index b40aacbf..9f15084d 100644 --- a/.github/workflows/ev_deployer.yml +++ b/.github/workflows/ev_deployer.yml @@ -34,6 +34,9 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Install Hyperlane soldeer dependencies + run: cd contracts/lib/hyperlane-monorepo/solidity && forge soldeer install + - name: Run bytecode verification tests run: cargo test -p ev-deployer -- --ignored --test-threads=1 From 9dd5011db0898f346efe77fe497b3c4b937f4adb Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:14:09 +0100 Subject: [PATCH 11/81] test(ev-deployer): add MerkleTreeHook verification to e2e genesis test Verify bytecode, storage slots, and patched immutables (mailbox, localDomain, deployedBlock) for the MerkleTreeHook contract via RPC. --- bin/ev-deployer/tests/e2e_genesis.sh | 79 +++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 79900e52..0ef390cd 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -81,8 +81,10 @@ grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" +grep -q "0000000000000000000000000000000000001100" "$GENESIS" \ + || fail "MerkleTreeHook address not found in genesis" -pass "genesis contains both contract addresses" +pass "genesis contains all three contract addresses" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -162,6 +164,81 @@ expected_slot6="0x00000000000000000000000000000000000000000000000000000000000027 || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" pass "FeeVault slot 6 (bridgeShareBps) = 10000" +# ── Step 6: Verify MerkleTreeHook ──────────────────────── + +MERKLE_TREE_HOOK="0x0000000000000000000000000000000000001100" +MERKLE_TREE_HOOK_OWNER="0x000000000000000000000000000000000000Ad00" +MERKLE_TREE_HOOK_MAILBOX="0x0000000000000000000000000000000000001200" + +echo "=== Verifying MerkleTreeHook at $MERKLE_TREE_HOOK ===" + +# Check code is present +mth_code=$(rpc_call "eth_getCode" "[\"$MERKLE_TREE_HOOK\", \"latest\"]") +[[ "$mth_code" != "0x" && "$mth_code" != "0x0" && ${#mth_code} -gt 10 ]] \ + || fail "MerkleTreeHook has no bytecode (got: $mth_code)" +pass "MerkleTreeHook has bytecode (${#mth_code} hex chars)" + +# Compare full bytecode against genesis JSON +# Extract expected code from genesis for the MerkleTreeHook address +expected_mth_code=$(python3 -c " +import json, sys +with open('$GENESIS') as f: + genesis = json.load(f) +alloc = genesis['alloc'] +# Address key is checksummed without 0x prefix +entry = alloc.get('0000000000000000000000000000000000001100') +print(entry['code']) +") +[[ "$(echo "$mth_code" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_mth_code" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "MerkleTreeHook bytecode from node does not match genesis JSON" +pass "MerkleTreeHook bytecode matches genesis JSON" + +# Slot 0: _initialized = 1 (OZ v4 Initializable) +mth_slot0=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x0\", \"latest\"]") +expected_init="0x0000000000000000000000000000000000000000000000000000000000000001" +[[ "$(echo "$mth_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_init" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "MerkleTreeHook slot 0 (_initialized) mismatch: got $mth_slot0, expected $expected_init" +pass "MerkleTreeHook slot 0 (_initialized) = 1" + +# Slot 51 (0x33): _owner +mth_slot51=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x33\", \"latest\"]") +expected_owner="0x000000000000000000000000000000000000000000000000000000000000ad00" +[[ "$(echo "$mth_slot51" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "MerkleTreeHook slot 51 (_owner) mismatch: got $mth_slot51, expected $expected_owner" +pass "MerkleTreeHook slot 51 (_owner) = $MERKLE_TREE_HOOK_OWNER" + +# Verify immutables are patched in bytecode: +# mailbox address at byte offsets 904 and 3300 (each is a 32-byte word, address in last 20 bytes) +# localDomain (chain_id=1234=0x04d2) at byte offset 644 +# The hex string has "0x" prefix, so byte N in the bytecode = hex chars at positions 2+2N..2+2N+2 +mth_hex="${mth_code#0x}" + +check_immutable() { + local name="$1" + local byte_offset="$2" + local expected_hex="$3" + local hex_offset=$((byte_offset * 2)) + local hex_len=${#expected_hex} + local actual="${mth_hex:$hex_offset:$hex_len}" + [[ "$(echo "$actual" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_hex" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "$name at byte offset $byte_offset mismatch: got $actual, expected $expected_hex" + pass "$name patched correctly at byte offset $byte_offset" +} + +# mailbox = 0x...1200 → 32-byte word with address in last 20 bytes +# Full 32-byte word: 000000000000000000000000 + 0000000000000000000000000000000000001200 +mailbox_word="0000000000000000000000000000000000000000000000000000000000001200" +check_immutable "mailbox" 904 "$mailbox_word" +check_immutable "mailbox (second ref)" 3300 "$mailbox_word" + +# localDomain = chain_id 1234 = 0x04d2 → 32-byte word +domain_word="00000000000000000000000000000000000000000000000000000000000004d2" +check_immutable "localDomain" 644 "$domain_word" + +# deployedBlock = 0 → 32 zero bytes +deployed_block_word="0000000000000000000000000000000000000000000000000000000000000000" +check_immutable "deployedBlock" 578 "$deployed_block_word" + # ── Done ───────────────────────────────────────────────── echo "" From 217be0c94fdba740f2b8bbcfe0b0fb4d67ebc5c9 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:22:18 +0100 Subject: [PATCH 12/81] fix(ev-deployer): escape brackets in doc comments to fix rustdoc Rustdoc interprets [644], [578], and [32] as intra-doc links, causing the docs CI job to fail with -D warnings. --- bin/ev-deployer/src/contracts/merkle_tree_hook.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs index ea52cfbd..0723adab 100644 --- a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs +++ b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs @@ -8,9 +8,9 @@ //! //! | Variable | Type | Offsets | //! |---------------|---------|---------------------| -//! | `mailbox` | address | [904, 3300] | -//! | `localDomain` | uint32 | [644] | -//! | `deployedBlock`| uint256| [578] | +//! | `mailbox` | address | \[904, 3300\] | +//! | `localDomain` | uint32 | \[644\] | +//! | `deployedBlock`| uint256| \[578\] | //! //! ## Storage layout (from `forge inspect MerkleTreeHook storageLayout`) //! @@ -23,7 +23,7 @@ //! | 101 | `hook` | address | //! | 102 | `_interchainSecurityModule` | address | //! | 103-150| `__GAP` (MailboxClient) | — | -//! | 151-182| `_tree.branch[0..31]` | bytes32[32] | +//! | 151-182| `_tree.branch\[0..31\]` | bytes32\[32\] | //! | 183 | `_tree.count` | uint256 | use crate::{ From 67151c3a306a6642ec1a7456a6de622e9273664d Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:43:29 +0100 Subject: [PATCH 13/81] feat(ev-deployer): add Permit2 contract support Add Uniswap Permit2 as a genesis-deployable contract with EIP-712 immutable patching (_CACHED_CHAIN_ID, _CACHED_DOMAIN_SEPARATOR). --- .gitmodules | 3 + bin/ev-deployer/examples/devnet.toml | 3 + bin/ev-deployer/src/config.rs | 9 + bin/ev-deployer/src/contracts/mod.rs | 1 + bin/ev-deployer/src/contracts/permit2.rs | 223 +++++++++++++++++++++++ bin/ev-deployer/src/genesis.rs | 6 + bin/ev-deployer/src/main.rs | 6 + bin/ev-deployer/src/output.rs | 7 + bin/ev-deployer/tests/e2e_genesis.sh | 24 ++- contracts/lib/permit2 | 1 + 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 bin/ev-deployer/src/contracts/permit2.rs create mode 160000 contracts/lib/permit2 diff --git a/.gitmodules b/.gitmodules index 735b8dc8..0cebd2a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "contracts/lib/hyperlane-monorepo"] path = contracts/lib/hyperlane-monorepo url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git +[submodule "contracts/lib/permit2"] + path = contracts/lib/permit2 + url = https://github.com/Uniswap/permit2 diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 87bafe09..0cf8d453 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -20,3 +20,6 @@ hyp_native_minter = "0x0000000000000000000000000000000000000000" address = "0x0000000000000000000000000000000000001100" owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" + +[contracts.permit2] +address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bed1e625..c3f674b1 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -31,6 +31,8 @@ pub(crate) struct ContractsConfig { pub fee_vault: Option, /// `MerkleTreeHook` contract config (optional). pub merkle_tree_hook: Option, + /// `Permit2` contract config (optional). + pub permit2: Option, } /// `AdminProxy` configuration. @@ -84,6 +86,13 @@ pub(crate) struct MerkleTreeHookConfig { pub mailbox: Address, } +/// `Permit2` configuration (Uniswap token approval manager). +#[derive(Debug, Deserialize)] +pub(crate) struct Permit2Config { + /// Address to deploy at. + pub address: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c142cc0c..43bfaf5f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; pub(crate) mod immutables; pub(crate) mod merkle_tree_hook; +pub(crate) mod permit2; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs new file mode 100644 index 00000000..fd62f2ea --- /dev/null +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -0,0 +1,223 @@ +//! `Permit2` bytecode and immutable encoding. +//! +//! Uniswap's `Permit2` provides gas-efficient token approval management +//! via signature-based permits (EIP-2612 style) for any ERC-20 token. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offset | +//! |-----------------------------|---------|--------| +//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! +//! Both come from the EIP-712 base contract (`src/EIP712.sol`). The +//! constructor normally caches `block.chainid` and the resulting domain +//! separator so that `DOMAIN_SEPARATOR()` can skip recomputation when the +//! chain ID hasn't changed. For a genesis deploy we patch the correct +//! values directly into the bytecode. +//! +//! ## Storage layout +//! +//! Permit2 has no storage that needs initialization at genesis. All state +//! (allowances, nonces, …) starts at zero. + +use crate::{ + config::Permit2Config, + contracts::{ + immutables::{patch_bytes, patch_u256, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) +/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode +/// ``` +const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); + +// ── Immutable reference offsets (from compiled artifact `immutableReferences`) ── + +/// `_CACHED_CHAIN_ID` (uint256) — from `EIP712.sol`. +const CACHED_CHAIN_ID_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6945, + length: 32, +}]; + +/// `_CACHED_DOMAIN_SEPARATOR` (bytes32) — from `EIP712.sol`. +const CACHED_DOMAIN_SEPARATOR_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6983, + length: 32, +}]; + +/// EIP-712 type hash: `keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)")` +const EIP712_TYPE_HASH: B256 = B256::new(hex!( + "8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866" +)); + +/// `keccak256("Permit2")` +const HASHED_NAME: B256 = B256::new(hex!( + "9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a" +)); + +/// Build a genesis alloc entry for `Permit2`. +pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { + let mut bytecode = PERMIT2_BYTECODE.to_vec(); + + // Patch _CACHED_CHAIN_ID + let chain_id_u256 = U256::from(chain_id); + patch_u256(&mut bytecode, CACHED_CHAIN_ID_REFS, chain_id_u256); + + // Compute and patch _CACHED_DOMAIN_SEPARATOR: + // keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, chainId, contractAddress)) + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let domain_separator = keccak256(buf); + patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> Permit2Config { + Permit2Config { + address: address!("000000000022D473030F116dDEE9F6B43aC78BA3"), + } + } + + #[test] + fn no_storage_entries() { + let contract = build(&test_config(), 1234); + assert!( + contract.storage.is_empty(), + "Permit2 should have no storage at genesis" + ); + } + + #[test] + fn bytecode_is_patched_with_chain_id() { + let contract = build(&test_config(), 42); + let code = contract.code.to_vec(); + let word = &code[6945..6945 + 32]; + assert_eq!(word[31], 42); + assert_eq!(word[0..31], [0u8; 31]); + } + + #[test] + fn bytecode_is_patched_with_domain_separator() { + let config = test_config(); + let chain_id: u64 = 1234; + let contract = build(&config, chain_id); + let code = contract.code.to_vec(); + + // Compute expected domain separator + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(U256::from(chain_id)).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let expected = keccak256(buf); + + let word = &code[6983..6983 + 32]; + assert_eq!(word, expected.as_slice()); + } + + #[test] + fn domain_separator_changes_with_chain_id() { + let config = test_config(); + let c1 = build(&config, 1); + let c2 = build(&config, 2); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + } + + #[test] + fn domain_separator_changes_with_address() { + let c1 = build( + &Permit2Config { + address: Address::repeat_byte(0x01), + }, + 1234, + ); + let c2 = build( + &Permit2Config { + address: Address::repeat_byte(0x02), + }, + 1234, + ); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + } + + #[test] + fn eip712_constants_are_correct() { + // Verify the hardcoded constants match the expected values + assert_eq!( + EIP712_TYPE_HASH, + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), + ); + assert_eq!(HASHED_NAME, keccak256("Permit2")); + } + + #[test] + #[ignore = "requires forge CLI"] + fn permit2_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("permit2"); + + let output = Command::new("forge") + .args(["inspect", "Permit2", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(PERMIT2_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "Permit2 bytecode mismatch! Regenerate with: \ + cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fee43554..ff5b4ec4 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -28,6 +28,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref p2_config) = config.contracts.permit2 { + let contract = contracts::permit2::build(p2_config, config.chain.chain_id); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -105,6 +110,7 @@ mod tests { }), fee_vault: None, merkle_tree_hook: None, + permit2: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5c9d30e..aab6e6e4 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -115,6 +115,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, + "permit2" => cfg + .contracts + .permit2 + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 59ae29af..10e41672 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -28,5 +28,12 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref p2) = config.contracts.permit2 { + manifest.insert( + "permit2".to_string(), + Value::String(format!("{}", p2.address)), + ); + } + Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 79900e52..2d47481e 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -81,8 +81,10 @@ grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" +grep -q "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ + || fail "Permit2 address not found in genesis" -pass "genesis contains both contract addresses" +pass "genesis contains all contract addresses" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -162,6 +164,26 @@ expected_slot6="0x00000000000000000000000000000000000000000000000000000000000027 || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" pass "FeeVault slot 6 (bridgeShareBps) = 10000" +# ── Step 6: Verify Permit2 ───────────────────────────── + +PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3" + +echo "=== Verifying Permit2 at $PERMIT2 ===" + +# Check code is present +p2_code=$(rpc_call "eth_getCode" "[\"$PERMIT2\", \"latest\"]") +[[ "$p2_code" != "0x" && "$p2_code" != "0x0" && ${#p2_code} -gt 10 ]] \ + || fail "Permit2 has no bytecode (got: $p2_code)" +pass "Permit2 has bytecode (${#p2_code} hex chars)" + +# Call DOMAIN_SEPARATOR() — selector 0x3644e515 +# Should return the cached domain separator matching chain_id=1234 and the contract address +p2_domain_sep=$(rpc_call "eth_call" "[{\"to\":\"$PERMIT2\",\"data\":\"0x3644e515\"}, \"latest\"]") +expected_domain_sep="0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95" +[[ "$(echo "$p2_domain_sep" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_domain_sep" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "Permit2 DOMAIN_SEPARATOR() mismatch: got $p2_domain_sep, expected $expected_domain_sep" +pass "Permit2 DOMAIN_SEPARATOR() correct for chain_id=1234" + # ── Done ───────────────────────────────────────────────── echo "" diff --git a/contracts/lib/permit2 b/contracts/lib/permit2 new file mode 160000 index 00000000..cc56ad0f --- /dev/null +++ b/contracts/lib/permit2 @@ -0,0 +1 @@ +Subproject commit cc56ad0f3439c502c246fc5cfcc3db92bb8b7219 From 56548ec8819f5ba33ec716dd75410f109c0aa407 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:44:28 +0100 Subject: [PATCH 14/81] docs(ev-deployer): add comment explaining canonical Permit2 address --- bin/ev-deployer/examples/devnet.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 0cf8d453..1c56e361 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -22,4 +22,6 @@ owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" [contracts.permit2] +# Canonical Uniswap Permit2 address (same on all chains via CREATE2). +# Using it here so frontends, SDKs and routers work out of the box. address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" From bfa5a23bd2889dbc21342fd0b1c32454b5f105b7 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:02:05 +0100 Subject: [PATCH 15/81] style(ev-deployer): fix fmt, clippy and rustdoc warnings in permit2 --- bin/ev-deployer/src/contracts/permit2.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index fd62f2ea..ee56f85b 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -7,8 +7,8 @@ //! //! | Variable | Type | Offset | //! |-----------------------------|---------|--------| -//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | -//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! | `_CACHED_CHAIN_ID` | uint256 | \[6945\] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | \[6983\] | //! //! Both come from the EIP-712 base contract (`src/EIP712.sol`). The //! constructor normally caches `block.chainid` and the resulting domain @@ -32,7 +32,7 @@ use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; use std::collections::BTreeMap; /// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) -/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// with solc 0.8.17 (via-ir, optimizer `1_000_000` runs, `bytecode_hash="none"`). /// /// Compiled with placeholder immutables (all zeros). Actual values are patched /// at genesis time via [`build`]. @@ -83,7 +83,11 @@ pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); buf[96..128].copy_from_slice(config.address.into_word().as_slice()); let domain_separator = keccak256(buf); - patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + patch_bytes( + &mut bytecode, + CACHED_DOMAIN_SEPARATOR_REFS, + &domain_separator.0, + ); GenesisContract { address: config.address, @@ -149,7 +153,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different chain IDs should produce different domain separators" + ); } #[test] @@ -169,7 +176,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different addresses should produce different domain separators" + ); } #[test] From 4da01ea7c5f08ef8af9493ed1a1e9a70ea0d391e Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:37:14 +0100 Subject: [PATCH 16/81] feat(ev-deployer): add Mailbox, NoopIsm, and ProtocolFee genesis contracts Add three Hyperlane core contracts to ev-deployer so the full messaging stack can be embedded at genesis without post-deploy transactions. - Mailbox: core messaging hub with localDomain/deployedBlock immutables - NoopIsm: stateless ISM that accepts all messages (for devnet) - ProtocolFee: post-dispatch hook with MAX_PROTOCOL_FEE immutable --- bin/ev-deployer/examples/devnet.toml | 17 + bin/ev-deployer/src/config.rs | 61 ++++ bin/ev-deployer/src/contracts/mailbox.rs | 295 ++++++++++++++++++ bin/ev-deployer/src/contracts/mod.rs | 3 + bin/ev-deployer/src/contracts/noop_ism.rs | 109 +++++++ bin/ev-deployer/src/contracts/protocol_fee.rs | 209 +++++++++++++ bin/ev-deployer/src/genesis.rs | 19 ++ bin/ev-deployer/src/main.rs | 18 ++ bin/ev-deployer/src/output.rs | 21 ++ 9 files changed, 752 insertions(+) create mode 100644 bin/ev-deployer/src/contracts/mailbox.rs create mode 100644 bin/ev-deployer/src/contracts/noop_ism.rs create mode 100644 bin/ev-deployer/src/contracts/protocol_fee.rs diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 87bafe09..91f033ca 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -16,7 +16,24 @@ bridge_share_bps = 10000 other_recipient = "0x0000000000000000000000000000000000000000" hyp_native_minter = "0x0000000000000000000000000000000000000000" +[contracts.mailbox] +address = "0x0000000000000000000000000000000000001200" +owner = "0x000000000000000000000000000000000000Ad00" +default_ism = "0x0000000000000000000000000000000000001300" +default_hook = "0x0000000000000000000000000000000000001400" +required_hook = "0x0000000000000000000000000000000000001100" + [contracts.merkle_tree_hook] address = "0x0000000000000000000000000000000000001100" owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" + +[contracts.noop_ism] +address = "0x0000000000000000000000000000000000001300" + +[contracts.protocol_fee] +address = "0x0000000000000000000000000000000000001400" +owner = "0x000000000000000000000000000000000000Ad00" +max_protocol_fee = 1000000000000000000 +protocol_fee = 0 +beneficiary = "0x000000000000000000000000000000000000Ad00" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bed1e625..b6f606cd 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -31,6 +31,12 @@ pub(crate) struct ContractsConfig { pub fee_vault: Option, /// `MerkleTreeHook` contract config (optional). pub merkle_tree_hook: Option, + /// `Mailbox` contract config (optional). + pub mailbox: Option, + /// `NoopIsm` contract config (optional). + pub noop_ism: Option, + /// `ProtocolFee` contract config (optional). + pub protocol_fee: Option, } /// `AdminProxy` configuration. @@ -84,6 +90,50 @@ pub(crate) struct MerkleTreeHookConfig { pub mailbox: Address, } +/// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee). +#[derive(Debug, Deserialize)] +pub(crate) struct ProtocolFeeConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + #[serde(default)] + pub owner: Address, + /// Maximum protocol fee in wei. + pub max_protocol_fee: u64, + /// Protocol fee charged per dispatch in wei. + #[serde(default)] + pub protocol_fee: u64, + /// Beneficiary address that receives collected fees. + #[serde(default)] + pub beneficiary: Address, +} + +/// `Mailbox` configuration (Hyperlane core messaging hub). +#[derive(Debug, Deserialize)] +pub(crate) struct MailboxConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + #[serde(default)] + pub owner: Address, + /// Default interchain security module. + #[serde(default)] + pub default_ism: Address, + /// Default post-dispatch hook. + #[serde(default)] + pub default_hook: Address, + /// Required post-dispatch hook (e.g. `MerkleTreeHook`). + #[serde(default)] + pub required_hook: Address, +} + +/// `NoopIsm` configuration (Hyperlane ISM that accepts all messages). +#[derive(Debug, Deserialize)] +pub(crate) struct NoopIsmConfig { + /// Address to deploy at. + pub address: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -121,6 +171,17 @@ impl DeployConfig { ); } + if let Some(ref pf) = self.contracts.protocol_fee { + eyre::ensure!( + !pf.owner.is_zero(), + "protocol_fee.owner must not be the zero address" + ); + eyre::ensure!( + !pf.beneficiary.is_zero(), + "protocol_fee.beneficiary must not be the zero address" + ); + } + Ok(()) } } diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs new file mode 100644 index 00000000..64cdf3d4 --- /dev/null +++ b/bin/ev-deployer/src/contracts/mailbox.rs @@ -0,0 +1,295 @@ +//! `Mailbox` bytecode and storage encoding. +//! +//! `Mailbox` is the core Hyperlane messaging hub. It dispatches and processes +//! cross-chain messages. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offsets | +//! |-----------------|---------|----------------------| +//! | `deployedBlock` | uint256 | \[930\] | +//! | `localDomain` | uint32 | \[982, 2831, 5985\] | +//! +//! ## Storage layout (from `forge inspect Mailbox storageLayout`) +//! +//! | Slot | Variable | Type | +//! |-------|-----------------------------|-----------| +//! | 0 | `_initialized` + `_initializing` | uint8 + bool | +//! | 1-50 | `__gap` (Initializable) | — | +//! | 51 | `_owner` | address | +//! | 52-100| `__gap` (Ownable) | — | +//! | 101 | `nonce` | uint32 | +//! | 102 | `latestDispatchedId` | bytes32 | +//! | 103 | `defaultIsm` | address | +//! | 104 | `defaultHook` | address | +//! | 105 | `requiredHook` | address | +//! | 106 | `deliveries` (mapping) | — | + +use crate::{ + config::MailboxConfig, + contracts::{ + immutables::{patch_u256, patch_u32, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `Mailbox` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode +/// ``` +const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); + +// ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── + +/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. +const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 930, + length: 32, +}]; + +/// `localDomain` (uint32) — from `Mailbox.sol`. +const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ + ImmutableRef { + start: 982, + length: 32, + }, + ImmutableRef { + start: 2831, + length: 32, + }, + ImmutableRef { + start: 5985, + length: 32, + }, +]; + +/// Build a genesis alloc entry for `Mailbox`. +pub(crate) fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { + let mut bytecode = MAILBOX_BYTECODE.to_vec(); + + // Patch immutables + patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); + patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); + + let mut storage = BTreeMap::new(); + + // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false + storage.insert(B256::ZERO, B256::from(U256::from(1u8))); + + // Slot 51: _owner + if !config.owner.is_zero() { + storage.insert( + B256::from(U256::from(51u64)), + B256::from(U256::from_be_bytes(config.owner.into_word().0)), + ); + } + + // Slot 103: defaultIsm + if !config.default_ism.is_zero() { + storage.insert( + B256::from(U256::from(103u64)), + B256::from(U256::from_be_bytes(config.default_ism.into_word().0)), + ); + } + + // Slot 104: defaultHook + if !config.default_hook.is_zero() { + storage.insert( + B256::from(U256::from(104u64)), + B256::from(U256::from_be_bytes(config.default_hook.into_word().0)), + ); + } + + // Slot 105: requiredHook + if !config.required_hook.is_zero() { + storage.insert( + B256::from(U256::from(105u64)), + B256::from(U256::from_be_bytes(config.required_hook.into_word().0)), + ); + } + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> MailboxConfig { + MailboxConfig { + address: address!("0000000000000000000000000000000000001200"), + owner: address!("000000000000000000000000000000000000ad00"), + default_ism: address!("0000000000000000000000000000000000002000"), + default_hook: address!("0000000000000000000000000000000000003000"), + required_hook: address!("0000000000000000000000000000000000004000"), + } + } + + #[test] + fn storage_has_initialized_flag() { + let contract = build(&test_config(), 1234); + assert_eq!( + contract.storage[&B256::ZERO], + B256::from(U256::from(1u8)), + "_initialized should be 1" + ); + } + + #[test] + fn storage_has_owner() { + let contract = build(&test_config(), 1234); + let owner_slot = B256::from(U256::from(51u64)); + let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&owner_slot], expected); + } + + #[test] + fn storage_has_default_ism() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(103u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000002000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn storage_has_default_hook() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(104u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000003000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn storage_has_required_hook() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(105u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000004000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn bytecode_is_patched_with_local_domain() { + let config = test_config(); + let contract = build(&config, 42); + let code = contract.code.to_vec(); + + for &offset in &[982, 2831, 5985] { + let word = &code[offset..offset + 32]; + assert_eq!(word[31], 42, "localDomain not patched at offset {offset}"); + assert_eq!( + word[0..31], + [0u8; 31], + "localDomain padding wrong at offset {offset}" + ); + } + } + + #[test] + fn bytecode_has_zero_deployed_block() { + let config = test_config(); + let contract = build(&config, 1234); + let code = contract.code.to_vec(); + + let word = &code[930..930 + 32]; + assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); + } + + #[test] + fn storage_count_for_standard_config() { + let contract = build(&test_config(), 1234); + // _initialized (0) + _owner (51) + defaultIsm (103) + defaultHook (104) + requiredHook (105) + assert_eq!(contract.storage.len(), 5); + } + + #[test] + fn zero_addresses_omit_slots() { + let config = MailboxConfig { + address: address!("0000000000000000000000000000000000001200"), + owner: Address::ZERO, + default_ism: Address::ZERO, + default_hook: Address::ZERO, + required_hook: Address::ZERO, + }; + let contract = build(&config, 1234); + // Only _initialized (slot 0) should be present + assert_eq!(contract.storage.len(), 1); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(51u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(103u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(104u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(105u64)))); + } + + #[test] + #[ignore = "requires forge CLI"] + fn mailbox_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "Mailbox", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(MAILBOX_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "Mailbox bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c142cc0c..d2c6500f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -3,7 +3,10 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; pub(crate) mod immutables; +pub(crate) mod mailbox; pub(crate) mod merkle_tree_hook; +pub(crate) mod noop_ism; +pub(crate) mod protocol_fee; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/noop_ism.rs b/bin/ev-deployer/src/contracts/noop_ism.rs new file mode 100644 index 00000000..9f17efe0 --- /dev/null +++ b/bin/ev-deployer/src/contracts/noop_ism.rs @@ -0,0 +1,109 @@ +//! `NoopIsm` bytecode encoding. +//! +//! `NoopIsm` is a Hyperlane Interchain Security Module (ISM) that accepts all +//! messages without verification — `verify` always returns `true`. +//! +//! ## Immutables +//! +//! None. +//! +//! ## Storage layout +//! +//! None. + +use crate::{config::NoopIsmConfig, contracts::GenesisContract}; +use alloy_primitives::{hex, Bytes}; +use std::collections::BTreeMap; + +/// `NoopIsm` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode +/// ``` +const NOOP_ISM_BYTECODE: &[u8] = &hex!("608060405234801561001057600080fd5b50600436106100415760003560e01c80636465e69f1461004657806393c4484714610065578063f7e83aee146100ae575b600080fd5b61004e600681565b60405160ff90911681526020015b60405180910390f35b6100a16040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161005c91906100d6565b6100c66100bc36600461018c565b6001949350505050565b604051901515815260200161005c565b60006020808352835180602085015260005b81811015610104578581018301518582016040015282016100e8565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008083601f84011261015557600080fd5b50813567ffffffffffffffff81111561016d57600080fd5b60208301915083602082850101111561018557600080fd5b9250929050565b600080600080604085870312156101a257600080fd5b843567ffffffffffffffff808211156101ba57600080fd5b6101c688838901610143565b909650945060208701359150808211156101df57600080fd5b506101ec87828801610143565b9598949750955050505056"); + +/// Build a genesis alloc entry for `NoopIsm`. +pub(crate) fn build(config: &NoopIsmConfig) -> GenesisContract { + GenesisContract { + address: config.address, + code: Bytes::from(NOOP_ISM_BYTECODE.to_vec()), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> NoopIsmConfig { + NoopIsmConfig { + address: address!("0000000000000000000000000000000000001300"), + } + } + + #[test] + fn storage_is_empty() { + let contract = build(&test_config()); + assert!( + contract.storage.is_empty(), + "NoopIsm should have no storage" + ); + } + + #[test] + fn bytecode_is_present() { + let contract = build(&test_config()); + assert!( + !contract.code.is_empty(), + "NoopIsm should have non-empty bytecode" + ); + } + + #[test] + #[ignore = "requires forge CLI"] + fn noop_ism_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "NoopIsm", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(NOOP_ISM_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "NoopIsm bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs new file mode 100644 index 00000000..9dea5d59 --- /dev/null +++ b/bin/ev-deployer/src/contracts/protocol_fee.rs @@ -0,0 +1,209 @@ +//! `ProtocolFee` bytecode and storage encoding. +//! +//! `ProtocolFee` is a Hyperlane post-dispatch hook that charges a protocol fee +//! on message dispatches. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offsets | +//! |-------------------|---------|-----------------| +//! | `MAX_PROTOCOL_FEE`| uint256 | \[655, 2248\] | +//! +//! ## Storage layout (from `forge inspect ProtocolFee storageLayout`) +//! +//! | Slot | Variable | Type | +//! |------|---------------|---------| +//! | 0 | `_owner` | address | +//! | 1 | `protocolFee` | uint256 | +//! | 2 | `beneficiary` | address | + +use crate::{ + config::ProtocolFeeConfig, + contracts::{ + immutables::{patch_u256, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `ProtocolFee` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode +/// ``` +const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401516103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); + +// ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── + +/// `MAX_PROTOCOL_FEE` (uint256) — maximum fee that can be set. +const MAX_PROTOCOL_FEE_REFS: &[ImmutableRef] = &[ + ImmutableRef { + start: 655, + length: 32, + }, + ImmutableRef { + start: 2248, + length: 32, + }, +]; + +/// Build a genesis alloc entry for `ProtocolFee`. +pub(crate) fn build(config: &ProtocolFeeConfig) -> GenesisContract { + let mut bytecode = PROTOCOL_FEE_BYTECODE.to_vec(); + + // Patch immutables + patch_u256( + &mut bytecode, + MAX_PROTOCOL_FEE_REFS, + U256::from(config.max_protocol_fee), + ); + + let mut storage = BTreeMap::new(); + + // Slot 0: _owner + if !config.owner.is_zero() { + storage.insert( + B256::ZERO, + B256::from(U256::from_be_bytes(config.owner.into_word().0)), + ); + } + + // Slot 1: protocolFee + if config.protocol_fee > 0 { + storage.insert( + B256::from(U256::from(1u64)), + B256::from(U256::from(config.protocol_fee)), + ); + } + + // Slot 2: beneficiary + if !config.beneficiary.is_zero() { + storage.insert( + B256::from(U256::from(2u64)), + B256::from(U256::from_be_bytes(config.beneficiary.into_word().0)), + ); + } + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> ProtocolFeeConfig { + ProtocolFeeConfig { + address: address!("0000000000000000000000000000000000001300"), + owner: address!("000000000000000000000000000000000000ad00"), + max_protocol_fee: 1_000_000_000_000_000_000, + protocol_fee: 100_000, + beneficiary: address!("000000000000000000000000000000000000be00"), + } + } + + #[test] + fn storage_has_owner() { + let contract = build(&test_config()); + let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&B256::ZERO], expected); + } + + #[test] + fn bytecode_is_patched_with_max_protocol_fee() { + let config = test_config(); + let contract = build(&config); + let code = contract.code.to_vec(); + + let expected = B256::from(U256::from(config.max_protocol_fee)); + for &offset in &[655, 2248] { + let word = &code[offset..offset + 32]; + assert_eq!( + word, + expected.as_slice(), + "max_protocol_fee not patched at offset {offset}" + ); + } + } + + #[test] + fn zero_protocol_fee_omits_slot_1() { + let config = ProtocolFeeConfig { + address: address!("0000000000000000000000000000000000001300"), + owner: address!("000000000000000000000000000000000000ad00"), + max_protocol_fee: 1_000_000_000_000_000_000, + protocol_fee: 0, + beneficiary: address!("000000000000000000000000000000000000be00"), + }; + let contract = build(&config); + let fee_slot = B256::from(U256::from(1u64)); + assert!( + !contract.storage.contains_key(&fee_slot), + "zero protocol_fee should not produce a storage entry" + ); + } + + #[test] + fn storage_count_for_standard_config() { + let contract = build(&test_config()); + // Should have exactly 3 storage entries: _owner (slot 0), protocolFee (slot 1), beneficiary (slot 2) + assert_eq!(contract.storage.len(), 3); + } + + #[test] + #[ignore = "requires forge CLI"] + fn protocol_fee_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "ProtocolFee", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(PROTOCOL_FEE_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "ProtocolFee bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fee43554..eb3b7283 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -28,6 +28,22 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref mb_config) = config.contracts.mailbox { + let local_domain = config.chain.chain_id as u32; + let contract = contracts::mailbox::build(mb_config, local_domain); + insert_contract(&mut alloc, &contract); + } + + if let Some(ref ni_config) = config.contracts.noop_ism { + let contract = contracts::noop_ism::build(ni_config); + insert_contract(&mut alloc, &contract); + } + + if let Some(ref pf_config) = config.contracts.protocol_fee { + let contract = contracts::protocol_fee::build(pf_config); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -105,6 +121,9 @@ mod tests { }), fee_vault: None, merkle_tree_hook: None, + mailbox: None, + noop_ism: None, + protocol_fee: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5c9d30e..2fb6f901 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -115,6 +115,24 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, + "mailbox" => cfg + .contracts + .mailbox + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("mailbox not configured"))?, + "noop_ism" => cfg + .contracts + .noop_ism + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("noop_ism not configured"))?, + "protocol_fee" => cfg + .contracts + .protocol_fee + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("protocol_fee not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 59ae29af..56e62608 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -28,5 +28,26 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref mb) = config.contracts.mailbox { + manifest.insert( + "mailbox".to_string(), + Value::String(format!("{}", mb.address)), + ); + } + + if let Some(ref ni) = config.contracts.noop_ism { + manifest.insert( + "noop_ism".to_string(), + Value::String(format!("{}", ni.address)), + ); + } + + if let Some(ref pf) = config.contracts.protocol_fee { + manifest.insert( + "protocol_fee".to_string(), + Value::String(format!("{}", pf.address)), + ); + } + Value::Object(manifest) } From 3faa62955aaf21e7d9effb7bcaecb4f82670c9e1 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:42:35 +0100 Subject: [PATCH 17/81] fix(ev-deployer): regenerate Mailbox and ProtocolFee bytecodes from clean ci build The embedded bytecodes were compiled with --extra-output storageLayout which subtly altered the output. Regenerated from a clean ci profile build to match what forge produces without extra flags. --- bin/ev-deployer/src/contracts/mailbox.rs | 2 +- bin/ev-deployer/src/contracts/protocol_fee.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs index 64cdf3d4..4f774587 100644 --- a/bin/ev-deployer/src/contracts/mailbox.rs +++ b/bin/ev-deployer/src/contracts/mailbox.rs @@ -47,7 +47,7 @@ use std::collections::BTreeMap; /// forge soldeer install && \ /// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode /// ``` -const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); +const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016109cd565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); // ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs index 9dea5d59..d7b2135c 100644 --- a/bin/ev-deployer/src/contracts/protocol_fee.rs +++ b/bin/ev-deployer/src/contracts/protocol_fee.rs @@ -39,7 +39,7 @@ use std::collections::BTreeMap; /// forge soldeer install && \ /// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode /// ``` -const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401516103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); +const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); // ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── From aeffc0da683867cf74dd9b387d78077593206910 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 19 Mar 2026 15:22:17 +0100 Subject: [PATCH 18/81] fix(ev-deployer): address PR review feedback - Expand CI workflow path triggers to include Cargo.toml, Cargo.lock, and the workflow file itself - Fix README merge behavior description (not in-place) - Validate contract addresses are unique in config - Use trim_start_matches("0x") instead of strip_prefix().unwrap() in bytecode verification tests - Add curl timeouts to e2e test RPC calls to prevent CI hangs --- .github/workflows/ev_deployer.yml | 6 +++++ bin/ev-deployer/README.md | 2 +- bin/ev-deployer/src/config.rs | 25 ++++++++++++++++++++ bin/ev-deployer/src/contracts/admin_proxy.rs | 3 +-- bin/ev-deployer/src/contracts/fee_vault.rs | 3 +-- bin/ev-deployer/tests/e2e_genesis.sh | 4 ++-- 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ev_deployer.yml b/.github/workflows/ev_deployer.yml index b40aacbf..ef57c12a 100644 --- a/.github/workflows/ev_deployer.yml +++ b/.github/workflows/ev_deployer.yml @@ -3,11 +3,17 @@ name: EV Deployer CI on: push: paths: + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/ev_deployer.yml' - 'contracts/src/**' - 'contracts/foundry.toml' - 'bin/ev-deployer/**' pull_request: paths: + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/ev_deployer.yml' - 'contracts/src/**' - 'contracts/foundry.toml' - 'bin/ev-deployer/**' diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index b6bd48cc..58af3a3e 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -83,7 +83,7 @@ ev-deployer genesis --config deploy.toml --output alloc.json ### Merge into an existing genesis file -Insert the generated entries into an existing `genesis.json`. This modifies the `alloc` field in-place and writes the full result: +Insert the generated entries into an existing `genesis.json`. The merged result is written to `--output` (or stdout if `--output` is omitted): ```bash ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 6bb60482..66fb1c24 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -100,6 +100,13 @@ impl DeployConfig { ); } + if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) { + eyre::ensure!( + ap.address != fv.address, + "contracts.admin_proxy.address and contracts.fee_vault.address must be distinct" + ); + } + Ok(()) } } @@ -165,6 +172,24 @@ bridge_share_bps = 10001 assert!(config.validate().is_err()); } + #[test] + fn reject_duplicate_addresses() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.fee_vault] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + #[test] fn admin_proxy_only() { let toml = r#" diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index 51d5ce59..ed187b12 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -68,8 +68,7 @@ mod tests { let forge_hex = String::from_utf8(output.stdout) .unwrap() .trim() - .strip_prefix("0x") - .unwrap() + .trim_start_matches("0x") .to_lowercase(); let hardcoded_hex = hex::encode(ADMIN_PROXY_BYTECODE); diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs index c99bd39b..445ea8c1 100644 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -171,8 +171,7 @@ mod tests { let forge_hex = String::from_utf8(output.stdout) .unwrap() .trim() - .strip_prefix("0x") - .unwrap() + .trim_start_matches("0x") .to_lowercase(); let hardcoded_hex = hex::encode(FEE_VAULT_BYTECODE); diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 79900e52..3b7783b4 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -32,7 +32,7 @@ pass() { echo "PASS: $1"; } rpc_call() { local method="$1" local params="$2" - curl -s -X POST "$RPC_URL" \ + curl -s --connect-timeout 5 --max-time 10 -X POST "$RPC_URL" \ -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"$method\",\"params\":$params,\"id\":1}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['result'])" @@ -41,7 +41,7 @@ rpc_call() { wait_for_rpc() { local max_attempts=30 for i in $(seq 1 $max_attempts); do - if curl -s -X POST "$RPC_URL" \ + if curl -s --connect-timeout 1 --max-time 2 -X POST "$RPC_URL" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ 2>/dev/null | grep -q result; then From 1ecc93c64d2e5dbbaca2123f6faca8325402da28 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 19 Mar 2026 21:20:55 +0100 Subject: [PATCH 19/81] feat: integrate ev-deployer into ev-dev for genesis contract deployment - Add ev-deployer dependency to ev-dev - Accept optional --deploy-config argument in ev-dev CLI - Load deploy config and override chain ID if needed - Display deployed contract addresses in dev chain banner - Add just recipes for installing ev-dev and ev-deployer binaries --- Cargo.lock | 1 + bin/ev-deployer/src/config.rs | 22 ++++---- bin/ev-deployer/src/contracts/admin_proxy.rs | 2 +- bin/ev-deployer/src/contracts/fee_vault.rs | 2 +- bin/ev-deployer/src/contracts/immutables.rs | 10 ++-- bin/ev-deployer/src/contracts/mailbox.rs | 2 +- .../src/contracts/merkle_tree_hook.rs | 2 +- bin/ev-deployer/src/contracts/mod.rs | 19 +++---- bin/ev-deployer/src/contracts/noop_ism.rs | 2 +- bin/ev-deployer/src/contracts/permit2.rs | 2 +- bin/ev-deployer/src/contracts/protocol_fee.rs | 2 +- bin/ev-deployer/src/genesis.rs | 15 +++--- bin/ev-deployer/src/lib.rs | 9 ++++ bin/ev-deployer/src/main.rs | 6 +-- bin/ev-deployer/src/output.rs | 2 +- bin/ev-dev/Cargo.toml | 1 + bin/ev-dev/src/main.rs | 51 +++++++++++++++++-- justfile | 8 +++ 18 files changed, 109 insertions(+), 49 deletions(-) create mode 100644 bin/ev-deployer/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3e41035d..c5d21994 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2937,6 +2937,7 @@ dependencies = [ "alloy-primitives", "alloy-signer-local", "clap", + "ev-deployer", "ev-node", "evolve-ev-reth", "eyre", diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 6140f90f..853de34a 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -7,7 +7,7 @@ use std::path::Path; /// Top-level deploy configuration. #[derive(Debug, Deserialize)] #[allow(dead_code)] -pub(crate) struct DeployConfig { +pub struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, /// Contract configurations. @@ -17,14 +17,14 @@ pub(crate) struct DeployConfig { /// Chain-level settings. #[derive(Debug, Deserialize)] #[allow(dead_code)] -pub(crate) struct ChainConfig { +pub struct ChainConfig { /// The chain ID. pub chain_id: u64, } /// All contract configurations. #[derive(Debug, Deserialize)] -pub(crate) struct ContractsConfig { +pub struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, /// `FeeVault` contract config (optional). @@ -43,7 +43,7 @@ pub(crate) struct ContractsConfig { /// `AdminProxy` configuration. #[derive(Debug, Deserialize)] -pub(crate) struct AdminProxyConfig { +pub struct AdminProxyConfig { /// Address to deploy at. pub address: Address, /// Owner address. @@ -52,7 +52,7 @@ pub(crate) struct AdminProxyConfig { /// `FeeVault` configuration. #[derive(Debug, Deserialize)] -pub(crate) struct FeeVaultConfig { +pub struct FeeVaultConfig { /// Address to deploy at. pub address: Address, /// Owner address. @@ -82,7 +82,7 @@ pub(crate) struct FeeVaultConfig { /// `MerkleTreeHook` configuration (Hyperlane required hook). #[derive(Debug, Deserialize)] -pub(crate) struct MerkleTreeHookConfig { +pub struct MerkleTreeHookConfig { /// Address to deploy at. pub address: Address, /// Owner address (for post-genesis hook/ISM changes). @@ -94,7 +94,7 @@ pub(crate) struct MerkleTreeHookConfig { /// `MailboxConfig` configuration (Hyperlane core messaging hub). #[derive(Debug, Deserialize)] -pub(crate) struct MailboxConfig { +pub struct MailboxConfig { /// Address to deploy at. pub address: Address, /// Owner address. @@ -113,21 +113,21 @@ pub(crate) struct MailboxConfig { /// `NoopIsm` configuration (Hyperlane ISM that accepts all messages). #[derive(Debug, Deserialize)] -pub(crate) struct NoopIsmConfig { +pub struct NoopIsmConfig { /// Address to deploy at. pub address: Address, } /// `Permit2` configuration (Uniswap token approval manager). #[derive(Debug, Deserialize)] -pub(crate) struct Permit2Config { +pub struct Permit2Config { /// Address to deploy at. pub address: Address, } /// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee). #[derive(Debug, Deserialize)] -pub(crate) struct ProtocolFeeConfig { +pub struct ProtocolFeeConfig { /// Address to deploy at. pub address: Address, /// Owner address. @@ -145,7 +145,7 @@ pub(crate) struct ProtocolFeeConfig { impl DeployConfig { /// Load and validate config from a TOML file. - pub(crate) fn load(path: &Path) -> eyre::Result { + pub fn load(path: &Path) -> eyre::Result { let content = std::fs::read_to_string(path)?; let config: Self = toml::from_str(&content)?; config.validate()?; diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index 51d5ce59..c1a4e1e8 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); /// Build a genesis alloc entry for `AdminProxy`. -pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { +pub fn build(config: &AdminProxyConfig) -> GenesisContract { let mut storage = BTreeMap::new(); // Slot 0: owner (address left-padded to 32 bytes) diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs index c99bd39b..0fad50ef 100644 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ b/bin/ev-deployer/src/contracts/fee_vault.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); /// Build a genesis alloc entry for `FeeVault`. -pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { +pub fn build(config: &FeeVaultConfig) -> GenesisContract { let mut storage = BTreeMap::new(); // Apply constructor default: bps 0 -> 10000 diff --git a/bin/ev-deployer/src/contracts/immutables.rs b/bin/ev-deployer/src/contracts/immutables.rs index 40c75f48..1d97624f 100644 --- a/bin/ev-deployer/src/contracts/immutables.rs +++ b/bin/ev-deployer/src/contracts/immutables.rs @@ -10,7 +10,7 @@ use alloy_primitives::{Address, B256, U256}; /// A single immutable reference inside a bytecode blob. #[derive(Debug, Clone, Copy)] -pub(crate) struct ImmutableRef { +pub struct ImmutableRef { /// Byte offset into the **runtime** bytecode. pub start: usize, /// Number of bytes (always 32 for EVM words). @@ -22,7 +22,7 @@ pub(crate) struct ImmutableRef { /// # Panics /// /// Panics if any reference extends past the end of `bytecode`. -pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) { +pub fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) { for r in refs { assert!( r.start + r.length <= bytecode.len(), @@ -36,19 +36,19 @@ pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u } /// Convenience: patch with an ABI-encoded `address` (left-padded to 32 bytes). -pub(crate) fn patch_address(bytecode: &mut [u8], refs: &[ImmutableRef], addr: Address) { +pub fn patch_address(bytecode: &mut [u8], refs: &[ImmutableRef], addr: Address) { let word: B256 = B256::from(U256::from_be_bytes(addr.into_word().0)); patch_bytes(bytecode, refs, &word.0); } /// Convenience: patch with an ABI-encoded `uint32` (left-padded to 32 bytes). -pub(crate) fn patch_u32(bytecode: &mut [u8], refs: &[ImmutableRef], val: u32) { +pub fn patch_u32(bytecode: &mut [u8], refs: &[ImmutableRef], val: u32) { let word = B256::from(U256::from(val)); patch_bytes(bytecode, refs, &word.0); } /// Convenience: patch with an ABI-encoded `uint256`. -pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { +pub fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { let word = B256::from(val); patch_bytes(bytecode, refs, &word.0); } diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs index 4f774587..3de51d5b 100644 --- a/bin/ev-deployer/src/contracts/mailbox.rs +++ b/bin/ev-deployer/src/contracts/mailbox.rs @@ -74,7 +74,7 @@ const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ ]; /// Build a genesis alloc entry for `Mailbox`. -pub(crate) fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { +pub fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { let mut bytecode = MAILBOX_BYTECODE.to_vec(); // Patch immutables diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs index 0723adab..8ba8b2cf 100644 --- a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs +++ b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs @@ -77,7 +77,7 @@ const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ImmutableRef { }]; /// Build a genesis alloc entry for `MerkleTreeHook`. -pub(crate) fn build(config: &MerkleTreeHookConfig, local_domain: u32) -> GenesisContract { +pub fn build(config: &MerkleTreeHookConfig, local_domain: u32) -> GenesisContract { let mut bytecode = MERKLE_TREE_HOOK_BYTECODE.to_vec(); // Patch immutables diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 72b5fcae..3e7c12dd 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,19 +1,20 @@ //! Contract bytecode and storage encoding. -pub(crate) mod admin_proxy; -pub(crate) mod fee_vault; -pub(crate) mod immutables; -pub(crate) mod mailbox; -pub(crate) mod merkle_tree_hook; -pub(crate) mod noop_ism; -pub(crate) mod permit2; -pub(crate) mod protocol_fee; +pub mod admin_proxy; +pub mod fee_vault; +pub mod immutables; +pub mod mailbox; +pub mod merkle_tree_hook; +pub mod noop_ism; +pub mod permit2; +pub mod protocol_fee; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; /// A contract ready to be placed in genesis alloc. -pub(crate) struct GenesisContract { +#[derive(Debug)] +pub struct GenesisContract { /// The address to deploy at. pub address: Address, /// Runtime bytecode. diff --git a/bin/ev-deployer/src/contracts/noop_ism.rs b/bin/ev-deployer/src/contracts/noop_ism.rs index 9f17efe0..51d7f946 100644 --- a/bin/ev-deployer/src/contracts/noop_ism.rs +++ b/bin/ev-deployer/src/contracts/noop_ism.rs @@ -27,7 +27,7 @@ use std::collections::BTreeMap; const NOOP_ISM_BYTECODE: &[u8] = &hex!("608060405234801561001057600080fd5b50600436106100415760003560e01c80636465e69f1461004657806393c4484714610065578063f7e83aee146100ae575b600080fd5b61004e600681565b60405160ff90911681526020015b60405180910390f35b6100a16040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161005c91906100d6565b6100c66100bc36600461018c565b6001949350505050565b604051901515815260200161005c565b60006020808352835180602085015260005b81811015610104578581018301518582016040015282016100e8565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008083601f84011261015557600080fd5b50813567ffffffffffffffff81111561016d57600080fd5b60208301915083602082850101111561018557600080fd5b9250929050565b600080600080604085870312156101a257600080fd5b843567ffffffffffffffff808211156101ba57600080fd5b6101c688838901610143565b909650945060208701359150808211156101df57600080fd5b506101ec87828801610143565b9598949750955050505056"); /// Build a genesis alloc entry for `NoopIsm`. -pub(crate) fn build(config: &NoopIsmConfig) -> GenesisContract { +pub fn build(config: &NoopIsmConfig) -> GenesisContract { GenesisContract { address: config.address, code: Bytes::from(NOOP_ISM_BYTECODE.to_vec()), diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index ee56f85b..00a1380a 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -68,7 +68,7 @@ const HASHED_NAME: B256 = B256::new(hex!( )); /// Build a genesis alloc entry for `Permit2`. -pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { +pub fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { let mut bytecode = PERMIT2_BYTECODE.to_vec(); // Patch _CACHED_CHAIN_ID diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs index d7b2135c..5d80d2de 100644 --- a/bin/ev-deployer/src/contracts/protocol_fee.rs +++ b/bin/ev-deployer/src/contracts/protocol_fee.rs @@ -56,7 +56,7 @@ const MAX_PROTOCOL_FEE_REFS: &[ImmutableRef] = &[ ]; /// Build a genesis alloc entry for `ProtocolFee`. -pub(crate) fn build(config: &ProtocolFeeConfig) -> GenesisContract { +pub fn build(config: &ProtocolFeeConfig) -> GenesisContract { let mut bytecode = PROTOCOL_FEE_BYTECODE.to_vec(); // Patch immutables diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 05b9fd40..59cc6bf5 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -9,7 +9,7 @@ use serde_json::{Map, Value}; use std::path::Path; /// Build the alloc JSON from config. -pub(crate) fn build_alloc(config: &DeployConfig) -> Value { +pub fn build_alloc(config: &DeployConfig) -> Value { let mut alloc = Map::new(); if let Some(ref ap_config) = config.contracts.admin_proxy { @@ -53,14 +53,15 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { } /// Build alloc and merge into an existing genesis JSON file. -pub(crate) fn merge_into( - config: &DeployConfig, - genesis_path: &Path, - force: bool, -) -> eyre::Result { +pub fn merge_into(config: &DeployConfig, genesis_path: &Path, force: bool) -> eyre::Result { let content = std::fs::read_to_string(genesis_path)?; let mut genesis: Value = serde_json::from_str(&content)?; + merge_alloc(config, &mut genesis, force)?; + Ok(genesis) +} +/// Merge deployer contracts into a genesis JSON value in memory. +pub fn merge_alloc(config: &DeployConfig, genesis: &mut Value, force: bool) -> eyre::Result<()> { let alloc = build_alloc(config); let genesis_alloc = genesis @@ -76,7 +77,7 @@ pub(crate) fn merge_into( genesis_alloc.insert(addr.clone(), entry.clone()); } - Ok(genesis) + Ok(()) } fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { diff --git a/bin/ev-deployer/src/lib.rs b/bin/ev-deployer/src/lib.rs new file mode 100644 index 00000000..08bca345 --- /dev/null +++ b/bin/ev-deployer/src/lib.rs @@ -0,0 +1,9 @@ +//! EV Deployer — genesis alloc generator for ev-reth contracts. +//! +//! This crate provides both a CLI tool and a library for generating genesis +//! alloc entries from declarative TOML configurations. + +pub mod config; +pub mod contracts; +pub mod genesis; +pub mod output; diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5e6e3d3..feda28f7 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -1,11 +1,7 @@ //! EV Deployer — genesis alloc generator for ev-reth contracts. -mod config; -mod contracts; -mod genesis; -mod output; - use clap::{Parser, Subcommand}; +use ev_deployer::{config, genesis, output}; use std::path::PathBuf; /// EV Deployer: generate genesis alloc entries for ev-reth contracts. diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 5e97dd4d..47b5a097 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -4,7 +4,7 @@ use crate::config::DeployConfig; use serde_json::{Map, Value}; /// Build an address manifest JSON from config. -pub(crate) fn build_manifest(config: &DeployConfig) -> Value { +pub fn build_manifest(config: &DeployConfig) -> Value { let mut manifest = Map::new(); if let Some(ref ap) = config.contracts.admin_proxy { diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml index 99a72d1a..5a107433 100644 --- a/bin/ev-dev/Cargo.toml +++ b/bin/ev-dev/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [dependencies] # Core evolve crates ev-node = { path = "../../crates/node" } +ev-deployer = { path = "../ev-deployer" } evolve-ev-reth = { path = "../../crates/evolve" } # Reth CLI and core dependencies diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 7ed4e653..6eeef394 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -7,12 +7,13 @@ use alloy_signer_local::{coins_bip39::English, MnemonicBuilder}; use clap::Parser; +use ev_deployer::{config::DeployConfig, genesis::merge_alloc, output::build_manifest}; use evolve_ev_reth::{ config::EvolveConfig, rpc::txpool::{EvolveTxpoolApiImpl, EvolveTxpoolApiServer}, }; use reth_ethereum_cli::Cli; -use std::io::Write; +use std::{io::Write, path::PathBuf}; use tracing::info; use ev_node::{EvolveArgs, EvolveChainSpecParser, EvolveNode}; @@ -55,6 +56,10 @@ struct EvDevArgs { /// Number of accounts to display (1..=20) #[arg(long, default_value_t = 10, value_parser = parse_accounts)] accounts: usize, + + /// Path to an ev-deployer TOML config to deploy contracts at genesis. + #[arg(long, value_name = "PATH")] + deploy_config: Option, } fn derive_keys(count: usize) -> Vec<(String, String)> { @@ -84,7 +89,7 @@ fn chain_id_from_genesis() -> u64 { .expect("genesis must have config.chainId") } -fn print_banner(args: &EvDevArgs) { +fn print_banner(args: &EvDevArgs, deploy_cfg: Option<&DeployConfig>) { let accounts = derive_keys(args.accounts); println!(); @@ -124,6 +129,20 @@ fn print_banner(args: &EvDevArgs) { println!("Mnemonic: {HARDHAT_MNEMONIC}"); println!("Derivation path: m/44'/60'/0'/0/{{index}}"); println!(); + + if let Some(cfg) = deploy_cfg { + let config_path = args.deploy_config.as_ref().unwrap(); + println!("Genesis Contracts (from {})", config_path.display()); + println!("=================="); + let manifest = build_manifest(cfg); + if let Some(obj) = manifest.as_object() { + for (name, addr) in obj { + println!(" {name:20} {addr}"); + } + } + println!(); + } + println!("WARNING: These accounts and keys are publicly known."); println!("Any funds sent to them on mainnet WILL BE LOST."); println!(); @@ -138,15 +157,39 @@ fn main() { let dev_args = EvDevArgs::parse(); + let deploy_cfg = dev_args.deploy_config.as_ref().map(|config_path| { + let mut cfg = DeployConfig::load(config_path) + .unwrap_or_else(|e| panic!("failed to load deploy config: {e}")); + + let genesis_chain_id = chain_id_from_genesis(); + if cfg.chain.chain_id != genesis_chain_id { + eprintln!( + "WARNING: deploy config chain_id ({}) differs from devnet genesis ({}), overriding to {}", + cfg.chain.chain_id, genesis_chain_id, genesis_chain_id + ); + cfg.chain.chain_id = genesis_chain_id; + } + cfg + }); + if !dev_args.silent { - print_banner(&dev_args); + print_banner(&dev_args, deploy_cfg.as_ref()); } + let genesis_json = if let Some(ref cfg) = deploy_cfg { + let mut genesis: serde_json::Value = + serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); + merge_alloc(cfg, &mut genesis, true).expect("failed to merge deploy config into genesis"); + serde_json::to_string(&genesis).expect("failed to serialize merged genesis") + } else { + DEVNET_GENESIS.to_string() + }; + // Write genesis to a temp file that lives for the process duration let mut genesis_file = tempfile::NamedTempFile::new().expect("failed to create temp genesis file"); genesis_file - .write_all(DEVNET_GENESIS.as_bytes()) + .write_all(genesis_json.as_bytes()) .expect("failed to write genesis"); let genesis_path = genesis_file .path() diff --git a/justfile b/justfile index 757a5b96..941833a6 100644 --- a/justfile +++ b/justfile @@ -38,6 +38,14 @@ build-all: build-deployer: {{cargo}} build --release --bin ev-deployer +# Install ev-dev to ~/.cargo/bin +install-ev-dev: + {{cargo}} install --path bin/ev-dev + +# Install ev-deployer to ~/.cargo/bin +install-ev-deployer: + {{cargo}} install --path bin/ev-deployer + # Testing ────────────────────────────────────────────── # Run all tests From 911e2179b877d7b226136b9833bed7d22c3495b1 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 19 Mar 2026 21:47:31 +0100 Subject: [PATCH 20/81] docs(ev-dev): add genesis contract deployment usage to README --- bin/ev-dev/README.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md index 39a615f2..731a6e2b 100644 --- a/bin/ev-dev/README.md +++ b/bin/ev-dev/README.md @@ -28,6 +28,7 @@ ev-dev [OPTIONS] | `--block-time` | `1` | Block time in seconds (`0` = mine on transaction) | | `--silent` | `false` | Suppress the startup banner | | `--accounts` | `10` | Number of accounts to display (1-20) | +| `--deploy-config` | — | Path to an ev-deployer TOML config to deploy contracts at genesis | ### Examples @@ -40,8 +41,38 @@ ev-dev --host 0.0.0.0 # Custom port, faster blocks ev-dev --port 9545 --block-time 2 + +# Start with genesis contracts deployed +ev-dev --deploy-config bin/ev-deployer/examples/devnet.toml ``` +## Genesis Contract Deployment + +You can deploy contracts into the genesis state by passing a `--deploy-config` flag pointing to an [ev-deployer](../ev-deployer/README.md) TOML config file. + +```bash +ev-dev --deploy-config path/to/deploy.toml +``` + +When a deploy config is provided, ev-dev will: + +1. Load and validate the config +2. Override the config's `chain_id` to match the devnet genesis (a warning is printed if they differ) +3. Merge the contract alloc entries into the genesis state before starting the node +4. Print the deployed contract addresses in the startup banner + +The startup banner will show an extra section: + +``` +Genesis Contracts (from path/to/deploy.toml) +================== + admin_proxy "0x000000000000000000000000000000000000Ad00" + fee_vault "0x000000000000000000000000000000000000FE00" + ... +``` + +See the [ev-deployer README](../ev-deployer/README.md) for full config reference and available contracts. + ## Chain Details | Property | Value | @@ -204,9 +235,10 @@ ev-dev includes all Evolve customizations out of the box: ev-dev is a thin wrapper around the full `ev-reth` node. On startup it: -1. Writes the embedded devnet genesis to a temp file -2. Creates a temporary data directory (clean state every run) -3. Launches `ev-reth` in `--dev` mode with networking disabled -4. Exposes HTTP and WebSocket RPC on the configured host/port +1. If `--deploy-config` is provided, loads the config and merges contract alloc entries into the genesis +2. Writes the (possibly extended) devnet genesis to a temp file +3. Creates a temporary data directory (clean state every run) +4. Launches `ev-reth` in `--dev` mode with networking disabled +5. Exposes HTTP and WebSocket RPC on the configured host/port Each run starts from a fresh genesis — there is no persistent state between restarts. From ebd4365d64f0e78cd416c780d06333c504598520 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Fri, 20 Mar 2026 00:02:00 +0100 Subject: [PATCH 21/81] feat(ev-dev): add interactive TUI dashboard with --tui flag - Integrate ratatui for terminal UI with blocks, logs, and accounts panels - Implement custom tracing layer to capture real-time log events - Add keyboard navigation (Tab for panel switch, arrows for scroll, q to quit) - Support coexistence of TUI and plain log output modes - Add crossterm for terminal event handling --- Cargo.lock | 422 +++++++++++++++++++++++++++- bin/ev-dev/Cargo.toml | 9 + bin/ev-dev/src/main.rs | 218 ++++++++++---- bin/ev-dev/src/tui/app.rs | 156 ++++++++++ bin/ev-dev/src/tui/events.rs | 16 ++ bin/ev-dev/src/tui/mod.rs | 72 +++++ bin/ev-dev/src/tui/tracing_layer.rs | 83 ++++++ bin/ev-dev/src/tui/ui.rs | 288 +++++++++++++++++++ 8 files changed, 1202 insertions(+), 62 deletions(-) create mode 100644 bin/ev-dev/src/tui/app.rs create mode 100644 bin/ev-dev/src/tui/events.rs create mode 100644 bin/ev-dev/src/tui/mod.rs create mode 100644 bin/ev-dev/src/tui/tracing_layer.rs create mode 100644 bin/ev-dev/src/tui/ui.rs diff --git a/Cargo.lock b/Cargo.lock index c5d21994..608726b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1406,6 +1406,15 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1528,15 +1537,30 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -2189,6 +2213,7 @@ dependencies = [ "crossterm_winapi", "derive_more", "document-features", + "futures-core", "mio", "parking_lot", "rustix", @@ -2235,6 +2260,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf 0.11.3", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2435,6 +2470,12 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + [[package]] name = "der" version = "0.7.10" @@ -2909,6 +2950,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "ev-common" version = "0.1.0" @@ -2937,16 +2987,21 @@ dependencies = [ "alloy-primitives", "alloy-signer-local", "clap", + "crossterm", "ev-deployer", "ev-node", "evolve-ev-reth", "eyre", + "futures", + "ratatui", "reth-cli-util", "reth-ethereum-cli", + "reth-tracing", "serde_json", "tempfile", "tokio", "tracing", + "tracing-subscriber 0.3.23", ] [[package]] @@ -3221,6 +3276,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set 0.5.3", + "regex", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -3275,6 +3340,17 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "filetime" version = "0.2.27" @@ -3292,6 +3368,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + [[package]] name = "fixed-cache" version = "0.1.8" @@ -3336,6 +3418,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.9" @@ -4623,6 +4711,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + [[package]] name = "lazy_static" version = "1.5.0" @@ -4844,6 +4938,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "mach2" version = "0.5.0" @@ -4905,6 +5009,21 @@ dependencies = [ "libc", ] +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metrics" version = "0.24.3" @@ -5101,6 +5220,19 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -5195,6 +5327,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -5541,6 +5684,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "p256" version = "0.13.2" @@ -5658,6 +5810,39 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "pharos" version = "0.5.3" @@ -5668,17 +5853,47 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.13.1", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -5686,7 +5901,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -5695,13 +5923,22 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "phf_shared" version = "0.13.1" @@ -5926,8 +6163,8 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ - "bit-set", - "bit-vec", + "bit-set 0.8.0", + "bit-vec 0.8.0", "bitflags 2.11.0", "num-traits", "rand 0.9.2", @@ -6211,6 +6448,8 @@ dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", "ratatui-widgets", ] @@ -6246,6 +6485,26 @@ dependencies = [ "ratatui-core", ] +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + [[package]] name = "ratatui-widgets" version = "0.3.0" @@ -9125,7 +9384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" dependencies = [ "bitvec", - "phf", + "phf 0.13.1", "revm-primitives", "serde", ] @@ -10243,6 +10502,69 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf 0.11.3", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bitflags 2.11.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf 0.11.3", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -11091,6 +11413,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ + "atomic", "getrandom 0.4.2", "js-sys", "wasm-bindgen", @@ -11166,6 +11489,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "wait-timeout" version = "0.2.1" @@ -11394,6 +11726,78 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + [[package]] name = "widestring" version = "1.2.1" diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml index 5a107433..deed6ff3 100644 --- a/bin/ev-dev/Cargo.toml +++ b/bin/ev-dev/Cargo.toml @@ -26,6 +26,9 @@ reth-ethereum-cli.workspace = true alloy-signer-local.workspace = true alloy-primitives.workspace = true +# Reth tracing (for Layers type) +reth-tracing.workspace = true + # Core dependencies eyre.workspace = true tracing.workspace = true @@ -33,6 +36,12 @@ tokio = { workspace = true, features = ["full"] } clap = { workspace = true, features = ["derive", "env"] } tempfile.workspace = true serde_json.workspace = true +futures.workspace = true + +# TUI +ratatui = "0.30" +crossterm = { version = "0.29", features = ["event-stream"] } +tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] } [lints] workspace = true diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 6eeef394..187f3a47 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -5,6 +5,8 @@ #![allow(missing_docs, rustdoc::missing_crate_level_docs)] +mod tui; + use alloy_signer_local::{coins_bip39::English, MnemonicBuilder}; use clap::Parser; use ev_deployer::{config::DeployConfig, genesis::merge_alloc, output::build_manifest}; @@ -15,6 +17,7 @@ use evolve_ev_reth::{ use reth_ethereum_cli::Cli; use std::{io::Write, path::PathBuf}; use tracing::info; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use ev_node::{EvolveArgs, EvolveChainSpecParser, EvolveNode}; @@ -60,6 +63,10 @@ struct EvDevArgs { /// Path to an ev-deployer TOML config to deploy contracts at genesis. #[arg(long, value_name = "PATH")] deploy_config: Option, + + /// Launch with terminal UI instead of plain log output + #[arg(long, default_value_t = false)] + tui: bool, } fn derive_keys(count: usize) -> Vec<(String, String)> { @@ -148,59 +155,11 @@ fn print_banner(args: &EvDevArgs, deploy_cfg: Option<&DeployConfig>) { println!(); } -fn main() { - reth_cli_util::sigsegv_handler::install(); - - if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "1"); - } - - let dev_args = EvDevArgs::parse(); - - let deploy_cfg = dev_args.deploy_config.as_ref().map(|config_path| { - let mut cfg = DeployConfig::load(config_path) - .unwrap_or_else(|e| panic!("failed to load deploy config: {e}")); - - let genesis_chain_id = chain_id_from_genesis(); - if cfg.chain.chain_id != genesis_chain_id { - eprintln!( - "WARNING: deploy config chain_id ({}) differs from devnet genesis ({}), overriding to {}", - cfg.chain.chain_id, genesis_chain_id, genesis_chain_id - ); - cfg.chain.chain_id = genesis_chain_id; - } - cfg - }); - - if !dev_args.silent { - print_banner(&dev_args, deploy_cfg.as_ref()); - } - - let genesis_json = if let Some(ref cfg) = deploy_cfg { - let mut genesis: serde_json::Value = - serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); - merge_alloc(cfg, &mut genesis, true).expect("failed to merge deploy config into genesis"); - serde_json::to_string(&genesis).expect("failed to serialize merged genesis") - } else { - DEVNET_GENESIS.to_string() - }; - - // Write genesis to a temp file that lives for the process duration - let mut genesis_file = - tempfile::NamedTempFile::new().expect("failed to create temp genesis file"); - genesis_file - .write_all(genesis_json.as_bytes()) - .expect("failed to write genesis"); - let genesis_path = genesis_file - .path() - .to_str() - .expect("valid path") - .to_string(); - - // Use a temp data directory so each run starts with clean state - let datadir = tempfile::TempDir::new().expect("failed to create temp data dir"); - let datadir_path = datadir.path().to_str().expect("valid path").to_string(); - +fn build_reth_args( + dev_args: &EvDevArgs, + genesis_path: String, + datadir_path: String, +) -> Vec { let mut args = vec![ "ev-dev".to_string(), "node".to_string(), @@ -236,6 +195,94 @@ fn main() { args.push(format!("{}s", dev_args.block_time)); } + args +} + +fn prepare_genesis( + deploy_cfg: &Option, +) -> (tempfile::NamedTempFile, tempfile::TempDir) { + let genesis_json = if let Some(ref cfg) = deploy_cfg { + let mut genesis: serde_json::Value = + serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); + merge_alloc(cfg, &mut genesis, true).expect("failed to merge deploy config into genesis"); + serde_json::to_string(&genesis).expect("failed to serialize merged genesis") + } else { + DEVNET_GENESIS.to_string() + }; + + let mut genesis_file = + tempfile::NamedTempFile::new().expect("failed to create temp genesis file"); + genesis_file + .write_all(genesis_json.as_bytes()) + .expect("failed to write genesis"); + + let datadir = tempfile::TempDir::new().expect("failed to create temp data dir"); + + (genesis_file, datadir) +} + +fn load_deploy_config(dev_args: &EvDevArgs) -> Option { + dev_args.deploy_config.as_ref().map(|config_path| { + let mut cfg = DeployConfig::load(config_path) + .unwrap_or_else(|e| panic!("failed to load deploy config: {e}")); + + let genesis_chain_id = chain_id_from_genesis(); + if cfg.chain.chain_id != genesis_chain_id { + eprintln!( + "WARNING: deploy config chain_id ({}) differs from devnet genesis ({}), overriding to {}", + cfg.chain.chain_id, genesis_chain_id, genesis_chain_id + ); + cfg.chain.chain_id = genesis_chain_id; + } + cfg + }) +} + +fn deploy_contracts_list(deploy_cfg: &Option) -> Option> { + deploy_cfg.as_ref().map(|cfg| { + let manifest = build_manifest(cfg); + manifest + .as_object() + .map(|obj| { + obj.iter() + .map(|(name, addr)| (name.clone(), addr.as_str().unwrap_or("").to_string())) + .collect() + }) + .unwrap_or_default() + }) +} + +fn main() { + reth_cli_util::sigsegv_handler::install(); + + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + let dev_args = EvDevArgs::parse(); + let deploy_cfg = load_deploy_config(&dev_args); + + if dev_args.tui { + run_with_tui(dev_args, deploy_cfg); + } else { + run_without_tui(dev_args, deploy_cfg); + } +} + +fn run_without_tui(dev_args: EvDevArgs, deploy_cfg: Option) { + if !dev_args.silent { + print_banner(&dev_args, deploy_cfg.as_ref()); + } + + let (genesis_file, datadir) = prepare_genesis(&deploy_cfg); + let genesis_path = genesis_file + .path() + .to_str() + .expect("valid path") + .to_string(); + let datadir_path = datadir.path().to_str().expect("valid path").to_string(); + let args = build_reth_args(&dev_args, genesis_path, datadir_path); + let cli = match Cli::::try_parse_from(args) { Ok(cli) => cli, Err(err) => { @@ -265,3 +312,68 @@ fn main() { std::process::exit(1); } } + +fn run_with_tui(dev_args: EvDevArgs, deploy_cfg: Option) { + let (log_tx, log_rx) = tokio::sync::mpsc::channel(10_000); + + // Install our tracing subscriber with the TUI layer BEFORE cli.run(). + // When reth's internal init_tracing calls try_init(), it will find a + // subscriber already installed and silently skip its own setup. + let tui_layer = tui::TuiTracingLayer::new(log_tx); + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()); + + tracing_subscriber::registry() + .with(filter) + .with(tui_layer) + .init(); + + let chain_id = chain_id_from_genesis(); + let rpc_url = format!("http://{}:{}", dev_args.host, dev_args.port); + let block_time = dev_args.block_time; + let accounts = derive_keys(dev_args.accounts); + let contracts = deploy_contracts_list(&deploy_cfg); + + let app = tui::App::new(chain_id, rpc_url, block_time, accounts, contracts, log_rx); + + let (genesis_file, datadir) = prepare_genesis(&deploy_cfg); + let genesis_path = genesis_file + .path() + .to_str() + .expect("valid path") + .to_string(); + let datadir_path = datadir.path().to_str().expect("valid path").to_string(); + let args = build_reth_args(&dev_args, genesis_path, datadir_path); + + let cli = match Cli::::try_parse_from(args) { + Ok(cli) => cli, + Err(err) => { + eprintln!("{err}"); + std::process::exit(2); + } + }; + + if let Err(err) = cli.run(|builder, _evolve_args| async move { + info!("=== EV-DEV: Starting local development chain (TUI) ==="); + let _handle = builder + .node(EvolveNode::new()) + .extend_rpc_modules(move |ctx| { + let evolve_cfg = EvolveConfig::default(); + let evolve_txpool = + EvolveTxpoolApiImpl::new(ctx.pool().clone(), evolve_cfg.max_txpool_bytes); + ctx.modules.merge_configured(evolve_txpool.into_rpc())?; + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + + info!("=== EV-DEV: Local chain running - RPC ready ==="); + + tui::run(app).await?; + + Ok(()) + }) { + let _ = tui::restore_terminal(); + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs new file mode 100644 index 00000000..d0a5b23c --- /dev/null +++ b/bin/ev-dev/src/tui/app.rs @@ -0,0 +1,156 @@ +use std::{collections::VecDeque, time::Instant}; + +use tokio::sync::mpsc; + +const MAX_LOGS: usize = 1000; +const MAX_BLOCKS: usize = 200; + +#[derive(Debug, Clone)] +pub(crate) struct BlockInfo { + pub(crate) number: u64, + pub(crate) hash: String, + pub(crate) tx_count: u64, + pub(crate) gas_used: u64, +} + +#[derive(Debug, Clone)] +pub(crate) struct LogEntry { + pub(crate) level: tracing::Level, + pub(crate) target: String, + pub(crate) message: String, + pub(crate) fields: Vec<(String, String)>, + pub(crate) timestamp: Instant, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Panel { + Blocks, + Logs, + Accounts, +} + +pub(crate) struct App { + // Static + pub(crate) chain_id: u64, + pub(crate) rpc_url: String, + pub(crate) block_time: u64, + pub(crate) accounts: Vec<(String, String)>, + pub(crate) deploy_contracts: Option>, + + // Dynamic + pub(crate) blocks: VecDeque, + pub(crate) logs: VecDeque, + pub(crate) current_block: u64, + pub(crate) start_time: Instant, + + // UI state + pub(crate) active_panel: Panel, + pub(crate) log_scroll: usize, + pub(crate) block_scroll: usize, + pub(crate) should_quit: bool, + + // Channel + pub(crate) log_rx: mpsc::Receiver, +} + +impl App { + pub(crate) fn new( + chain_id: u64, + rpc_url: String, + block_time: u64, + accounts: Vec<(String, String)>, + deploy_contracts: Option>, + log_rx: mpsc::Receiver, + ) -> Self { + Self { + chain_id, + rpc_url, + block_time, + accounts, + deploy_contracts, + blocks: VecDeque::new(), + logs: VecDeque::new(), + current_block: 0, + start_time: Instant::now(), + active_panel: Panel::Logs, + log_scroll: 0, + block_scroll: 0, + should_quit: false, + log_rx, + } + } + + pub(crate) fn drain_logs(&mut self) { + while let Ok(entry) = self.log_rx.try_recv() { + if entry.message == "built block" { + if let Some(block) = self.parse_block_from_fields(&entry.fields) { + self.current_block = block.number; + self.blocks.push_front(block); + if self.blocks.len() > MAX_BLOCKS { + self.blocks.pop_back(); + } + } + } + + self.logs.push_back(entry); + if self.logs.len() > MAX_LOGS { + self.logs.pop_front(); + } + } + } + + fn parse_block_from_fields(&self, fields: &[(String, String)]) -> Option { + let mut number = None; + let mut hash = String::new(); + let mut tx_count = 0; + let mut gas_used = 0; + + for (k, v) in fields { + match k.as_str() { + "block_number" => number = v.parse().ok(), + "block_hash" => { + let h = v.trim_matches('"'); + hash = if h.len() > 10 { + format!("{}..{}", &h[..6], &h[h.len() - 4..]) + } else { + h.to_string() + }; + } + "tx_count" => tx_count = v.parse().unwrap_or(0), + "gas_used" => gas_used = v.parse().unwrap_or(0), + _ => {} + } + } + + number.map(|n| BlockInfo { + number: n, + hash, + tx_count, + gas_used, + }) + } + + pub(crate) fn next_panel(&mut self) { + self.active_panel = match self.active_panel { + Panel::Blocks => Panel::Logs, + Panel::Logs => Panel::Accounts, + Panel::Accounts => Panel::Blocks, + }; + } + + pub(crate) fn scroll_up(&mut self) { + match self.active_panel { + Panel::Logs => self.log_scroll = self.log_scroll.saturating_add(1), + Panel::Blocks => self.block_scroll = self.block_scroll.saturating_add(1), + Panel::Accounts => {} + } + } + + pub(crate) fn scroll_down(&mut self) { + match self.active_panel { + Panel::Logs => self.log_scroll = self.log_scroll.saturating_sub(1), + Panel::Blocks => self.block_scroll = self.block_scroll.saturating_sub(1), + Panel::Accounts => {} + } + } +} diff --git a/bin/ev-dev/src/tui/events.rs b/bin/ev-dev/src/tui/events.rs new file mode 100644 index 00000000..5606ac8f --- /dev/null +++ b/bin/ev-dev/src/tui/events.rs @@ -0,0 +1,16 @@ +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + +use super::app::App; + +pub(crate) fn handle_key(app: &mut App, key: KeyEvent) { + match key.code { + KeyCode::Char('q') | KeyCode::Esc => app.should_quit = true, + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + app.should_quit = true; + } + KeyCode::Tab => app.next_panel(), + KeyCode::Up => app.scroll_up(), + KeyCode::Down => app.scroll_down(), + _ => {} + } +} diff --git a/bin/ev-dev/src/tui/mod.rs b/bin/ev-dev/src/tui/mod.rs new file mode 100644 index 00000000..b611a3a1 --- /dev/null +++ b/bin/ev-dev/src/tui/mod.rs @@ -0,0 +1,72 @@ +pub(crate) mod app; +mod events; +mod tracing_layer; +mod ui; + +pub(crate) use app::App; +pub(crate) use tracing_layer::TuiTracingLayer; + +use std::io::{self, stdout}; + +use crossterm::{ + event::{Event, EventStream}, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + ExecutableCommand, +}; +use futures::StreamExt; +use ratatui::prelude::CrosstermBackend; + +struct TerminalGuard; + +impl Drop for TerminalGuard { + fn drop(&mut self) { + let _ = disable_raw_mode(); + let _ = stdout().execute(LeaveAlternateScreen); + } +} + +pub(crate) async fn run(mut app: App) -> eyre::Result<()> { + let original_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + let _ = disable_raw_mode(); + let _ = stdout().execute(LeaveAlternateScreen); + original_hook(info); + })); + + enable_raw_mode()?; + stdout().execute(EnterAlternateScreen)?; + let _guard = TerminalGuard; + + let backend = CrosstermBackend::new(stdout()); + let mut terminal = ratatui::Terminal::new(backend)?; + + let mut event_stream = EventStream::new(); + let mut tick = tokio::time::interval(std::time::Duration::from_millis(100)); + + loop { + if app.should_quit { + break; + } + + tokio::select! { + _ = tick.tick() => { + app.drain_logs(); + terminal.draw(|frame| ui::draw(frame, &app))?; + } + maybe_event = event_stream.next() => { + if let Some(Ok(Event::Key(key))) = maybe_event { + events::handle_key(&mut app, key); + } + } + } + } + + // Terminal restored by TerminalGuard drop + Ok(()) +} + +pub(crate) fn restore_terminal() -> io::Result<()> { + disable_raw_mode()?; + stdout().execute(LeaveAlternateScreen)?; + Ok(()) +} diff --git a/bin/ev-dev/src/tui/tracing_layer.rs b/bin/ev-dev/src/tui/tracing_layer.rs new file mode 100644 index 00000000..7ea78d5e --- /dev/null +++ b/bin/ev-dev/src/tui/tracing_layer.rs @@ -0,0 +1,83 @@ +use std::time::Instant; + +use tokio::sync::mpsc; +use tracing::{ + field::{Field, Visit}, + Subscriber, +}; +use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer}; + +use super::app::LogEntry; + +struct FieldCollector { + fields: Vec<(String, String)>, +} + +impl FieldCollector { + const fn new() -> Self { + Self { fields: Vec::new() } + } + + fn take_message(&mut self) -> String { + if let Some(pos) = self.fields.iter().position(|(k, _)| k == "message") { + self.fields.remove(pos).1 + } else { + String::new() + } + } +} + +impl Visit for FieldCollector { + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + self.fields + .push((field.name().to_string(), format!("{:?}", value))); + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } +} + +pub(crate) struct TuiTracingLayer { + tx: mpsc::Sender, +} + +impl TuiTracingLayer { + pub(crate) const fn new(tx: mpsc::Sender) -> Self { + Self { tx } + } +} + +impl Layer for TuiTracingLayer +where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, +{ + fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) { + let mut collector = FieldCollector::new(); + event.record(&mut collector); + + let message = collector.take_message(); + let metadata = event.metadata(); + + let entry = LogEntry { + level: *metadata.level(), + target: metadata.target().to_string(), + message, + fields: collector.fields, + timestamp: Instant::now(), + }; + + let _ = self.tx.try_send(entry); + } +} diff --git a/bin/ev-dev/src/tui/ui.rs b/bin/ev-dev/src/tui/ui.rs new file mode 100644 index 00000000..fe3d783e --- /dev/null +++ b/bin/ev-dev/src/tui/ui.rs @@ -0,0 +1,288 @@ +use ratatui::{ + layout::{Constraint, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Cell, List, ListItem, Paragraph, Row, Table}, + Frame, +}; + +use super::app::{App, Panel}; + +fn border_style(app: &App, panel: Panel) -> Style { + if app.active_panel == panel { + Style::default().fg(Color::Cyan) + } else { + Style::default().fg(Color::DarkGray) + } +} + +const fn level_color(level: &tracing::Level) -> Color { + match *level { + tracing::Level::ERROR => Color::Red, + tracing::Level::WARN => Color::Yellow, + tracing::Level::INFO => Color::Green, + tracing::Level::DEBUG | tracing::Level::TRACE => Color::DarkGray, + } +} + +fn format_uptime(secs: u64) -> String { + let h = secs / 3600; + let m = (secs % 3600) / 60; + let s = secs % 60; + if h > 0 { + format!("{h}h{m:02}m{s:02}s") + } else if m > 0 { + format!("{m}m{s:02}s") + } else { + format!("{s}s") + } +} + +fn format_gas(gas: u64) -> String { + if gas >= 1_000_000 { + format!("{:.1}M", gas as f64 / 1_000_000.0) + } else if gas >= 1_000 { + format!("{:.1}k", gas as f64 / 1_000.0) + } else { + gas.to_string() + } +} + +pub(crate) fn draw(frame: &mut Frame<'_>, app: &App) { + let area = frame.area(); + + let outer = Layout::vertical([ + Constraint::Length(3), // header + Constraint::Min(6), // main content + Constraint::Length(3), // footer + ]) + .split(area); + + draw_header(frame, app, outer[0]); + draw_main(frame, app, outer[1]); + draw_footer(frame, app, outer[2]); +} + +fn draw_header(frame: &mut Frame<'_>, app: &App, area: Rect) { + let block_time_str = if app.block_time == 0 { + "auto".to_string() + } else { + format!("{}s", app.block_time) + }; + + let text = Line::from(vec![ + Span::styled(" Chain: ", Style::default().fg(Color::DarkGray)), + Span::styled(app.chain_id.to_string(), Style::default().fg(Color::White)), + Span::styled(" RPC: ", Style::default().fg(Color::DarkGray)), + Span::styled(&app.rpc_url, Style::default().fg(Color::Cyan)), + Span::styled(" Block: ", Style::default().fg(Color::DarkGray)), + Span::styled(block_time_str, Style::default().fg(Color::White)), + ]); + + let block = Block::default() + .borders(Borders::ALL) + .title(" ev-dev ") + .title_style( + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ) + .border_style(Style::default().fg(Color::Cyan)); + + let paragraph = Paragraph::new(text).block(block); + frame.render_widget(paragraph, area); +} + +fn draw_main(frame: &mut Frame<'_>, app: &App, area: Rect) { + let main_split = Layout::vertical([ + Constraint::Percentage(45), // top row (blocks + accounts) + Constraint::Percentage(55), // logs + ]) + .split(area); + + let top_split = Layout::horizontal([ + Constraint::Percentage(55), // blocks + Constraint::Percentage(45), // accounts + ]) + .split(main_split[0]); + + draw_blocks(frame, app, top_split[0]); + draw_accounts(frame, app, top_split[1]); + draw_logs(frame, app, main_split[1]); +} + +fn draw_blocks(frame: &mut Frame<'_>, app: &App, area: Rect) { + let block = Block::default() + .borders(Borders::ALL) + .title(" Blocks ") + .border_style(border_style(app, Panel::Blocks)); + + let header = Row::new(vec![ + Cell::from("Block").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("Hash").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("Txs").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("Gas").style(Style::default().add_modifier(Modifier::BOLD)), + ]) + .style(Style::default().fg(Color::DarkGray)); + + let rows: Vec> = app + .blocks + .iter() + .skip(app.block_scroll) + .map(|b| { + Row::new(vec![ + Cell::from(format!("#{}", b.number)), + Cell::from(b.hash.clone()).style(Style::default().fg(Color::DarkGray)), + Cell::from(format!("{}", b.tx_count)), + Cell::from(format_gas(b.gas_used)), + ]) + }) + .collect(); + + let widths = [ + Constraint::Length(8), + Constraint::Length(12), + Constraint::Length(5), + Constraint::Min(6), + ]; + + let table = Table::new(rows, widths) + .header(header) + .block(block) + .row_highlight_style(Style::default().fg(Color::Cyan)); + + frame.render_widget(table, area); +} + +fn draw_accounts(frame: &mut Frame<'_>, app: &App, area: Rect) { + let mut items: Vec> = app + .accounts + .iter() + .enumerate() + .map(|(i, (addr, _key))| { + let truncated = if addr.len() > 10 { + format!("{}..{}", &addr[..6], &addr[addr.len() - 4..]) + } else { + addr.clone() + }; + ListItem::new(Line::from(vec![ + Span::styled(format!("({i}) "), Style::default().fg(Color::DarkGray)), + Span::styled(truncated, Style::default().fg(Color::White)), + Span::styled(" 1000000 ETH", Style::default().fg(Color::Green)), + ])) + }) + .collect(); + + if let Some(ref contracts) = app.deploy_contracts { + items.push(ListItem::new(Line::from(""))); + items.push(ListItem::new(Line::from(Span::styled( + "Genesis Contracts", + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + )))); + for (name, addr) in contracts { + let truncated = if addr.len() > 10 { + format!("{}..{}", &addr[..6], &addr[addr.len() - 4..]) + } else { + addr.clone() + }; + items.push(ListItem::new(Line::from(vec![ + Span::styled(format!("{name:18} "), Style::default().fg(Color::DarkGray)), + Span::styled(truncated, Style::default().fg(Color::White)), + ]))); + } + } + + let block = Block::default() + .borders(Borders::ALL) + .title(" Accounts ") + .border_style(border_style(app, Panel::Accounts)); + + let list = List::new(items).block(block); + frame.render_widget(list, area); +} + +fn draw_logs(frame: &mut Frame<'_>, app: &App, area: Rect) { + let block = Block::default() + .borders(Borders::ALL) + .title(" Logs ") + .border_style(border_style(app, Panel::Logs)); + + let inner_height = area.height.saturating_sub(2) as usize; + let total = app.logs.len(); + + let end = total.saturating_sub(app.log_scroll); + let start = end.saturating_sub(inner_height); + + let items: Vec> = app + .logs + .iter() + .skip(start) + .take(end.saturating_sub(start)) + .map(|entry| { + let color = level_color(&entry.level); + let level_str = format!("{:5}", entry.level); + let elapsed = entry.timestamp.elapsed().as_secs(); + let ts = format!("{elapsed:>4}s"); + + let target_short = entry.target.rsplit("::").next().unwrap_or(&entry.target); + + let mut spans = vec![ + Span::styled(ts, Style::default().fg(Color::DarkGray)), + Span::raw(" "), + Span::styled(level_str, Style::default().fg(color)), + Span::raw(" "), + Span::styled( + format!("{target_short:>16} "), + Style::default().fg(Color::DarkGray), + ), + Span::styled(entry.message.clone(), Style::default().fg(Color::White)), + ]; + + for (k, v) in &entry.fields { + spans.push(Span::raw(" ")); + spans.push(Span::styled( + format!("{k}="), + Style::default().fg(Color::DarkGray), + )); + spans.push(Span::styled(v.clone(), Style::default().fg(Color::Gray))); + } + + ListItem::new(Line::from(spans)) + }) + .collect(); + + let list = List::new(items).block(block); + frame.render_widget(list, area); +} + +fn draw_footer(frame: &mut Frame<'_>, app: &App, area: Rect) { + let uptime = app.start_time.elapsed().as_secs(); + + let text = Line::from(vec![ + Span::styled(" Up: ", Style::default().fg(Color::DarkGray)), + Span::styled(format_uptime(uptime), Style::default().fg(Color::White)), + Span::styled(" Block: ", Style::default().fg(Color::DarkGray)), + Span::styled( + format!("#{}", app.current_block), + Style::default().fg(Color::Cyan), + ), + Span::styled(" | ", Style::default().fg(Color::DarkGray)), + Span::styled("[q]", Style::default().fg(Color::Yellow)), + Span::styled("uit ", Style::default().fg(Color::DarkGray)), + Span::styled("[Tab]", Style::default().fg(Color::Yellow)), + Span::styled("focus ", Style::default().fg(Color::DarkGray)), + Span::styled("[", Style::default().fg(Color::Yellow)), + Span::styled("Up/Down", Style::default().fg(Color::Yellow)), + Span::styled("]", Style::default().fg(Color::Yellow)), + Span::styled("scroll", Style::default().fg(Color::DarkGray)), + ]); + + let block = Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::DarkGray)); + + let paragraph = Paragraph::new(text).block(block); + frame.render_widget(paragraph, area); +} From e0c333054bb94afadb3e0178499ab20d906a8a26 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 20 Mar 2026 15:41:09 +0100 Subject: [PATCH 22/81] feat(ev-dev): add real-time balance polling to TUI dashboard --- Cargo.lock | 1 + bin/ev-dev/Cargo.toml | 1 + bin/ev-dev/src/main.rs | 12 ++++++- bin/ev-dev/src/tui/app.rs | 71 ++++++++++++++++++++++++++++++++++++++- bin/ev-dev/src/tui/mod.rs | 3 +- bin/ev-dev/src/tui/ui.rs | 7 +++- 6 files changed, 91 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 608726b2..587e1d9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2985,6 +2985,7 @@ name = "ev-dev" version = "0.1.0" dependencies = [ "alloy-primitives", + "alloy-provider", "alloy-signer-local", "clap", "crossterm", diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml index deed6ff3..57115c7b 100644 --- a/bin/ev-dev/Cargo.toml +++ b/bin/ev-dev/Cargo.toml @@ -25,6 +25,7 @@ reth-ethereum-cli.workspace = true # Alloy dependencies alloy-signer-local.workspace = true alloy-primitives.workspace = true +alloy-provider.workspace = true # Reth tracing (for Layers type) reth-tracing.workspace = true diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 187f3a47..4c8b660c 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -333,7 +333,16 @@ fn run_with_tui(dev_args: EvDevArgs, deploy_cfg: Option) { let accounts = derive_keys(dev_args.accounts); let contracts = deploy_contracts_list(&deploy_cfg); - let app = tui::App::new(chain_id, rpc_url, block_time, accounts, contracts, log_rx); + let (balance_tx, balance_rx) = tokio::sync::mpsc::channel(16); + let app = tui::App::new( + chain_id, + rpc_url.clone(), + block_time, + accounts.clone(), + contracts, + log_rx, + balance_rx, + ); let (genesis_file, datadir) = prepare_genesis(&deploy_cfg); let genesis_path = genesis_file @@ -368,6 +377,7 @@ fn run_with_tui(dev_args: EvDevArgs, deploy_cfg: Option) { info!("=== EV-DEV: Local chain running - RPC ready ==="); + tui::spawn_balance_poller(rpc_url, accounts, balance_tx); tui::run(app).await?; Ok(()) diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs index d0a5b23c..a0ac2a57 100644 --- a/bin/ev-dev/src/tui/app.rs +++ b/bin/ev-dev/src/tui/app.rs @@ -1,5 +1,6 @@ use std::{collections::VecDeque, time::Instant}; +use alloy_primitives::{Address, U256}; use tokio::sync::mpsc; const MAX_LOGS: usize = 1000; @@ -42,6 +43,7 @@ pub(crate) struct App { pub(crate) logs: VecDeque, pub(crate) current_block: u64, pub(crate) start_time: Instant, + pub(crate) balances: Vec, // UI state pub(crate) active_panel: Panel, @@ -49,8 +51,9 @@ pub(crate) struct App { pub(crate) block_scroll: usize, pub(crate) should_quit: bool, - // Channel + // Channels pub(crate) log_rx: mpsc::Receiver, + pub(crate) balance_rx: mpsc::Receiver>, } impl App { @@ -61,7 +64,10 @@ impl App { accounts: Vec<(String, String)>, deploy_contracts: Option>, log_rx: mpsc::Receiver, + balance_rx: mpsc::Receiver>, ) -> Self { + let initial_balance = "1000000 ETH".to_string(); + let balances = vec![initial_balance; accounts.len()]; Self { chain_id, rpc_url, @@ -72,11 +78,19 @@ impl App { logs: VecDeque::new(), current_block: 0, start_time: Instant::now(), + balances, active_panel: Panel::Logs, log_scroll: 0, block_scroll: 0, should_quit: false, log_rx, + balance_rx, + } + } + + pub(crate) fn drain_balances(&mut self) { + while let Ok(new_balances) = self.balance_rx.try_recv() { + self.balances = new_balances; } } @@ -154,3 +168,58 @@ impl App { } } } + +fn format_ether(wei: U256) -> String { + let ether_unit = U256::from(10u64).pow(U256::from(18)); + let whole = wei / ether_unit; + let remainder = wei % ether_unit; + + let frac_digits = 4; + let frac_unit = U256::from(10u64).pow(U256::from(18 - frac_digits)); + let frac = remainder / frac_unit; + + let frac_val: u64 = frac.try_into().unwrap_or(0); + let formatted = format!("{whole}.{frac_val:0>4}"); + // Trim trailing zeros but keep at least one decimal + let trimmed = formatted.trim_end_matches('0'); + let trimmed = trimmed.trim_end_matches('.'); + format!("{trimmed} ETH") +} + +pub(crate) fn spawn_balance_poller( + rpc_url: String, + accounts: Vec<(String, String)>, + tx: mpsc::Sender>, +) { + let addresses: Vec
= accounts + .iter() + .filter_map(|(addr, _)| addr.parse().ok()) + .collect(); + + tokio::spawn(async move { + use alloy_provider::{Provider, ProviderBuilder}; + + let mut interval = tokio::time::interval(std::time::Duration::from_secs(2)); + loop { + interval.tick().await; + + let provider = match ProviderBuilder::new() + .connect_http(rpc_url.parse().expect("valid RPC URL")) + { + provider => provider, + }; + + let mut balances = Vec::with_capacity(addresses.len()); + for addr in &addresses { + match provider.get_balance(*addr).await { + Ok(bal) => balances.push(format_ether(bal)), + Err(_) => balances.push("? ETH".to_string()), + } + } + + if tx.send(balances).await.is_err() { + break; + } + } + }); +} diff --git a/bin/ev-dev/src/tui/mod.rs b/bin/ev-dev/src/tui/mod.rs index b611a3a1..083eff37 100644 --- a/bin/ev-dev/src/tui/mod.rs +++ b/bin/ev-dev/src/tui/mod.rs @@ -3,7 +3,7 @@ mod events; mod tracing_layer; mod ui; -pub(crate) use app::App; +pub(crate) use app::{spawn_balance_poller, App}; pub(crate) use tracing_layer::TuiTracingLayer; use std::io::{self, stdout}; @@ -51,6 +51,7 @@ pub(crate) async fn run(mut app: App) -> eyre::Result<()> { tokio::select! { _ = tick.tick() => { app.drain_logs(); + app.drain_balances(); terminal.draw(|frame| ui::draw(frame, &app))?; } maybe_event = event_stream.next() => { diff --git a/bin/ev-dev/src/tui/ui.rs b/bin/ev-dev/src/tui/ui.rs index fe3d783e..3006f70f 100644 --- a/bin/ev-dev/src/tui/ui.rs +++ b/bin/ev-dev/src/tui/ui.rs @@ -165,10 +165,15 @@ fn draw_accounts(frame: &mut Frame<'_>, app: &App, area: Rect) { } else { addr.clone() }; + let balance = app + .balances + .get(i) + .cloned() + .unwrap_or_else(|| "? ETH".to_string()); ListItem::new(Line::from(vec![ Span::styled(format!("({i}) "), Style::default().fg(Color::DarkGray)), Span::styled(truncated, Style::default().fg(Color::White)), - Span::styled(" 1000000 ETH", Style::default().fg(Color::Green)), + Span::styled(format!(" {balance}"), Style::default().fg(Color::Green)), ])) }) .collect(); From 8a3a4f362c303e14c39d37077e7dbe10891ed077 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 23 Mar 2026 14:21:12 +0100 Subject: [PATCH 23/81] fix(ev-dev): replace redundant match with direct let binding --- bin/ev-dev/src/tui/app.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs index a0ac2a57..29490e91 100644 --- a/bin/ev-dev/src/tui/app.rs +++ b/bin/ev-dev/src/tui/app.rs @@ -203,11 +203,8 @@ pub(crate) fn spawn_balance_poller( loop { interval.tick().await; - let provider = match ProviderBuilder::new() - .connect_http(rpc_url.parse().expect("valid RPC URL")) - { - provider => provider, - }; + let provider = + ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL")); let mut balances = Vec::with_capacity(addresses.len()); for addr in &addresses { From fa99c4f54a70b8dea2dbb9918cf295cef7f55e4e Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 10:45:11 +0100 Subject: [PATCH 24/81] feat(ev-dev): add block selection and transaction detail overlay to TUI Add interactive block selection with arrow keys and Enter to fetch/display block transactions via RPC in a popup overlay. --- Cargo.lock | 271 ++++++++++++++++++++++++++++++++++- bin/ev-dev/Cargo.toml | 3 + bin/ev-dev/README.md | 40 +++++- bin/ev-dev/src/tui/app.rs | 141 +++++++++++++++++- bin/ev-dev/src/tui/events.rs | 24 +++- bin/ev-dev/src/tui/mod.rs | 1 + bin/ev-dev/src/tui/ui.rs | 195 ++++++++++++++++++++++--- 7 files changed, 647 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 587e1d9c..cd194bfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,6 +1039,26 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1730,6 +1750,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -1922,6 +1948,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "coins-bip32" version = "0.12.0" @@ -2685,6 +2720,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2872,6 +2917,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "ethereum_hashing" version = "0.7.0" @@ -2984,9 +3035,12 @@ dependencies = [ name = "ev-dev" version = "0.1.0" dependencies = [ + "alloy-network", "alloy-primitives", "alloy-provider", + "alloy-rpc-types", "alloy-signer-local", + "arboard", "clap", "crossterm", "ev-deployer", @@ -3315,6 +3369,35 @@ dependencies = [ "bytes", ] +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fdlimit" version = "0.3.0" @@ -3607,6 +3690,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -3753,6 +3846,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hash-db" version = "0.15.2" @@ -4215,6 +4319,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -5180,6 +5298,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "multiaddr" version = "0.18.2" @@ -5436,6 +5564,27 @@ dependencies = [ "smallvec", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-graphics", + "objc2-foundation", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -5443,6 +5592,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.11.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", ] [[package]] @@ -5455,6 +5636,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -6012,6 +6204,19 @@ dependencies = [ "crunchy", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polyval" version = "0.6.2" @@ -6221,6 +6426,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + [[package]] name = "quanta" version = "0.12.6" @@ -6242,6 +6453,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-protobuf" version = "0.8.1" @@ -9839,7 +10056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", - "quick-error", + "quick-error 1.2.3", "tempfile", "wait-timeout", ] @@ -10624,6 +10841,20 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error 2.0.1", + "weezl", + "zune-jpeg", +] + [[package]] name = "tikv-jemalloc-ctl" version = "0.6.1" @@ -11727,6 +11958,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -12393,6 +12630,23 @@ dependencies = [ "tap", ] +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xattr" version = "1.6.1" @@ -12559,3 +12813,18 @@ dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" +dependencies = [ + "zune-core", +] diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml index 57115c7b..db274636 100644 --- a/bin/ev-dev/Cargo.toml +++ b/bin/ev-dev/Cargo.toml @@ -26,6 +26,8 @@ reth-ethereum-cli.workspace = true alloy-signer-local.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true +alloy-rpc-types.workspace = true +alloy-network.workspace = true # Reth tracing (for Layers type) reth-tracing.workspace = true @@ -43,6 +45,7 @@ futures.workspace = true ratatui = "0.30" crossterm = { version = "0.29", features = ["event-stream"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] } +arboard = "3" [lints] workspace = true diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md index 39a615f2..23387662 100644 --- a/bin/ev-dev/README.md +++ b/bin/ev-dev/README.md @@ -2,15 +2,24 @@ One-command local development chain for Evolve. Think of it as the Evolve equivalent of [Hardhat Node](https://hardhat.org/hardhat-network/docs/overview) or [Anvil](https://book.getfoundry.sh/reference/anvil/). +## Installation + +```bash +# Install to ~/.cargo/bin +just install-ev-dev + +# Or build without installing +just build-ev-dev +``` + ## Quick Start ```bash # Build and run just dev-chain -# Or build separately -just build-ev-dev -./target/release/ev-dev +# Or run directly after installing +ev-dev ``` The chain starts immediately with 10 pre-funded accounts, each holding 1,000,000 ETH. @@ -28,6 +37,31 @@ ev-dev [OPTIONS] | `--block-time` | `1` | Block time in seconds (`0` = mine on transaction) | | `--silent` | `false` | Suppress the startup banner | | `--accounts` | `10` | Number of accounts to display (1-20) | +| `--deploy-config` | — | Path to an ev-deployer TOML config to deploy contracts at genesis | +| `--tui` | `false` | Launch with an interactive terminal UI instead of plain log output | + +### TUI Mode + +Pass `--tui` to launch an interactive terminal dashboard: + +```bash +ev-dev --tui +``` + +The TUI shows: + +- **Chain info** — chain ID, RPC URL, block time +- **Accounts** — addresses, private keys, and real-time balances (polled every 2s) +- **Deployed contracts** — when using `--deploy-config` +- **Logs** — live node logs with scrollback + +Keyboard shortcuts: + +| Key | Action | +|-----|--------| +| `Tab` | Cycle between panels | +| `↑` / `↓` | Scroll within the active panel | +| `q` / `Esc` / `Ctrl+C` | Quit | ### Examples diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs index 29490e91..b9c79547 100644 --- a/bin/ev-dev/src/tui/app.rs +++ b/bin/ev-dev/src/tui/app.rs @@ -23,6 +23,20 @@ pub(crate) struct LogEntry { pub(crate) timestamp: Instant, } +#[derive(Debug, Clone)] +pub(crate) struct TxInfo { + pub(crate) hash: String, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) value: String, +} + +#[derive(Debug, Clone)] +pub(crate) struct BlockDetail { + pub(crate) number: u64, + pub(crate) txs: Vec, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum Panel { Blocks, @@ -48,12 +62,17 @@ pub(crate) struct App { // UI state pub(crate) active_panel: Panel, pub(crate) log_scroll: usize, - pub(crate) block_scroll: usize, + pub(crate) block_selected: usize, + pub(crate) account_selected: usize, + pub(crate) clipboard_msg: Option<(String, Instant)>, + pub(crate) block_detail: Option, pub(crate) should_quit: bool, // Channels pub(crate) log_rx: mpsc::Receiver, pub(crate) balance_rx: mpsc::Receiver>, + pub(crate) detail_tx: mpsc::Sender, + pub(crate) detail_rx: mpsc::Receiver, } impl App { @@ -68,6 +87,7 @@ impl App { ) -> Self { let initial_balance = "1000000 ETH".to_string(); let balances = vec![initial_balance; accounts.len()]; + let (detail_tx, detail_rx) = mpsc::channel(4); Self { chain_id, rpc_url, @@ -81,10 +101,15 @@ impl App { balances, active_panel: Panel::Logs, log_scroll: 0, - block_scroll: 0, + block_selected: 0, + account_selected: 0, + clipboard_msg: None, + block_detail: None, should_quit: false, log_rx, balance_rx, + detail_tx, + detail_rx, } } @@ -155,18 +180,122 @@ impl App { pub(crate) fn scroll_up(&mut self) { match self.active_panel { Panel::Logs => self.log_scroll = self.log_scroll.saturating_add(1), - Panel::Blocks => self.block_scroll = self.block_scroll.saturating_add(1), - Panel::Accounts => {} + Panel::Blocks => { + self.block_selected = self.block_selected.saturating_sub(1); + } + Panel::Accounts => { + self.account_selected = self.account_selected.saturating_sub(1); + } } } pub(crate) fn scroll_down(&mut self) { match self.active_panel { Panel::Logs => self.log_scroll = self.log_scroll.saturating_sub(1), - Panel::Blocks => self.block_scroll = self.block_scroll.saturating_sub(1), - Panel::Accounts => {} + Panel::Blocks => { + if !self.blocks.is_empty() { + self.block_selected = + (self.block_selected + 1).min(self.blocks.len() - 1); + } + } + Panel::Accounts => { + if !self.accounts.is_empty() { + self.account_selected = + (self.account_selected + 1).min(self.accounts.len() - 1); + } + } + } + } + + pub(crate) fn copy_account_address(&mut self) { + if let Some((addr, _)) = self.accounts.get(self.account_selected) { + if let Ok(mut clipboard) = arboard::Clipboard::new() { + let _ = clipboard.set_text(addr.clone()); + let truncated = if addr.len() > 10 { + format!("{}..{}", &addr[..6], &addr[addr.len() - 4..]) + } else { + addr.clone() + }; + self.clipboard_msg = + Some((format!("Copied address {truncated}"), Instant::now())); + } + } + } + + pub(crate) fn copy_account_key(&mut self) { + if let Some((_, key)) = self.accounts.get(self.account_selected) { + if let Ok(mut clipboard) = arboard::Clipboard::new() { + let _ = clipboard.set_text(key.clone()); + self.clipboard_msg = + Some(("Copied private key".to_string(), Instant::now())); + } + } + } + + pub(crate) fn fetch_block_detail(&self) { + let Some(block_info) = self.blocks.get(self.block_selected) else { + return; + }; + let tx = self.detail_tx.clone(); + let rpc_url = self.rpc_url.clone(); + let block_num = block_info.number; + + tokio::spawn(async move { + use alloy_network::TransactionResponse; + use alloy_provider::{Provider, ProviderBuilder}; + use alloy_rpc_types::{BlockNumberOrTag, TransactionTrait}; + + let provider = + ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL")); + + let result = provider + .get_block_by_number(BlockNumberOrTag::Number(block_num)) + .full() + .await; + + let txs = match result { + Ok(Some(block)) => block + .transactions + .into_transactions() + .map(|t| { + let hash = format!("{}", t.tx_hash()); + let from = format!("{}", t.from()); + let to = t + .to() + .map_or("Contract Creation".into(), |a| truncate_hex(&format!("{a}"))); + let value = format_ether(t.value()); + TxInfo { + hash: truncate_hex(&hash), + from: truncate_hex(&from), + to, + value, + } + }) + .collect(), + _ => vec![], + }; + + let _ = tx.send(BlockDetail { number: block_num, txs }).await; + }); + } + + pub(crate) fn drain_block_detail(&mut self) { + if let Ok(detail) = self.detail_rx.try_recv() { + self.block_detail = Some(detail); } } + + pub(crate) fn close_block_detail(&mut self) { + self.block_detail = None; + } +} + +fn truncate_hex(s: &str) -> String { + if s.len() > 10 { + format!("{}..{}", &s[..6], &s[s.len() - 4..]) + } else { + s.to_string() + } } fn format_ether(wei: U256) -> String { diff --git a/bin/ev-dev/src/tui/events.rs b/bin/ev-dev/src/tui/events.rs index 5606ac8f..8e23ea9a 100644 --- a/bin/ev-dev/src/tui/events.rs +++ b/bin/ev-dev/src/tui/events.rs @@ -1,8 +1,21 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use super::app::App; +use super::app::{App, Panel}; pub(crate) fn handle_key(app: &mut App, key: KeyEvent) { + // If block detail overlay is open, handle it separately + if app.block_detail.is_some() { + match key.code { + KeyCode::Esc | KeyCode::Enter => app.close_block_detail(), + KeyCode::Char('q') => app.should_quit = true, + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + app.should_quit = true; + } + _ => {} + } + return; + } + match key.code { KeyCode::Char('q') | KeyCode::Esc => app.should_quit = true, KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { @@ -11,6 +24,15 @@ pub(crate) fn handle_key(app: &mut App, key: KeyEvent) { KeyCode::Tab => app.next_panel(), KeyCode::Up => app.scroll_up(), KeyCode::Down => app.scroll_down(), + KeyCode::Char('a') if app.active_panel == Panel::Accounts => { + app.copy_account_address(); + } + KeyCode::Char('k') if app.active_panel == Panel::Accounts => { + app.copy_account_key(); + } + KeyCode::Enter if app.active_panel == Panel::Blocks => { + app.fetch_block_detail(); + } _ => {} } } diff --git a/bin/ev-dev/src/tui/mod.rs b/bin/ev-dev/src/tui/mod.rs index 083eff37..10f09348 100644 --- a/bin/ev-dev/src/tui/mod.rs +++ b/bin/ev-dev/src/tui/mod.rs @@ -52,6 +52,7 @@ pub(crate) async fn run(mut app: App) -> eyre::Result<()> { _ = tick.tick() => { app.drain_logs(); app.drain_balances(); + app.drain_block_detail(); terminal.draw(|frame| ui::draw(frame, &app))?; } maybe_event = event_stream.next() => { diff --git a/bin/ev-dev/src/tui/ui.rs b/bin/ev-dev/src/tui/ui.rs index 3006f70f..211a0ba9 100644 --- a/bin/ev-dev/src/tui/ui.rs +++ b/bin/ev-dev/src/tui/ui.rs @@ -2,11 +2,11 @@ use ratatui::{ layout::{Constraint, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, - widgets::{Block, Borders, Cell, List, ListItem, Paragraph, Row, Table}, + widgets::{Block, Borders, Cell, Clear, List, ListItem, Paragraph, Row, Table}, Frame, }; -use super::app::{App, Panel}; +use super::app::{App, BlockDetail, Panel}; fn border_style(app: &App, panel: Panel) -> Style { if app.active_panel == panel { @@ -61,6 +61,10 @@ pub(crate) fn draw(frame: &mut Frame<'_>, app: &App) { draw_header(frame, app, outer[0]); draw_main(frame, app, outer[1]); draw_footer(frame, app, outer[2]); + + if let Some(ref detail) = app.block_detail { + draw_block_detail(frame, detail, area); + } } fn draw_header(frame: &mut Frame<'_>, app: &App, area: Rect) { @@ -112,6 +116,8 @@ fn draw_main(frame: &mut Frame<'_>, app: &App, area: Rect) { } fn draw_blocks(frame: &mut Frame<'_>, app: &App, area: Rect) { + let is_focused = app.active_panel == Panel::Blocks; + let block = Block::default() .borders(Borders::ALL) .title(" Blocks ") @@ -125,36 +131,53 @@ fn draw_blocks(frame: &mut Frame<'_>, app: &App, area: Rect) { ]) .style(Style::default().fg(Color::DarkGray)); + // Auto-scroll to keep selected block visible + let inner_height = area.height.saturating_sub(4) as usize; // borders + header + header separator + let scroll = if inner_height > 0 && app.block_selected >= inner_height { + app.block_selected - inner_height + 1 + } else { + 0 + }; + let rows: Vec> = app .blocks .iter() - .skip(app.block_scroll) - .map(|b| { + .enumerate() + .skip(scroll) + .take(inner_height.max(1)) + .map(|(i, b)| { + let selected = is_focused && i == app.block_selected; + let marker = if selected { "▸" } else { " " }; + let style = if selected { + Style::default().fg(Color::Cyan) + } else { + Style::default() + }; + Row::new(vec![ - Cell::from(format!("#{}", b.number)), + Cell::from(format!("{marker}#{}", b.number)), Cell::from(b.hash.clone()).style(Style::default().fg(Color::DarkGray)), Cell::from(format!("{}", b.tx_count)), Cell::from(format_gas(b.gas_used)), ]) + .style(style) }) .collect(); let widths = [ - Constraint::Length(8), + Constraint::Length(10), Constraint::Length(12), Constraint::Length(5), Constraint::Min(6), ]; - let table = Table::new(rows, widths) - .header(header) - .block(block) - .row_highlight_style(Style::default().fg(Color::Cyan)); + let table = Table::new(rows, widths).header(header).block(block); frame.render_widget(table, area); } fn draw_accounts(frame: &mut Frame<'_>, app: &App, area: Rect) { + let is_focused = app.active_panel == Panel::Accounts; let mut items: Vec> = app .accounts .iter() @@ -170,9 +193,15 @@ fn draw_accounts(frame: &mut Frame<'_>, app: &App, area: Rect) { .get(i) .cloned() .unwrap_or_else(|| "? ETH".to_string()); + + let selected = is_focused && i == app.account_selected; + let marker = if selected { "▸ " } else { " " }; + let addr_color = if selected { Color::Cyan } else { Color::White }; + ListItem::new(Line::from(vec![ + Span::styled(marker, Style::default().fg(Color::Cyan)), Span::styled(format!("({i}) "), Style::default().fg(Color::DarkGray)), - Span::styled(truncated, Style::default().fg(Color::White)), + Span::styled(truncated, Style::default().fg(addr_color)), Span::styled(format!(" {balance}"), Style::default().fg(Color::Green)), ])) }) @@ -262,10 +291,98 @@ fn draw_logs(frame: &mut Frame<'_>, app: &App, area: Rect) { frame.render_widget(list, area); } +fn draw_block_detail(frame: &mut Frame<'_>, detail: &BlockDetail, area: Rect) { + let popup = centered_rect(80, 60, area); + frame.render_widget(Clear, popup); + + let title = format!( + " Block #{} ({} txs) ", + detail.number, + detail.txs.len() + ); + + let block = Block::default() + .borders(Borders::ALL) + .title(title) + .title_style( + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ) + .border_style(Style::default().fg(Color::Cyan)); + + if detail.txs.is_empty() { + let text = Paragraph::new(Line::from(vec![ + Span::styled( + " No transactions in this block", + Style::default().fg(Color::DarkGray), + ), + ])) + .block(block); + frame.render_widget(text, popup); + } else { + let header = Row::new(vec![ + Cell::from("Hash").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("From").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("To").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("Value").style(Style::default().add_modifier(Modifier::BOLD)), + ]) + .style(Style::default().fg(Color::DarkGray)); + + let rows: Vec> = detail + .txs + .iter() + .map(|tx| { + Row::new(vec![ + Cell::from(tx.hash.clone()).style(Style::default().fg(Color::DarkGray)), + Cell::from(tx.from.clone()), + Cell::from(tx.to.clone()), + Cell::from(tx.value.clone()).style(Style::default().fg(Color::Green)), + ]) + }) + .collect(); + + let widths = [ + Constraint::Length(14), + Constraint::Length(14), + Constraint::Length(18), + Constraint::Min(10), + ]; + + let table = Table::new(rows, widths).header(header).block(block); + frame.render_widget(table, popup); + } +} + +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + let popup_layout = Layout::vertical([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + + Layout::horizontal([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] +} + fn draw_footer(frame: &mut Frame<'_>, app: &App, area: Rect) { let uptime = app.start_time.elapsed().as_secs(); - let text = Line::from(vec![ + // Check for clipboard flash message (show for 2 seconds) + let clipboard_flash = app.clipboard_msg.as_ref().and_then(|(msg, when)| { + if when.elapsed().as_secs() < 2 { + Some(msg.clone()) + } else { + None + } + }); + + let mut spans = vec![ Span::styled(" Up: ", Style::default().fg(Color::DarkGray)), Span::styled(format_uptime(uptime), Style::default().fg(Color::White)), Span::styled(" Block: ", Style::default().fg(Color::DarkGray)), @@ -278,11 +395,55 @@ fn draw_footer(frame: &mut Frame<'_>, app: &App, area: Rect) { Span::styled("uit ", Style::default().fg(Color::DarkGray)), Span::styled("[Tab]", Style::default().fg(Color::Yellow)), Span::styled("focus ", Style::default().fg(Color::DarkGray)), - Span::styled("[", Style::default().fg(Color::Yellow)), - Span::styled("Up/Down", Style::default().fg(Color::Yellow)), - Span::styled("]", Style::default().fg(Color::Yellow)), - Span::styled("scroll", Style::default().fg(Color::DarkGray)), - ]); + ]; + + if app.block_detail.is_some() { + spans.extend([ + Span::styled("[Esc]", Style::default().fg(Color::Yellow)), + Span::styled("close", Style::default().fg(Color::DarkGray)), + ]); + } else { + match app.active_panel { + Panel::Accounts => { + spans.extend([ + Span::styled("[↑↓]", Style::default().fg(Color::Yellow)), + Span::styled("select ", Style::default().fg(Color::DarkGray)), + Span::styled("[a]", Style::default().fg(Color::Yellow)), + Span::styled("ddress ", Style::default().fg(Color::DarkGray)), + Span::styled("[k]", Style::default().fg(Color::Yellow)), + Span::styled("ey", Style::default().fg(Color::DarkGray)), + ]); + } + Panel::Blocks => { + spans.extend([ + Span::styled("[↑↓]", Style::default().fg(Color::Yellow)), + Span::styled("select ", Style::default().fg(Color::DarkGray)), + Span::styled("[Enter]", Style::default().fg(Color::Yellow)), + Span::styled("txs", Style::default().fg(Color::DarkGray)), + ]); + } + Panel::Logs => { + spans.extend([ + Span::styled("[↑↓]", Style::default().fg(Color::Yellow)), + Span::styled("scroll", Style::default().fg(Color::DarkGray)), + ]); + } + } + } + + if let Some(msg) = clipboard_flash { + spans.extend([ + Span::styled(" ", Style::default()), + Span::styled( + format!("✓ {msg}"), + Style::default() + .fg(Color::Green) + .add_modifier(Modifier::BOLD), + ), + ]); + } + + let text = Line::from(spans); let block = Block::default() .borders(Borders::ALL) From b68f7f7537848ff79aca269cbc08577dd3fe25a0 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 10:50:30 +0100 Subject: [PATCH 25/81] style(ev-dev): fix rustfmt formatting in TUI module --- bin/ev-dev/src/tui/app.rs | 22 ++++++++++++---------- bin/ev-dev/src/tui/ui.rs | 16 +++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs index b9c79547..d55b389b 100644 --- a/bin/ev-dev/src/tui/app.rs +++ b/bin/ev-dev/src/tui/app.rs @@ -194,8 +194,7 @@ impl App { Panel::Logs => self.log_scroll = self.log_scroll.saturating_sub(1), Panel::Blocks => { if !self.blocks.is_empty() { - self.block_selected = - (self.block_selected + 1).min(self.blocks.len() - 1); + self.block_selected = (self.block_selected + 1).min(self.blocks.len() - 1); } } Panel::Accounts => { @@ -216,8 +215,7 @@ impl App { } else { addr.clone() }; - self.clipboard_msg = - Some((format!("Copied address {truncated}"), Instant::now())); + self.clipboard_msg = Some((format!("Copied address {truncated}"), Instant::now())); } } } @@ -226,8 +224,7 @@ impl App { if let Some((_, key)) = self.accounts.get(self.account_selected) { if let Ok(mut clipboard) = arboard::Clipboard::new() { let _ = clipboard.set_text(key.clone()); - self.clipboard_msg = - Some(("Copied private key".to_string(), Instant::now())); + self.clipboard_msg = Some(("Copied private key".to_string(), Instant::now())); } } } @@ -260,9 +257,9 @@ impl App { .map(|t| { let hash = format!("{}", t.tx_hash()); let from = format!("{}", t.from()); - let to = t - .to() - .map_or("Contract Creation".into(), |a| truncate_hex(&format!("{a}"))); + let to = t.to().map_or("Contract Creation".into(), |a| { + truncate_hex(&format!("{a}")) + }); let value = format_ether(t.value()); TxInfo { hash: truncate_hex(&hash), @@ -275,7 +272,12 @@ impl App { _ => vec![], }; - let _ = tx.send(BlockDetail { number: block_num, txs }).await; + let _ = tx + .send(BlockDetail { + number: block_num, + txs, + }) + .await; }); } diff --git a/bin/ev-dev/src/tui/ui.rs b/bin/ev-dev/src/tui/ui.rs index 211a0ba9..8bf4cff2 100644 --- a/bin/ev-dev/src/tui/ui.rs +++ b/bin/ev-dev/src/tui/ui.rs @@ -295,11 +295,7 @@ fn draw_block_detail(frame: &mut Frame<'_>, detail: &BlockDetail, area: Rect) { let popup = centered_rect(80, 60, area); frame.render_widget(Clear, popup); - let title = format!( - " Block #{} ({} txs) ", - detail.number, - detail.txs.len() - ); + let title = format!(" Block #{} ({} txs) ", detail.number, detail.txs.len()); let block = Block::default() .borders(Borders::ALL) @@ -312,12 +308,10 @@ fn draw_block_detail(frame: &mut Frame<'_>, detail: &BlockDetail, area: Rect) { .border_style(Style::default().fg(Color::Cyan)); if detail.txs.is_empty() { - let text = Paragraph::new(Line::from(vec![ - Span::styled( - " No transactions in this block", - Style::default().fg(Color::DarkGray), - ), - ])) + let text = Paragraph::new(Line::from(vec![Span::styled( + " No transactions in this block", + Style::default().fg(Color::DarkGray), + )])) .block(block); frame.render_widget(text, popup); } else { From 50302597b847f9258ed0bbe509cd953c91e9f6fd Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 10:56:06 +0100 Subject: [PATCH 26/81] fix(ev-dev): use map_or_else to satisfy clippy or_fun_call lint --- bin/ev-dev/src/tui/app.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/ev-dev/src/tui/app.rs b/bin/ev-dev/src/tui/app.rs index d55b389b..b09d8766 100644 --- a/bin/ev-dev/src/tui/app.rs +++ b/bin/ev-dev/src/tui/app.rs @@ -257,9 +257,10 @@ impl App { .map(|t| { let hash = format!("{}", t.tx_hash()); let from = format!("{}", t.from()); - let to = t.to().map_or("Contract Creation".into(), |a| { - truncate_hex(&format!("{a}")) - }); + let to = t.to().map_or_else( + || "Contract Creation".into(), + |a| truncate_hex(&format!("{a}")), + ); let value = format_ether(t.value()); TxInfo { hash: truncate_hex(&hash), From e8a39f8d90df936c6d6414d5872863db36f76779 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:03:15 +0100 Subject: [PATCH 27/81] refactor(ev-deployer): remove FeeVault contract from part 1 FeeVault will be added in a separate PR to keep this one focused on AdminProxy only. --- bin/ev-deployer/README.md | 31 +--- bin/ev-deployer/examples/devnet.toml | 11 -- bin/ev-deployer/src/config.rs | 99 +---------- bin/ev-deployer/src/contracts/fee_vault.rs | 184 --------------------- bin/ev-deployer/src/contracts/mod.rs | 1 - bin/ev-deployer/src/genesis.rs | 6 - bin/ev-deployer/src/main.rs | 6 - bin/ev-deployer/src/output.rs | 7 - bin/ev-deployer/tests/e2e_genesis.sh | 41 +---- 9 files changed, 4 insertions(+), 382 deletions(-) delete mode 100644 bin/ev-deployer/src/contracts/fee_vault.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 58af3a3e..bf695602 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -21,21 +21,8 @@ chain_id = 1234 [contracts.admin_proxy] address = "0x000000000000000000000000000000000000Ad00" owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -destination_domain = 0 -recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -minimum_amount = 0 -call_fee = 0 -bridge_share_bps = 10000 -other_recipient = "0x0000000000000000000000000000000000000000" -hyp_native_minter = "0x0000000000000000000000000000000000000000" ``` -Both contracts are optional — include only the sections you need. - ### Config reference #### `[chain]` @@ -51,20 +38,6 @@ Both contracts are optional — include only the sections you need. | `address` | address | Address to deploy at | | `owner` | address | Owner (must not be zero) | -#### `[contracts.fee_vault]` - -| Field | Type | Default | Description | -|----------------------|---------|---------|------------------------------------------------| -| `address` | address | — | Address to deploy at | -| `owner` | address | — | Owner (must not be zero) | -| `destination_domain` | u32 | 0 | Hyperlane destination domain | -| `recipient_address` | bytes32 | 0x0…0 | Hyperlane recipient | -| `minimum_amount` | u64 | 0 | Minimum amount for bridging | -| `call_fee` | u64 | 0 | Fee for sendToCelestia | -| `bridge_share_bps` | u64 | 0 | Bridge share in basis points (0–10000). 0 maps to 10000 | -| `other_recipient` | address | 0x0…0 | Split accounting recipient | -| `hyp_native_minter` | address | 0x0…0 | HypNativeMinter address | - ## Usage ### Generate genesis alloc @@ -107,8 +80,7 @@ Output: ```json { - "admin_proxy": "0x000000000000000000000000000000000000Ad00", - "fee_vault": "0x000000000000000000000000000000000000FE00" + "admin_proxy": "0x000000000000000000000000000000000000Ad00" } ``` @@ -123,7 +95,6 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy | Contract | Description | |----------------|-----------------------------------------------------| | `admin_proxy` | Proxy contract with owner-based access control | -| `fee_vault` | Fee vault with Hyperlane bridge integration | Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index f332b1ad..c0201807 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -4,14 +4,3 @@ chain_id = 1234 [contracts.admin_proxy] address = "0x000000000000000000000000000000000000Ad00" owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -destination_domain = 0 -recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -minimum_amount = 0 -call_fee = 0 -bridge_share_bps = 10000 -other_recipient = "0x0000000000000000000000000000000000000000" -hyp_native_minter = "0x0000000000000000000000000000000000000000" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 66fb1c24..4eb8c48f 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,6 +1,6 @@ //! TOML config types, parsing, and validation. -use alloy_primitives::{Address, B256}; +use alloy_primitives::Address; use serde::Deserialize; use std::path::Path; @@ -27,8 +27,6 @@ pub(crate) struct ChainConfig { pub(crate) struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, - /// `FeeVault` contract config (optional). - pub fee_vault: Option, } /// `AdminProxy` configuration. @@ -40,36 +38,6 @@ pub(crate) struct AdminProxyConfig { pub owner: Address, } -/// `FeeVault` configuration. -#[derive(Debug, Deserialize)] -pub(crate) struct FeeVaultConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - pub owner: Address, - /// Hyperlane destination domain. - #[serde(default)] - pub destination_domain: u32, - /// Hyperlane recipient address (bytes32). - #[serde(default)] - pub recipient_address: B256, - /// Minimum amount for bridging. - #[serde(default)] - pub minimum_amount: u64, - /// Call fee for sendToCelestia. - #[serde(default)] - pub call_fee: u64, - /// Basis points for bridge share (0-10000). 0 defaults to 10000. - #[serde(default)] - pub bridge_share_bps: u64, - /// Other recipient for split accounting. - #[serde(default)] - pub other_recipient: Address, - /// `HypNativeMinter` address. - #[serde(default)] - pub hyp_native_minter: Address, -} - impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -88,25 +56,6 @@ impl DeployConfig { ); } - if let Some(ref fv) = self.contracts.fee_vault { - eyre::ensure!( - !fv.owner.is_zero(), - "fee_vault.owner must not be the zero address" - ); - eyre::ensure!( - fv.bridge_share_bps <= 10000, - "fee_vault.bridge_share_bps must be 0-10000, got {}", - fv.bridge_share_bps - ); - } - - if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) { - eyre::ensure!( - ap.address != fv.address, - "contracts.admin_proxy.address and contracts.fee_vault.address must be distinct" - ); - } - Ok(()) } } @@ -124,22 +73,10 @@ chain_id = 1234 [contracts.admin_proxy] address = "0x000000000000000000000000000000000000Ad00" owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -destination_domain = 0 -recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -minimum_amount = 0 -call_fee = 0 -bridge_share_bps = 10000 -other_recipient = "0x0000000000000000000000000000000000000000" -hyp_native_minter = "0x0000000000000000000000000000000000000000" "#; let config: DeployConfig = toml::from_str(toml).unwrap(); assert_eq!(config.chain.chain_id, 1234); assert!(config.contracts.admin_proxy.is_some()); - assert!(config.contracts.fee_vault.is_some()); config.validate().unwrap(); } @@ -157,39 +94,6 @@ owner = "0x0000000000000000000000000000000000000000" assert!(config.validate().is_err()); } - #[test] - fn reject_bps_over_10000() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -bridge_share_bps = 10001 -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - - #[test] - fn reject_duplicate_addresses() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - #[test] fn admin_proxy_only() { let toml = r#" @@ -203,6 +107,5 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" let config: DeployConfig = toml::from_str(toml).unwrap(); config.validate().unwrap(); assert!(config.contracts.admin_proxy.is_some()); - assert!(config.contracts.fee_vault.is_none()); } } diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs deleted file mode 100644 index 445ea8c1..00000000 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! `FeeVault` bytecode and storage encoding. - -use crate::{config::FeeVaultConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). -/// Regenerate with: `cd contracts && forge inspect FeeVault deployedBytecode` -const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); - -/// Build a genesis alloc entry for `FeeVault`. -pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { - let mut storage = BTreeMap::new(); - - // Apply constructor default: bps 0 -> 10000 - let effective_bps = if config.bridge_share_bps == 0 { - 10000 - } else { - config.bridge_share_bps - }; - - // Slot 0: hypNativeMinter (address) - storage.insert( - B256::ZERO, - B256::from(U256::from_be_bytes(config.hyp_native_minter.into_word().0)), - ); - - // Slot 1: owner (lower 160 bits) + destinationDomain (shifted left 160 bits) - let owner_u256 = U256::from_be_bytes(config.owner.into_word().0); - let domain_u256 = U256::from(config.destination_domain) << 160; - storage.insert( - B256::with_last_byte(1), - B256::from(owner_u256 | domain_u256), - ); - - // Slot 2: recipientAddress (bytes32) - storage.insert(B256::with_last_byte(2), config.recipient_address); - - // Slot 3: minimumAmount - storage.insert( - B256::with_last_byte(3), - B256::from(U256::from(config.minimum_amount)), - ); - - // Slot 4: callFee - storage.insert( - B256::with_last_byte(4), - B256::from(U256::from(config.call_fee)), - ); - - // Slot 5: otherRecipient (address) - storage.insert( - B256::with_last_byte(5), - B256::from(U256::from_be_bytes(config.other_recipient.into_word().0)), - ); - - // Slot 6: bridgeShareBps - storage.insert( - B256::with_last_byte(6), - B256::from(U256::from(effective_bps)), - ); - - GenesisContract { - address: config.address, - code: Bytes::from_static(FEE_VAULT_BYTECODE), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, Address}; - use std::{path::PathBuf, process::Command}; - - #[test] - fn fee_vault_storage_encoding() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // Slot 0: hypNativeMinter = zero - assert_eq!(contract.storage[&B256::ZERO], B256::ZERO); - - // Slot 1: owner packed with domain - let expected_slot1: B256 = - "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected_slot1); - - // Slot 6: bridgeShareBps = 10000 - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn bps_zero_defaults_to_10000() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 0, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn slot1_packing_with_nonzero_domain() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - destination_domain: 42, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // slot1 = (42 << 160) | owner - let owner_u256 = U256::from_be_bytes( - address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266") - .into_word() - .0, - ); - let expected = B256::from((U256::from(42u32) << 160) | owner_u256); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); - } - - #[test] - #[ignore = "requires forge CLI"] - fn fee_vault_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts"); - - let output = Command::new("forge") - .args(["inspect", "FeeVault", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .trim_start_matches("0x") - .to_lowercase(); - - let hardcoded_hex = hex::encode(FEE_VAULT_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "FeeVault bytecode mismatch! Update the constant with: cd contracts && forge inspect FeeVault deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 8ef01558..569e4510 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,7 +1,6 @@ //! Contract bytecode and storage encoding. pub(crate) mod admin_proxy; -pub(crate) mod fee_vault; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 9b200769..fa3c1445 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -17,11 +17,6 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } - if let Some(ref fv_config) = config.contracts.fee_vault { - let contract = contracts::fee_vault::build(fv_config); - insert_contract(&mut alloc, &contract); - } - Value::Object(alloc) } @@ -97,7 +92,6 @@ mod tests { address: address!("000000000000000000000000000000000000Ad00"), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), - fee_vault: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 42ad6a4a..ce93568f 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -103,12 +103,6 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, - "fee_vault" => cfg - .contracts - .fee_vault - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("fee_vault not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 22bf063c..b30e373c 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -14,12 +14,5 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - if let Some(ref fv) = config.contracts.fee_vault { - manifest.insert( - "fee_vault".to_string(), - Value::String(format!("{}", fv.address)), - ); - } - Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 3b7783b4..c1bee05d 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -76,13 +76,11 @@ echo "=== Generating genesis with ev-deployer ===" echo "Genesis written to $GENESIS" -# Quick sanity: addresses should be in the alloc +# Quick sanity: address should be in the alloc grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" -grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ - || fail "FeeVault address not found in genesis" -pass "genesis contains both contract addresses" +pass "genesis contains AdminProxy address" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -127,41 +125,6 @@ expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cff || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" pass "AdminProxy owner slot 0 = $ADMIN_OWNER" -# ── Step 5: Verify FeeVault ────────────────────────────── - -FEE_VAULT="0x000000000000000000000000000000000000FE00" -FEE_VAULT_OWNER="0x000000000000000000000000000000000000Ad00" - -echo "=== Verifying FeeVault at $FEE_VAULT ===" - -# Check code is present -fv_code=$(rpc_call "eth_getCode" "[\"$FEE_VAULT\", \"latest\"]") -[[ "$fv_code" != "0x" && "$fv_code" != "0x0" && ${#fv_code} -gt 10 ]] \ - || fail "FeeVault has no bytecode (got: $fv_code)" -pass "FeeVault has bytecode (${#fv_code} hex chars)" - -# Slot 0: hypNativeMinter (should be zero) -fv_slot0=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x0\", \"latest\"]") -expected_zero="0x0000000000000000000000000000000000000000000000000000000000000000" -[[ "$(echo "$fv_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_zero" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 0 (hypNativeMinter) should be zero, got $fv_slot0" -pass "FeeVault slot 0 (hypNativeMinter) = zero" - -# Slot 1: owner (lower 160 bits) + destinationDomain (upper bits) -# With domain=0 and owner=0x...Ad00, it's just the owner padded -fv_slot1=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x1\", \"latest\"]") -expected_slot1="0x000000000000000000000000000000000000000000000000000000000000ad00" -[[ "$(echo "$fv_slot1" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot1" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 1 (owner|domain) mismatch: got $fv_slot1, expected $expected_slot1" -pass "FeeVault slot 1 (owner|domain) correct" - -# Slot 6: bridgeShareBps = 10000 = 0x2710 -fv_slot6=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x6\", \"latest\"]") -expected_slot6="0x0000000000000000000000000000000000000000000000000000000000002710" -[[ "$(echo "$fv_slot6" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot6" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" -pass "FeeVault slot 6 (bridgeShareBps) = 10000" - # ── Done ───────────────────────────────────────────────── echo "" From 089ef220b7dd9bfba11cecd07ce9aa8257080b04 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:32:14 +0100 Subject: [PATCH 28/81] refactor(ev-deployer): remove AdminProxy contract from part 1 Keep only the CLI framework (config, genesis merge, manifest output) without any contract implementations. Contracts will be added in subsequent PRs. --- bin/ev-deployer/README.md | 33 +------- bin/ev-deployer/examples/devnet.toml | 4 +- bin/ev-deployer/src/config.rs | 58 +------------- bin/ev-deployer/src/contracts/admin_proxy.rs | 81 -------------------- bin/ev-deployer/src/contracts/mod.rs | 2 - bin/ev-deployer/src/genesis.rs | 80 ++++--------------- bin/ev-deployer/src/main.rs | 18 +---- bin/ev-deployer/src/output.rs | 12 +-- bin/ev-deployer/tests/e2e_genesis.sh | 33 +++----- 9 files changed, 32 insertions(+), 289 deletions(-) delete mode 100644 bin/ev-deployer/src/contracts/admin_proxy.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index bf695602..3fbf7716 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -18,9 +18,7 @@ EV Deployer uses a TOML config file to define what contracts to include and how [chain] chain_id = 1234 -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +[contracts] ``` ### Config reference @@ -31,13 +29,6 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" |------------|------|-------------| | `chain_id` | u64 | Chain ID | -#### `[contracts.admin_proxy]` - -| Field | Type | Description | -|-----------|---------|---------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner (must not be zero) | - ## Usage ### Generate genesis alloc @@ -76,28 +67,6 @@ Write a JSON mapping of contract names to their configured addresses: ev-deployer genesis --config deploy.toml --addresses-out addresses.json ``` -Output: - -```json -{ - "admin_proxy": "0x000000000000000000000000000000000000Ad00" -} -``` - -### Look up a contract address - -```bash -ev-deployer compute-address --config deploy.toml --contract admin_proxy -``` - -## Contracts - -| Contract | Description | -|----------------|-----------------------------------------------------| -| `admin_proxy` | Proxy contract with owner-based access control | - -Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. - ## Testing ```bash diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index c0201807..66cdb6f1 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -1,6 +1,4 @@ [chain] chain_id = 1234 -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +[contracts] diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 4eb8c48f..ecac4e50 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,6 +1,5 @@ //! TOML config types, parsing, and validation. -use alloy_primitives::Address; use serde::Deserialize; use std::path::Path; @@ -23,20 +22,8 @@ pub(crate) struct ChainConfig { } /// All contract configurations. -#[derive(Debug, Deserialize)] -pub(crate) struct ContractsConfig { - /// `AdminProxy` contract config (optional). - pub admin_proxy: Option, -} - -/// `AdminProxy` configuration. -#[derive(Debug, Deserialize)] -pub(crate) struct AdminProxyConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - pub owner: Address, -} +#[derive(Debug, Deserialize, Default)] +pub(crate) struct ContractsConfig {} impl DeployConfig { /// Load and validate config from a TOML file. @@ -49,13 +36,6 @@ impl DeployConfig { /// Validate config values. fn validate(&self) -> eyre::Result<()> { - if let Some(ref ap) = self.contracts.admin_proxy { - eyre::ensure!( - !ap.owner.is_zero(), - "admin_proxy.owner must not be the zero address" - ); - } - Ok(()) } } @@ -70,42 +50,10 @@ mod tests { [chain] chain_id = 1234 -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +[contracts] "#; let config: DeployConfig = toml::from_str(toml).unwrap(); assert_eq!(config.chain.chain_id, 1234); - assert!(config.contracts.admin_proxy.is_some()); - config.validate().unwrap(); - } - - #[test] - fn reject_zero_owner() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0x0000000000000000000000000000000000000000" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - - #[test] - fn admin_proxy_only() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); config.validate().unwrap(); - assert!(config.contracts.admin_proxy.is_some()); } } diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs deleted file mode 100644 index ed187b12..00000000 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! `AdminProxy` bytecode and storage encoding. - -use crate::{config::AdminProxyConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). -/// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` -const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); - -/// Build a genesis alloc entry for `AdminProxy`. -pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { - let mut storage = BTreeMap::new(); - - // Slot 0: owner (address left-padded to 32 bytes) - let owner_value = B256::from(U256::from_be_bytes(config.owner.into_word().0)); - storage.insert(B256::ZERO, owner_value); - - GenesisContract { - address: config.address, - code: Bytes::from_static(ADMIN_PROXY_BYTECODE), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::address; - use std::{path::PathBuf, process::Command}; - - #[test] - fn golden_admin_proxy_storage() { - let config = AdminProxyConfig { - address: address!("000000000000000000000000000000000000Ad00"), - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - }; - let contract = build(&config); - - let expected_slot0: B256 = - "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::ZERO], expected_slot0); - } - - #[test] - #[ignore = "requires forge CLI"] - fn admin_proxy_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts"); - - let output = Command::new("forge") - .args(["inspect", "AdminProxy", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .trim_start_matches("0x") - .to_lowercase(); - - let hardcoded_hex = hex::encode(ADMIN_PROXY_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "AdminProxy bytecode mismatch! Update the constant with: cd contracts && forge inspect AdminProxy deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 569e4510..c24ffb0c 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,7 +1,5 @@ //! Contract bytecode and storage encoding. -pub(crate) mod admin_proxy; - use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fa3c1445..d8786cb5 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -1,22 +1,13 @@ //! Genesis alloc JSON builder. -use crate::{ - config::DeployConfig, - contracts::{self, GenesisContract}, -}; +use crate::{config::DeployConfig, contracts::GenesisContract}; use alloy_primitives::B256; use serde_json::{Map, Value}; use std::path::Path; /// Build the alloc JSON from config. -pub(crate) fn build_alloc(config: &DeployConfig) -> Value { - let mut alloc = Map::new(); - - if let Some(ref ap_config) = config.contracts.admin_proxy { - let contract = contracts::admin_proxy::build(ap_config); - insert_contract(&mut alloc, &contract); - } - +pub(crate) fn build_alloc(_config: &DeployConfig) -> Value { + let alloc = Map::new(); Value::Object(alloc) } @@ -47,6 +38,7 @@ pub(crate) fn merge_into( Ok(genesis) } +#[allow(dead_code)] fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { // Address key without 0x prefix, using checksummed format let addr_hex = format!("{}", contract.address); @@ -74,6 +66,7 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { /// Format a storage slot key as a full 32-byte hex string. /// `B256::ZERO` -> "0x0000000000000000000000000000000000000000000000000000000000000000" +#[allow(dead_code)] fn format_slot_key(slot: &B256) -> String { format!("{slot}") } @@ -82,53 +75,19 @@ fn format_slot_key(slot: &B256) -> String { mod tests { use super::*; use crate::config::*; - use alloy_primitives::address; fn test_config() -> DeployConfig { DeployConfig { chain: ChainConfig { chain_id: 1234 }, - contracts: ContractsConfig { - admin_proxy: Some(AdminProxyConfig { - address: address!("000000000000000000000000000000000000Ad00"), - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - }), - }, + contracts: ContractsConfig {}, } } #[test] - fn alloc_json_structure() { + fn empty_alloc() { let alloc = build_alloc(&test_config()); let obj = alloc.as_object().unwrap(); - assert!(obj.contains_key("000000000000000000000000000000000000Ad00")); - - let entry = obj - .get("000000000000000000000000000000000000Ad00") - .unwrap() - .as_object() - .unwrap(); - assert_eq!(entry["balance"], "0x0"); - assert!(entry["code"].as_str().unwrap().starts_with("0x")); - assert!(entry.contains_key("storage")); - } - - #[test] - fn alloc_golden_value() { - let alloc = build_alloc(&test_config()); - let storage = alloc - .as_object() - .unwrap() - .get("000000000000000000000000000000000000Ad00") - .unwrap() - .get("storage") - .unwrap() - .as_object() - .unwrap(); - - assert_eq!( - storage["0x0000000000000000000000000000000000000000000000000000000000000000"], - "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" - ); + assert!(obj.is_empty()); } #[test] @@ -148,26 +107,13 @@ mod tests { } #[test] - fn merge_detects_collision() { - let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; - let tmp = tempfile::NamedTempFile::new().unwrap(); - std::fs::write(tmp.path(), genesis).unwrap(); - - let result = merge_into(&test_config(), tmp.path(), false); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("address collision")); - } - - #[test] - fn merge_force_overwrites() { - let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + fn merge_into_existing_genesis() { + let genesis = r#"{"alloc":{"deadbeef":{"balance":"0x1"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); - let result = merge_into(&test_config(), tmp.path(), true); - assert!(result.is_ok()); + let result = merge_into(&test_config(), tmp.path(), false).unwrap(); + let alloc = result.get("alloc").unwrap().as_object().unwrap(); + assert!(alloc.contains_key("deadbeef")); } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index ce93568f..fdf836b5 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -49,7 +49,7 @@ enum Command { #[arg(long)] config: PathBuf, - /// Contract name (`admin_proxy` or `fee_vault`). + /// Contract name. #[arg(long)] contract: String, }, @@ -91,22 +91,10 @@ fn main() -> eyre::Result<()> { } } Command::ComputeAddress { - config: config_path, + config: _config_path, contract, } => { - let cfg = config::DeployConfig::load(&config_path)?; - - let address = match contract.as_str() { - "admin_proxy" => cfg - .contracts - .admin_proxy - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, - other => eyre::bail!("unknown contract: {other}"), - }; - - println!("{address}"); + eyre::bail!("unknown contract: {contract}"); } } diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index b30e373c..df4817e7 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -4,15 +4,7 @@ use crate::config::DeployConfig; use serde_json::{Map, Value}; /// Build an address manifest JSON from config. -pub(crate) fn build_manifest(config: &DeployConfig) -> Value { - let mut manifest = Map::new(); - - if let Some(ref ap) = config.contracts.admin_proxy { - manifest.insert( - "admin_proxy".to_string(), - Value::String(format!("{}", ap.address)), - ); - } - +pub(crate) fn build_manifest(_config: &DeployConfig) -> Value { + let manifest = Map::new(); Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index c1bee05d..e690aad1 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# End-to-end test: generate genesis with ev-deployer, boot ev-reth, verify contracts via RPC. +# End-to-end test: generate genesis with ev-deployer, boot ev-reth, verify merge works via RPC. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" @@ -76,11 +76,11 @@ echo "=== Generating genesis with ev-deployer ===" echo "Genesis written to $GENESIS" -# Quick sanity: address should be in the alloc -grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ - || fail "AdminProxy address not found in genesis" +# Sanity: output should be valid JSON with alloc field +python3 -c "import sys,json; g=json.load(open('$GENESIS')); assert 'alloc' in g" \ + || fail "genesis output is not valid JSON or missing alloc" -pass "genesis contains AdminProxy address" +pass "genesis merge produced valid JSON with alloc" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -104,26 +104,11 @@ echo "Node PID: $NODE_PID, waiting for RPC..." wait_for_rpc pass "node is up and responding to RPC" -# ── Step 4: Verify AdminProxy ──────────────────────────── +# ── Step 4: Verify node boots with merged genesis ──────── -ADMIN_PROXY="0x000000000000000000000000000000000000Ad00" -ADMIN_OWNER="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -echo "=== Verifying AdminProxy at $ADMIN_PROXY ===" - -# Check code is present -admin_code=$(rpc_call "eth_getCode" "[\"$ADMIN_PROXY\", \"latest\"]") -[[ "$admin_code" != "0x" && "$admin_code" != "0x0" && ${#admin_code} -gt 10 ]] \ - || fail "AdminProxy has no bytecode (got: $admin_code)" -pass "AdminProxy has bytecode (${#admin_code} hex chars)" - -# Check owner in slot 0 -admin_slot0=$(rpc_call "eth_getStorageAt" "[\"$ADMIN_PROXY\", \"0x0\", \"latest\"]") -# Owner should be in the lower 20 bytes, left-padded to 32 bytes -expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" -[[ "$(echo "$admin_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner_slot" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" -pass "AdminProxy owner slot 0 = $ADMIN_OWNER" +block_number=$(rpc_call "eth_blockNumber" "[]") +[[ -n "$block_number" ]] || fail "could not get block number from node" +pass "node booted successfully with merged genesis (block: $block_number)" # ── Done ───────────────────────────────────────────────── From 6b85563d6f30101792e6054abec18e947fde8310 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:35:27 +0100 Subject: [PATCH 29/81] Revert "refactor(ev-deployer): remove AdminProxy contract from part 1" This reverts commit 089ef220b7dd9bfba11cecd07ce9aa8257080b04. --- bin/ev-deployer/README.md | 33 +++++++- bin/ev-deployer/examples/devnet.toml | 4 +- bin/ev-deployer/src/config.rs | 58 +++++++++++++- bin/ev-deployer/src/contracts/admin_proxy.rs | 81 ++++++++++++++++++++ bin/ev-deployer/src/contracts/mod.rs | 2 + bin/ev-deployer/src/genesis.rs | 80 +++++++++++++++---- bin/ev-deployer/src/main.rs | 18 ++++- bin/ev-deployer/src/output.rs | 12 ++- bin/ev-deployer/tests/e2e_genesis.sh | 33 +++++--- 9 files changed, 289 insertions(+), 32 deletions(-) create mode 100644 bin/ev-deployer/src/contracts/admin_proxy.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 3fbf7716..bf695602 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -18,7 +18,9 @@ EV Deployer uses a TOML config file to define what contracts to include and how [chain] chain_id = 1234 -[contracts] +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" ``` ### Config reference @@ -29,6 +31,13 @@ chain_id = 1234 |------------|------|-------------| | `chain_id` | u64 | Chain ID | +#### `[contracts.admin_proxy]` + +| Field | Type | Description | +|-----------|---------|---------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner (must not be zero) | + ## Usage ### Generate genesis alloc @@ -67,6 +76,28 @@ Write a JSON mapping of contract names to their configured addresses: ev-deployer genesis --config deploy.toml --addresses-out addresses.json ``` +Output: + +```json +{ + "admin_proxy": "0x000000000000000000000000000000000000Ad00" +} +``` + +### Look up a contract address + +```bash +ev-deployer compute-address --config deploy.toml --contract admin_proxy +``` + +## Contracts + +| Contract | Description | +|----------------|-----------------------------------------------------| +| `admin_proxy` | Proxy contract with owner-based access control | + +Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. + ## Testing ```bash diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 66cdb6f1..c0201807 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -1,4 +1,6 @@ [chain] chain_id = 1234 -[contracts] +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index ecac4e50..4eb8c48f 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,5 +1,6 @@ //! TOML config types, parsing, and validation. +use alloy_primitives::Address; use serde::Deserialize; use std::path::Path; @@ -22,8 +23,20 @@ pub(crate) struct ChainConfig { } /// All contract configurations. -#[derive(Debug, Deserialize, Default)] -pub(crate) struct ContractsConfig {} +#[derive(Debug, Deserialize)] +pub(crate) struct ContractsConfig { + /// `AdminProxy` contract config (optional). + pub admin_proxy: Option, +} + +/// `AdminProxy` configuration. +#[derive(Debug, Deserialize)] +pub(crate) struct AdminProxyConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + pub owner: Address, +} impl DeployConfig { /// Load and validate config from a TOML file. @@ -36,6 +49,13 @@ impl DeployConfig { /// Validate config values. fn validate(&self) -> eyre::Result<()> { + if let Some(ref ap) = self.contracts.admin_proxy { + eyre::ensure!( + !ap.owner.is_zero(), + "admin_proxy.owner must not be the zero address" + ); + } + Ok(()) } } @@ -50,10 +70,42 @@ mod tests { [chain] chain_id = 1234 -[contracts] +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" "#; let config: DeployConfig = toml::from_str(toml).unwrap(); assert_eq!(config.chain.chain_id, 1234); + assert!(config.contracts.admin_proxy.is_some()); + config.validate().unwrap(); + } + + #[test] + fn reject_zero_owner() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + + #[test] + fn admin_proxy_only() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); config.validate().unwrap(); + assert!(config.contracts.admin_proxy.is_some()); } } diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs new file mode 100644 index 00000000..ed187b12 --- /dev/null +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -0,0 +1,81 @@ +//! `AdminProxy` bytecode and storage encoding. + +use crate::{config::AdminProxyConfig, contracts::GenesisContract}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). +/// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` +const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); + +/// Build a genesis alloc entry for `AdminProxy`. +pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { + let mut storage = BTreeMap::new(); + + // Slot 0: owner (address left-padded to 32 bytes) + let owner_value = B256::from(U256::from_be_bytes(config.owner.into_word().0)); + storage.insert(B256::ZERO, owner_value); + + GenesisContract { + address: config.address, + code: Bytes::from_static(ADMIN_PROXY_BYTECODE), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + use std::{path::PathBuf, process::Command}; + + #[test] + fn golden_admin_proxy_storage() { + let config = AdminProxyConfig { + address: address!("000000000000000000000000000000000000Ad00"), + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }; + let contract = build(&config); + + let expected_slot0: B256 = + "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + .parse() + .unwrap(); + assert_eq!(contract.storage[&B256::ZERO], expected_slot0); + } + + #[test] + #[ignore = "requires forge CLI"] + fn admin_proxy_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts"); + + let output = Command::new("forge") + .args(["inspect", "AdminProxy", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .trim_start_matches("0x") + .to_lowercase(); + + let hardcoded_hex = hex::encode(ADMIN_PROXY_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "AdminProxy bytecode mismatch! Update the constant with: cd contracts && forge inspect AdminProxy deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c24ffb0c..569e4510 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,5 +1,7 @@ //! Contract bytecode and storage encoding. +pub(crate) mod admin_proxy; + use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index d8786cb5..fa3c1445 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -1,13 +1,22 @@ //! Genesis alloc JSON builder. -use crate::{config::DeployConfig, contracts::GenesisContract}; +use crate::{ + config::DeployConfig, + contracts::{self, GenesisContract}, +}; use alloy_primitives::B256; use serde_json::{Map, Value}; use std::path::Path; /// Build the alloc JSON from config. -pub(crate) fn build_alloc(_config: &DeployConfig) -> Value { - let alloc = Map::new(); +pub(crate) fn build_alloc(config: &DeployConfig) -> Value { + let mut alloc = Map::new(); + + if let Some(ref ap_config) = config.contracts.admin_proxy { + let contract = contracts::admin_proxy::build(ap_config); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -38,7 +47,6 @@ pub(crate) fn merge_into( Ok(genesis) } -#[allow(dead_code)] fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { // Address key without 0x prefix, using checksummed format let addr_hex = format!("{}", contract.address); @@ -66,7 +74,6 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { /// Format a storage slot key as a full 32-byte hex string. /// `B256::ZERO` -> "0x0000000000000000000000000000000000000000000000000000000000000000" -#[allow(dead_code)] fn format_slot_key(slot: &B256) -> String { format!("{slot}") } @@ -75,19 +82,53 @@ fn format_slot_key(slot: &B256) -> String { mod tests { use super::*; use crate::config::*; + use alloy_primitives::address; fn test_config() -> DeployConfig { DeployConfig { chain: ChainConfig { chain_id: 1234 }, - contracts: ContractsConfig {}, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: address!("000000000000000000000000000000000000Ad00"), + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + }, } } #[test] - fn empty_alloc() { + fn alloc_json_structure() { let alloc = build_alloc(&test_config()); let obj = alloc.as_object().unwrap(); - assert!(obj.is_empty()); + assert!(obj.contains_key("000000000000000000000000000000000000Ad00")); + + let entry = obj + .get("000000000000000000000000000000000000Ad00") + .unwrap() + .as_object() + .unwrap(); + assert_eq!(entry["balance"], "0x0"); + assert!(entry["code"].as_str().unwrap().starts_with("0x")); + assert!(entry.contains_key("storage")); + } + + #[test] + fn alloc_golden_value() { + let alloc = build_alloc(&test_config()); + let storage = alloc + .as_object() + .unwrap() + .get("000000000000000000000000000000000000Ad00") + .unwrap() + .get("storage") + .unwrap() + .as_object() + .unwrap(); + + assert_eq!( + storage["0x0000000000000000000000000000000000000000000000000000000000000000"], + "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ); } #[test] @@ -107,13 +148,26 @@ mod tests { } #[test] - fn merge_into_existing_genesis() { - let genesis = r#"{"alloc":{"deadbeef":{"balance":"0x1"}}}"#; + fn merge_detects_collision() { + let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + let tmp = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(tmp.path(), genesis).unwrap(); + + let result = merge_into(&test_config(), tmp.path(), false); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("address collision")); + } + + #[test] + fn merge_force_overwrites() { + let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); - let result = merge_into(&test_config(), tmp.path(), false).unwrap(); - let alloc = result.get("alloc").unwrap().as_object().unwrap(); - assert!(alloc.contains_key("deadbeef")); + let result = merge_into(&test_config(), tmp.path(), true); + assert!(result.is_ok()); } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index fdf836b5..ce93568f 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -49,7 +49,7 @@ enum Command { #[arg(long)] config: PathBuf, - /// Contract name. + /// Contract name (`admin_proxy` or `fee_vault`). #[arg(long)] contract: String, }, @@ -91,10 +91,22 @@ fn main() -> eyre::Result<()> { } } Command::ComputeAddress { - config: _config_path, + config: config_path, contract, } => { - eyre::bail!("unknown contract: {contract}"); + let cfg = config::DeployConfig::load(&config_path)?; + + let address = match contract.as_str() { + "admin_proxy" => cfg + .contracts + .admin_proxy + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, + other => eyre::bail!("unknown contract: {other}"), + }; + + println!("{address}"); } } diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index df4817e7..b30e373c 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -4,7 +4,15 @@ use crate::config::DeployConfig; use serde_json::{Map, Value}; /// Build an address manifest JSON from config. -pub(crate) fn build_manifest(_config: &DeployConfig) -> Value { - let manifest = Map::new(); +pub(crate) fn build_manifest(config: &DeployConfig) -> Value { + let mut manifest = Map::new(); + + if let Some(ref ap) = config.contracts.admin_proxy { + manifest.insert( + "admin_proxy".to_string(), + Value::String(format!("{}", ap.address)), + ); + } + Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index e690aad1..c1bee05d 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# End-to-end test: generate genesis with ev-deployer, boot ev-reth, verify merge works via RPC. +# End-to-end test: generate genesis with ev-deployer, boot ev-reth, verify contracts via RPC. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" @@ -76,11 +76,11 @@ echo "=== Generating genesis with ev-deployer ===" echo "Genesis written to $GENESIS" -# Sanity: output should be valid JSON with alloc field -python3 -c "import sys,json; g=json.load(open('$GENESIS')); assert 'alloc' in g" \ - || fail "genesis output is not valid JSON or missing alloc" +# Quick sanity: address should be in the alloc +grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ + || fail "AdminProxy address not found in genesis" -pass "genesis merge produced valid JSON with alloc" +pass "genesis contains AdminProxy address" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -104,11 +104,26 @@ echo "Node PID: $NODE_PID, waiting for RPC..." wait_for_rpc pass "node is up and responding to RPC" -# ── Step 4: Verify node boots with merged genesis ──────── +# ── Step 4: Verify AdminProxy ──────────────────────────── -block_number=$(rpc_call "eth_blockNumber" "[]") -[[ -n "$block_number" ]] || fail "could not get block number from node" -pass "node booted successfully with merged genesis (block: $block_number)" +ADMIN_PROXY="0x000000000000000000000000000000000000Ad00" +ADMIN_OWNER="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +echo "=== Verifying AdminProxy at $ADMIN_PROXY ===" + +# Check code is present +admin_code=$(rpc_call "eth_getCode" "[\"$ADMIN_PROXY\", \"latest\"]") +[[ "$admin_code" != "0x" && "$admin_code" != "0x0" && ${#admin_code} -gt 10 ]] \ + || fail "AdminProxy has no bytecode (got: $admin_code)" +pass "AdminProxy has bytecode (${#admin_code} hex chars)" + +# Check owner in slot 0 +admin_slot0=$(rpc_call "eth_getStorageAt" "[\"$ADMIN_PROXY\", \"0x0\", \"latest\"]") +# Owner should be in the lower 20 bytes, left-padded to 32 bytes +expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" +[[ "$(echo "$admin_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner_slot" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" +pass "AdminProxy owner slot 0 = $ADMIN_OWNER" # ── Done ───────────────────────────────────────────────── From 93b3eaa254398f2c01230dc7e282f8badefc8316 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:42:17 +0100 Subject: [PATCH 30/81] fix(ev-deployer): make [contracts] section optional in config Default to an empty ContractsConfig when the section is omitted, so a minimal config only needs [chain]. --- bin/ev-deployer/src/config.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 4eb8c48f..b4794b4d 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -11,6 +11,7 @@ pub(crate) struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, /// Contract configurations. + #[serde(default)] pub contracts: ContractsConfig, } @@ -23,7 +24,7 @@ pub(crate) struct ChainConfig { } /// All contract configurations. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Default)] pub(crate) struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, @@ -94,6 +95,17 @@ owner = "0x0000000000000000000000000000000000000000" assert!(config.validate().is_err()); } + #[test] + fn no_contracts_section() { + let toml = r#" +[chain] +chain_id = 1 +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.admin_proxy.is_none()); + } + #[test] fn admin_proxy_only() { let toml = r#" From 70111fd9dbe17c1f5e74a64c2bdcd4849f059661 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:47:23 +0100 Subject: [PATCH 31/81] feat(ev-deployer): add init command to generate starter config Generates a TOML config template with all supported contracts commented out and documented. --- bin/ev-deployer/src/init_template.toml | 16 ++++++++++++++++ bin/ev-deployer/src/main.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 bin/ev-deployer/src/init_template.toml diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml new file mode 100644 index 00000000..d147156f --- /dev/null +++ b/bin/ev-deployer/src/init_template.toml @@ -0,0 +1,16 @@ +# EV Deployer configuration +# See: bin/ev-deployer/README.md + +[chain] +# The chain ID for the target network. +chain_id = 0 + +# ── Contracts ──────────────────────────────────────────── +# Uncomment and configure the contracts you want to include +# in the genesis alloc. + +# AdminProxy: transparent proxy with owner-based access control. +# The owner address is stored in slot 0. +# [contracts.admin_proxy] +# address = "0x000000000000000000000000000000000000Ad00" +# owner = "0x..." diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index ce93568f..21c8f120 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -43,6 +43,12 @@ enum Command { #[arg(long)] addresses_out: Option, }, + /// Generate a starter config file with all supported contracts commented out. + Init { + /// Write config to this file instead of stdout. + #[arg(long)] + output: Option, + }, /// Compute the address for a configured contract. ComputeAddress { /// Path to the deploy TOML config. @@ -90,6 +96,16 @@ fn main() -> eyre::Result<()> { eprintln!("Wrote address manifest to {}", addr_path.display()); } } + Command::Init { output } => { + let template = include_str!("init_template.toml"); + + if let Some(ref out_path) = output { + std::fs::write(out_path, template)?; + eprintln!("Wrote config to {}", out_path.display()); + } else { + print!("{template}"); + } + } Command::ComputeAddress { config: config_path, contract, From fa0e71f4af99a4c5b771eeac8dc5b89590640e01 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 14:48:29 +0100 Subject: [PATCH 32/81] fix(ev-deployer): clean up command ordering and stale fee_vault reference Move Init subcommand first in help output and remove leftover fee_vault mention from --contract flag doc. --- bin/ev-deployer/src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 21c8f120..78b88ec4 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -21,6 +21,12 @@ struct Cli { #[derive(Subcommand)] enum Command { + /// Generate a starter config file with all supported contracts commented out. + Init { + /// Write config to this file instead of stdout. + #[arg(long)] + output: Option, + }, /// Generate genesis alloc JSON from a deploy config. Genesis { /// Path to the deploy TOML config. @@ -43,19 +49,13 @@ enum Command { #[arg(long)] addresses_out: Option, }, - /// Generate a starter config file with all supported contracts commented out. - Init { - /// Write config to this file instead of stdout. - #[arg(long)] - output: Option, - }, /// Compute the address for a configured contract. ComputeAddress { /// Path to the deploy TOML config. #[arg(long)] config: PathBuf, - /// Contract name (`admin_proxy` or `fee_vault`). + /// Contract name (e.g. `admin_proxy`). #[arg(long)] contract: String, }, From 1acd3c8ef63453f776eca362db9012392f1a786f Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 14:50:08 +0100 Subject: [PATCH 33/81] docs(ev-deployer): document init command in README --- bin/ev-deployer/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index bf695602..2e659459 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -40,6 +40,14 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" ## Usage +### Generate a starter config + +```bash +ev-deployer init --output deploy.toml +``` + +This creates a TOML config template with all supported contracts commented out and documented. + ### Generate genesis alloc Print alloc JSON to stdout: From ee683543486e4fdfbd12a2f7bee2f669a38ce2b6 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 22:43:34 +0100 Subject: [PATCH 34/81] fix(ev-deployer): remove extra blank lines from merge to pass rustfmt --- bin/ev-deployer/src/config.rs | 2 -- bin/ev-deployer/src/genesis.rs | 1 - bin/ev-deployer/src/output.rs | 1 - 3 files changed, 4 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 8bd6cecf..c085ba10 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -135,7 +135,6 @@ pub(crate) struct NoopIsmConfig { pub address: Address, } - impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -191,7 +190,6 @@ impl DeployConfig { ); } - Ok(()) } } diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 3ffa4f4d..eb3b7283 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -44,7 +44,6 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } - Value::Object(alloc) } diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 72841fe1..56e62608 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -49,6 +49,5 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - Value::Object(manifest) } From ef5ac9eae1832b5ab8e42dd4d01e45027785b2c9 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 22:43:37 +0100 Subject: [PATCH 35/81] docs(ev-deployer): document all supported contracts in README and init template --- bin/ev-deployer/README.md | 75 +++++++++++++++++++++----- bin/ev-deployer/src/init_template.toml | 38 +++++++++++++ 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 2e659459..235e493a 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -14,14 +14,7 @@ The binary is output to `target/release/ev-deployer`. EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example. -```toml -[chain] -chain_id = 1234 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -``` +See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured. ### Config reference @@ -38,6 +31,54 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" | `address` | address | Address to deploy at | | `owner` | address | Owner (must not be zero) | +#### `[contracts.fee_vault]` + +| Field | Type | Description | +|----------------------|---------|--------------------------------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner address | +| `destination_domain` | u32 | Hyperlane destination domain (default: 0) | +| `recipient_address` | bytes32 | Hyperlane recipient address (default: zero) | +| `minimum_amount` | u64 | Minimum amount for bridging (default: 0) | +| `call_fee` | u64 | Call fee for sendToCelestia (default: 0) | +| `bridge_share_bps` | u64 | Basis points for bridge share, 0–10000 (default: 0, treated as 10000) | +| `other_recipient` | address | Other recipient for split accounting (default: zero) | +| `hyp_native_minter` | address | HypNativeMinter address (default: zero) | + +#### `[contracts.mailbox]` + +| Field | Type | Description | +|-----------------|---------|-----------------------------------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner address (default: zero) | +| `default_ism` | address | Default interchain security module (default: zero) | +| `default_hook` | address | Default post-dispatch hook (default: zero) | +| `required_hook` | address | Required post-dispatch hook, e.g. MerkleTreeHook (default: zero) | + +#### `[contracts.merkle_tree_hook]` + +| Field | Type | Description | +|-----------|---------|----------------------------------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner address (default: zero) | +| `mailbox` | address | Mailbox address (patched into bytecode as immutable)| + +#### `[contracts.noop_ism]` + +| Field | Type | Description | +|-----------|---------|----------------------| +| `address` | address | Address to deploy at | + +#### `[contracts.protocol_fee]` + +| Field | Type | Description | +|--------------------|---------|---------------------------------------------------| +| `address` | address | Address to deploy at | +| `owner` | address | Owner address (default: zero) | +| `max_protocol_fee` | u64 | Maximum protocol fee in wei | +| `protocol_fee` | u64 | Protocol fee charged per dispatch in wei (default: 0) | +| `beneficiary` | address | Beneficiary address that receives collected fees (default: zero) | + ## Usage ### Generate a starter config @@ -88,7 +129,12 @@ Output: ```json { - "admin_proxy": "0x000000000000000000000000000000000000Ad00" + "admin_proxy": "0x000000000000000000000000000000000000Ad00", + "fee_vault": "0x000000000000000000000000000000000000FE00", + "mailbox": "0x0000000000000000000000000000000000001200", + "merkle_tree_hook": "0x0000000000000000000000000000000000001100", + "noop_ism": "0x0000000000000000000000000000000000001300", + "protocol_fee": "0x0000000000000000000000000000000000001400" } ``` @@ -100,9 +146,14 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy ## Contracts -| Contract | Description | -|----------------|-----------------------------------------------------| -| `admin_proxy` | Proxy contract with owner-based access control | +| Contract | Description | +|--------------------|---------------------------------------------------------| +| `admin_proxy` | Proxy contract with owner-based access control | +| `fee_vault` | Fee vault with Hyperlane bridging support | +| `mailbox` | Hyperlane core messaging hub | +| `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) | +| `noop_ism` | Hyperlane ISM that accepts all messages | +| `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee| Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index d147156f..2d75b981 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -14,3 +14,41 @@ chain_id = 0 # [contracts.admin_proxy] # address = "0x000000000000000000000000000000000000Ad00" # owner = "0x..." + +# FeeVault: fee vault with Hyperlane bridging support. +# [contracts.fee_vault] +# address = "0x000000000000000000000000000000000000FE00" +# owner = "0x..." +# destination_domain = 0 +# recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" +# minimum_amount = 0 +# call_fee = 0 +# bridge_share_bps = 10000 +# other_recipient = "0x0000000000000000000000000000000000000000" +# hyp_native_minter = "0x0000000000000000000000000000000000000000" + +# Mailbox: Hyperlane core messaging hub. +# [contracts.mailbox] +# address = "0x0000000000000000000000000000000000001200" +# owner = "0x..." +# default_ism = "0x0000000000000000000000000000000000000000" +# default_hook = "0x0000000000000000000000000000000000000000" +# required_hook = "0x0000000000000000000000000000000000000000" + +# MerkleTreeHook: Hyperlane required hook (Merkle tree for messages). +# [contracts.merkle_tree_hook] +# address = "0x0000000000000000000000000000000000001100" +# owner = "0x..." +# mailbox = "0x0000000000000000000000000000000000001200" + +# NoopIsm: Hyperlane ISM that accepts all messages. +# [contracts.noop_ism] +# address = "0x0000000000000000000000000000000000001300" + +# ProtocolFee: Hyperlane post-dispatch hook that charges a protocol fee. +# [contracts.protocol_fee] +# address = "0x0000000000000000000000000000000000001400" +# owner = "0x..." +# max_protocol_fee = 1000000000000000000 +# protocol_fee = 0 +# beneficiary = "0x..." From 65bbf9e10e75ce5dd705f4985c2c6dee45714b71 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 22:46:34 +0100 Subject: [PATCH 36/81] fix(ev-deployer): normalize alloc keys for collision detection Canonicalize address keys (lowercase, strip 0x) before comparing during merge, so collisions are detected regardless of case or prefix in the existing genesis file. --- bin/ev-deployer/src/genesis.rs | 52 +++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fa3c1445..76033b70 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -38,19 +38,29 @@ pub(crate) fn merge_into( let new_alloc = alloc.as_object().unwrap(); for (addr, entry) in new_alloc { - if genesis_alloc.contains_key(addr) && !force { + let canonical = normalize_addr(addr); + let existing_key = genesis_alloc + .keys() + .find(|k| normalize_addr(k) == canonical) + .cloned(); + if existing_key.is_some() && !force { eyre::bail!("address collision at {addr}; use --force to overwrite"); } - genesis_alloc.insert(addr.clone(), entry.clone()); + if let Some(key) = existing_key { + genesis_alloc.remove(&key); + } + genesis_alloc.insert(canonical, entry.clone()); } Ok(genesis) } +fn normalize_addr(addr: &str) -> String { + addr.strip_prefix("0x").unwrap_or(addr).to_lowercase() +} + fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { - // Address key without 0x prefix, using checksummed format - let addr_hex = format!("{}", contract.address); - let addr_key = addr_hex.strip_prefix("0x").unwrap_or(&addr_hex); + let addr_key = normalize_addr(&format!("{}", contract.address)); let mut storage_map = Map::new(); for (slot, value) in &contract.storage { @@ -89,7 +99,7 @@ mod tests { chain: ChainConfig { chain_id: 1234 }, contracts: ContractsConfig { admin_proxy: Some(AdminProxyConfig { - address: address!("000000000000000000000000000000000000Ad00"), + address: address!("000000000000000000000000000000000000ad00"), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), }, @@ -100,10 +110,10 @@ mod tests { fn alloc_json_structure() { let alloc = build_alloc(&test_config()); let obj = alloc.as_object().unwrap(); - assert!(obj.contains_key("000000000000000000000000000000000000Ad00")); + assert!(obj.contains_key("000000000000000000000000000000000000ad00")); let entry = obj - .get("000000000000000000000000000000000000Ad00") + .get("000000000000000000000000000000000000ad00") .unwrap() .as_object() .unwrap(); @@ -118,7 +128,7 @@ mod tests { let storage = alloc .as_object() .unwrap() - .get("000000000000000000000000000000000000Ad00") + .get("000000000000000000000000000000000000ad00") .unwrap() .get("storage") .unwrap() @@ -149,7 +159,7 @@ mod tests { #[test] fn merge_detects_collision() { - let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + let genesis = r#"{"alloc":{"000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); @@ -163,11 +173,31 @@ mod tests { #[test] fn merge_force_overwrites() { - let genesis = r#"{"alloc":{"000000000000000000000000000000000000Ad00":{"balance":"0x0"}}}"#; + let genesis = r#"{"alloc":{"000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); let result = merge_into(&test_config(), tmp.path(), true); assert!(result.is_ok()); } + + #[test] + fn merge_detects_collision_with_0x_prefix() { + let genesis = r#"{"alloc":{"0x000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; + let tmp = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(tmp.path(), genesis).unwrap(); + + let result = merge_into(&test_config(), tmp.path(), false); + assert!(result.is_err()); + } + + #[test] + fn merge_detects_collision_with_mixed_case() { + let genesis = r#"{"alloc":{"000000000000000000000000000000000000AD00":{"balance":"0x0"}}}"#; + let tmp = tempfile::NamedTempFile::new().unwrap(); + std::fs::write(tmp.path(), genesis).unwrap(); + + let result = merge_into(&test_config(), tmp.path(), false); + assert!(result.is_err()); + } } From 08c9eb4af123504dd0550d51f31d857307a8b960 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 05:45:22 +0100 Subject: [PATCH 37/81] style(ev-deployer): fix fmt and clippy lint in genesis.rs --- bin/ev-deployer/src/genesis.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 76033b70..167499bb 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -79,7 +79,7 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { ); entry.insert("storage".to_string(), Value::Object(storage_map)); - alloc.insert(addr_key.to_string(), Value::Object(entry)); + alloc.insert(addr_key, Value::Object(entry)); } /// Format a storage slot key as a full 32-byte hex string. @@ -183,7 +183,8 @@ mod tests { #[test] fn merge_detects_collision_with_0x_prefix() { - let genesis = r#"{"alloc":{"0x000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; + let genesis = + r#"{"alloc":{"0x000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); From 7f9e238d89099fb6bce9a310a0b1f52958eb6799 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 05:57:53 +0100 Subject: [PATCH 38/81] docs(ev-deployer): add Permit2 to init template and README --- bin/ev-deployer/README.md | 8 ++++++++ bin/ev-deployer/src/init_template.toml | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 235e493a..d02d9c11 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -69,6 +69,12 @@ See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with a |-----------|---------|----------------------| | `address` | address | Address to deploy at | +#### `[contracts.permit2]` + +| Field | Type | Description | +|-----------|---------|----------------------------------------------------------| +| `address` | address | Address to deploy at (canonical: `0x000000000022D473...`) | + #### `[contracts.protocol_fee]` | Field | Type | Description | @@ -134,6 +140,7 @@ Output: "mailbox": "0x0000000000000000000000000000000000001200", "merkle_tree_hook": "0x0000000000000000000000000000000000001100", "noop_ism": "0x0000000000000000000000000000000000001300", + "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "protocol_fee": "0x0000000000000000000000000000000000001400" } ``` @@ -153,6 +160,7 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy | `mailbox` | Hyperlane core messaging hub | | `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) | | `noop_ism` | Hyperlane ISM that accepts all messages | +| `permit2` | Uniswap canonical token approval manager | | `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee| Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 2d75b981..97f8eeb9 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -45,6 +45,10 @@ chain_id = 0 # [contracts.noop_ism] # address = "0x0000000000000000000000000000000000001300" +# Permit2: Uniswap canonical token approval manager. +# [contracts.permit2] +# address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" + # ProtocolFee: Hyperlane post-dispatch hook that charges a protocol fee. # [contracts.protocol_fee] # address = "0x0000000000000000000000000000000000001400" From b1e5ae30e336e4b7a329b3ef598efba3679c3881 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 10:52:03 +0100 Subject: [PATCH 39/81] fix(ev-deployer): use case-insensitive grep in e2e genesis address checks --- bin/ev-deployer/tests/e2e_genesis.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 19424ae8..b1f318b3 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -77,13 +77,13 @@ echo "=== Generating genesis with ev-deployer ===" echo "Genesis written to $GENESIS" # Quick sanity: address should be in the alloc -grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ +grep -qi "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" -grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ +grep -qi "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" -grep -q "0000000000000000000000000000000000001100" "$GENESIS" \ +grep -qi "0000000000000000000000000000000000001100" "$GENESIS" \ || fail "MerkleTreeHook address not found in genesis" -grep -q "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ +grep -qi "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ || fail "Permit2 address not found in genesis" pass "genesis contains all contract addresses" From 5d2be71ebdc2ae25aac1c656caf7514c49db33cc Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 11:48:56 +0100 Subject: [PATCH 40/81] refactor(ev-deployer): remove Hyperlane contracts, keep only AdminProxy and Permit2 Remove FeeVault, Mailbox, MerkleTreeHook, NoopIsm, and ProtocolFee contracts along with their config, validation, tests, docs, and e2e checks. This scopes the permit2 branch to only Part 1 (core) and Part 3 (Permit2) functionality. --- bin/ev-deployer/README.md | 70 +---- bin/ev-deployer/examples/devnet.toml | 33 -- bin/ev-deployer/src/config.rs | 185 +---------- bin/ev-deployer/src/contracts/fee_vault.rs | 184 ----------- bin/ev-deployer/src/contracts/immutables.rs | 53 +--- bin/ev-deployer/src/contracts/mailbox.rs | 295 ------------------ .../src/contracts/merkle_tree_hook.rs | 254 --------------- bin/ev-deployer/src/contracts/mod.rs | 5 - bin/ev-deployer/src/contracts/noop_ism.rs | 109 ------- bin/ev-deployer/src/contracts/protocol_fee.rs | 209 ------------- bin/ev-deployer/src/genesis.rs | 32 -- bin/ev-deployer/src/init_template.toml | 38 --- bin/ev-deployer/src/main.rs | 30 -- bin/ev-deployer/src/output.rs | 35 --- bin/ev-deployer/tests/e2e_genesis.sh | 116 +------ 15 files changed, 8 insertions(+), 1640 deletions(-) delete mode 100644 bin/ev-deployer/src/contracts/fee_vault.rs delete mode 100644 bin/ev-deployer/src/contracts/mailbox.rs delete mode 100644 bin/ev-deployer/src/contracts/merkle_tree_hook.rs delete mode 100644 bin/ev-deployer/src/contracts/noop_ism.rs delete mode 100644 bin/ev-deployer/src/contracts/protocol_fee.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index d02d9c11..fe7ae4dc 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -14,8 +14,6 @@ The binary is output to `target/release/ev-deployer`. EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example. -See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured. - ### Config reference #### `[chain]` @@ -31,60 +29,12 @@ See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with a | `address` | address | Address to deploy at | | `owner` | address | Owner (must not be zero) | -#### `[contracts.fee_vault]` - -| Field | Type | Description | -|----------------------|---------|--------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address | -| `destination_domain` | u32 | Hyperlane destination domain (default: 0) | -| `recipient_address` | bytes32 | Hyperlane recipient address (default: zero) | -| `minimum_amount` | u64 | Minimum amount for bridging (default: 0) | -| `call_fee` | u64 | Call fee for sendToCelestia (default: 0) | -| `bridge_share_bps` | u64 | Basis points for bridge share, 0–10000 (default: 0, treated as 10000) | -| `other_recipient` | address | Other recipient for split accounting (default: zero) | -| `hyp_native_minter` | address | HypNativeMinter address (default: zero) | - -#### `[contracts.mailbox]` - -| Field | Type | Description | -|-----------------|---------|-----------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `default_ism` | address | Default interchain security module (default: zero) | -| `default_hook` | address | Default post-dispatch hook (default: zero) | -| `required_hook` | address | Required post-dispatch hook, e.g. MerkleTreeHook (default: zero) | - -#### `[contracts.merkle_tree_hook]` - -| Field | Type | Description | -|-----------|---------|----------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `mailbox` | address | Mailbox address (patched into bytecode as immutable)| - -#### `[contracts.noop_ism]` - -| Field | Type | Description | -|-----------|---------|----------------------| -| `address` | address | Address to deploy at | - #### `[contracts.permit2]` | Field | Type | Description | |-----------|---------|----------------------------------------------------------| | `address` | address | Address to deploy at (canonical: `0x000000000022D473...`) | -#### `[contracts.protocol_fee]` - -| Field | Type | Description | -|--------------------|---------|---------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `max_protocol_fee` | u64 | Maximum protocol fee in wei | -| `protocol_fee` | u64 | Protocol fee charged per dispatch in wei (default: 0) | -| `beneficiary` | address | Beneficiary address that receives collected fees (default: zero) | - ## Usage ### Generate a starter config @@ -136,12 +86,7 @@ Output: ```json { "admin_proxy": "0x000000000000000000000000000000000000Ad00", - "fee_vault": "0x000000000000000000000000000000000000FE00", - "mailbox": "0x0000000000000000000000000000000000001200", - "merkle_tree_hook": "0x0000000000000000000000000000000000001100", - "noop_ism": "0x0000000000000000000000000000000000001300", - "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "protocol_fee": "0x0000000000000000000000000000000000001400" + "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3" } ``` @@ -153,15 +98,10 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy ## Contracts -| Contract | Description | -|--------------------|---------------------------------------------------------| -| `admin_proxy` | Proxy contract with owner-based access control | -| `fee_vault` | Fee vault with Hyperlane bridging support | -| `mailbox` | Hyperlane core messaging hub | -| `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) | -| `noop_ism` | Hyperlane ISM that accepts all messages | -| `permit2` | Uniswap canonical token approval manager | -| `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee| +| Contract | Description | +|---------------|----------------------------------------------------| +| `admin_proxy` | Proxy contract with owner-based access control | +| `permit2` | Uniswap canonical token approval manager | Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index a17ad4ca..5e3d9096 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -5,40 +5,7 @@ chain_id = 1234 address = "0x000000000000000000000000000000000000Ad00" owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -destination_domain = 0 -recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -minimum_amount = 0 -call_fee = 0 -bridge_share_bps = 10000 -other_recipient = "0x0000000000000000000000000000000000000000" -hyp_native_minter = "0x0000000000000000000000000000000000000000" - -[contracts.mailbox] -address = "0x0000000000000000000000000000000000001200" -owner = "0x000000000000000000000000000000000000Ad00" -default_ism = "0x0000000000000000000000000000000000001300" -default_hook = "0x0000000000000000000000000000000000001400" -required_hook = "0x0000000000000000000000000000000000001100" - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -owner = "0x000000000000000000000000000000000000Ad00" -mailbox = "0x0000000000000000000000000000000000001200" - -[contracts.noop_ism] -address = "0x0000000000000000000000000000000000001300" - [contracts.permit2] # Canonical Uniswap Permit2 address (same on all chains via CREATE2). # Using it here so frontends, SDKs and routers work out of the box. address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" - -[contracts.protocol_fee] -address = "0x0000000000000000000000000000000000001400" -owner = "0x000000000000000000000000000000000000Ad00" -max_protocol_fee = 1000000000000000000 -protocol_fee = 0 -beneficiary = "0x000000000000000000000000000000000000Ad00" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 4dad06a2..bf1f8671 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,6 +1,6 @@ //! TOML config types, parsing, and validation. -use alloy_primitives::{Address, B256}; +use alloy_primitives::Address; use serde::Deserialize; use std::path::Path; @@ -28,18 +28,8 @@ pub(crate) struct ChainConfig { pub(crate) struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, - /// `FeeVault` contract config (optional). - pub fee_vault: Option, - /// `MerkleTreeHook` contract config (optional). - pub merkle_tree_hook: Option, - /// `Mailbox` contract config (optional). - pub mailbox: Option, - /// `NoopIsm` contract config (optional). - pub noop_ism: Option, /// `Permit2` contract config (optional). pub permit2: Option, - /// `ProtocolFee` contract config (optional). - pub protocol_fee: Option, } /// `AdminProxy` configuration. @@ -51,74 +41,6 @@ pub(crate) struct AdminProxyConfig { pub owner: Address, } -/// `FeeVault` configuration. -#[derive(Debug, Deserialize)] -pub(crate) struct FeeVaultConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - pub owner: Address, - /// Hyperlane destination domain. - #[serde(default)] - pub destination_domain: u32, - /// Hyperlane recipient address (bytes32). - #[serde(default)] - pub recipient_address: B256, - /// Minimum amount for bridging. - #[serde(default)] - pub minimum_amount: u64, - /// Call fee for sendToCelestia. - #[serde(default)] - pub call_fee: u64, - /// Basis points for bridge share (0-10000). 0 defaults to 10000. - #[serde(default)] - pub bridge_share_bps: u64, - /// Other recipient for split accounting. - #[serde(default)] - pub other_recipient: Address, - /// `HypNativeMinter` address. - #[serde(default)] - pub hyp_native_minter: Address, -} - -/// `MerkleTreeHook` configuration (Hyperlane required hook). -#[derive(Debug, Deserialize)] -pub(crate) struct MerkleTreeHookConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address (for post-genesis hook/ISM changes). - #[serde(default)] - pub owner: Address, - /// Mailbox address (patched into bytecode as immutable). - pub mailbox: Address, -} - -/// `MailboxConfig` configuration (Hyperlane core messaging hub). -#[derive(Debug, Deserialize)] -pub(crate) struct MailboxConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - #[serde(default)] - pub owner: Address, - /// Default interchain security module. - #[serde(default)] - pub default_ism: Address, - /// Default post-dispatch hook. - #[serde(default)] - pub default_hook: Address, - /// Required post-dispatch hook (e.g. `MerkleTreeHook`). - #[serde(default)] - pub required_hook: Address, -} - -/// `NoopIsm` configuration (Hyperlane ISM that accepts all messages). -#[derive(Debug, Deserialize)] -pub(crate) struct NoopIsmConfig { - /// Address to deploy at. - pub address: Address, -} - /// `Permit2` configuration (Uniswap token approval manager). #[derive(Debug, Deserialize)] pub(crate) struct Permit2Config { @@ -126,24 +48,6 @@ pub(crate) struct Permit2Config { pub address: Address, } -/// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee). -#[derive(Debug, Deserialize)] -pub(crate) struct ProtocolFeeConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - #[serde(default)] - pub owner: Address, - /// Maximum protocol fee in wei. - pub max_protocol_fee: u64, - /// Protocol fee charged per dispatch in wei. - #[serde(default)] - pub protocol_fee: u64, - /// Beneficiary address that receives collected fees. - #[serde(default)] - pub beneficiary: Address, -} - impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -162,43 +66,6 @@ impl DeployConfig { ); } - if let Some(ref fv) = self.contracts.fee_vault { - eyre::ensure!( - !fv.owner.is_zero(), - "fee_vault.owner must not be the zero address" - ); - eyre::ensure!( - fv.bridge_share_bps <= 10000, - "fee_vault.bridge_share_bps must be 0-10000, got {}", - fv.bridge_share_bps - ); - } - - if let Some(ref mth) = self.contracts.merkle_tree_hook { - eyre::ensure!( - !mth.mailbox.is_zero(), - "merkle_tree_hook.mailbox must not be the zero address" - ); - } - - if let Some(ref pf) = self.contracts.protocol_fee { - eyre::ensure!( - !pf.owner.is_zero(), - "protocol_fee.owner must not be the zero address" - ); - eyre::ensure!( - !pf.beneficiary.is_zero(), - "protocol_fee.beneficiary must not be the zero address" - ); - } - - if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) { - eyre::ensure!( - ap.address != fv.address, - "contracts.admin_proxy.address and contracts.fee_vault.address must be distinct" - ); - } - Ok(()) } } @@ -248,56 +115,6 @@ chain_id = 1 assert!(config.contracts.admin_proxy.is_none()); } - #[test] - fn parse_merkle_tree_hook_config() { - let toml = r#" -[chain] -chain_id = 1234 - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -owner = "0x000000000000000000000000000000000000ad00" -mailbox = "0x0000000000000000000000000000000000001200" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - config.validate().unwrap(); - assert!(config.contracts.merkle_tree_hook.is_some()); - let mth = config.contracts.merkle_tree_hook.unwrap(); - assert!(!mth.mailbox.is_zero()); - } - - #[test] - fn reject_zero_mailbox_merkle_tree_hook() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -mailbox = "0x0000000000000000000000000000000000000000" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - - #[test] - fn reject_duplicate_addresses() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - #[test] fn admin_proxy_only() { let toml = r#" diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs deleted file mode 100644 index 445ea8c1..00000000 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! `FeeVault` bytecode and storage encoding. - -use crate::{config::FeeVaultConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). -/// Regenerate with: `cd contracts && forge inspect FeeVault deployedBytecode` -const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); - -/// Build a genesis alloc entry for `FeeVault`. -pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { - let mut storage = BTreeMap::new(); - - // Apply constructor default: bps 0 -> 10000 - let effective_bps = if config.bridge_share_bps == 0 { - 10000 - } else { - config.bridge_share_bps - }; - - // Slot 0: hypNativeMinter (address) - storage.insert( - B256::ZERO, - B256::from(U256::from_be_bytes(config.hyp_native_minter.into_word().0)), - ); - - // Slot 1: owner (lower 160 bits) + destinationDomain (shifted left 160 bits) - let owner_u256 = U256::from_be_bytes(config.owner.into_word().0); - let domain_u256 = U256::from(config.destination_domain) << 160; - storage.insert( - B256::with_last_byte(1), - B256::from(owner_u256 | domain_u256), - ); - - // Slot 2: recipientAddress (bytes32) - storage.insert(B256::with_last_byte(2), config.recipient_address); - - // Slot 3: minimumAmount - storage.insert( - B256::with_last_byte(3), - B256::from(U256::from(config.minimum_amount)), - ); - - // Slot 4: callFee - storage.insert( - B256::with_last_byte(4), - B256::from(U256::from(config.call_fee)), - ); - - // Slot 5: otherRecipient (address) - storage.insert( - B256::with_last_byte(5), - B256::from(U256::from_be_bytes(config.other_recipient.into_word().0)), - ); - - // Slot 6: bridgeShareBps - storage.insert( - B256::with_last_byte(6), - B256::from(U256::from(effective_bps)), - ); - - GenesisContract { - address: config.address, - code: Bytes::from_static(FEE_VAULT_BYTECODE), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, Address}; - use std::{path::PathBuf, process::Command}; - - #[test] - fn fee_vault_storage_encoding() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // Slot 0: hypNativeMinter = zero - assert_eq!(contract.storage[&B256::ZERO], B256::ZERO); - - // Slot 1: owner packed with domain - let expected_slot1: B256 = - "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected_slot1); - - // Slot 6: bridgeShareBps = 10000 - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn bps_zero_defaults_to_10000() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 0, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn slot1_packing_with_nonzero_domain() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - destination_domain: 42, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // slot1 = (42 << 160) | owner - let owner_u256 = U256::from_be_bytes( - address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266") - .into_word() - .0, - ); - let expected = B256::from((U256::from(42u32) << 160) | owner_u256); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); - } - - #[test] - #[ignore = "requires forge CLI"] - fn fee_vault_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts"); - - let output = Command::new("forge") - .args(["inspect", "FeeVault", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .trim_start_matches("0x") - .to_lowercase(); - - let hardcoded_hex = hex::encode(FEE_VAULT_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "FeeVault bytecode mismatch! Update the constant with: cd contracts && forge inspect FeeVault deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/immutables.rs b/bin/ev-deployer/src/contracts/immutables.rs index 40c75f48..09341047 100644 --- a/bin/ev-deployer/src/contracts/immutables.rs +++ b/bin/ev-deployer/src/contracts/immutables.rs @@ -6,7 +6,7 @@ //! byte offsets. This module replaces those regions with the actual values from //! the deploy config at genesis-generation time. -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{B256, U256}; /// A single immutable reference inside a bytecode blob. #[derive(Debug, Clone, Copy)] @@ -35,18 +35,6 @@ pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u } } -/// Convenience: patch with an ABI-encoded `address` (left-padded to 32 bytes). -pub(crate) fn patch_address(bytecode: &mut [u8], refs: &[ImmutableRef], addr: Address) { - let word: B256 = B256::from(U256::from_be_bytes(addr.into_word().0)); - patch_bytes(bytecode, refs, &word.0); -} - -/// Convenience: patch with an ABI-encoded `uint32` (left-padded to 32 bytes). -pub(crate) fn patch_u32(bytecode: &mut [u8], refs: &[ImmutableRef], val: u32) { - let word = B256::from(U256::from(val)); - patch_bytes(bytecode, refs, &word.0); -} - /// Convenience: patch with an ABI-encoded `uint256`. pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { let word = B256::from(val); @@ -74,45 +62,6 @@ mod tests { assert_eq!(bytecode[42], 0); } - #[test] - fn patch_multiple_refs() { - let mut bytecode = vec![0u8; 128]; - let refs = [ - ImmutableRef { - start: 0, - length: 32, - }, - ImmutableRef { - start: 64, - length: 32, - }, - ]; - let addr = Address::repeat_byte(0xAB); - patch_address(&mut bytecode, &refs, addr); - - // Both locations should have the address (last 20 bytes of the 32-byte word) - assert_eq!(bytecode[12..32], [0xAB; 20]); - assert_eq!(bytecode[76..96], [0xAB; 20]); - // Padding bytes should be zero - assert_eq!(bytecode[0..12], [0u8; 12]); - assert_eq!(bytecode[64..76], [0u8; 12]); - } - - #[test] - fn patch_u32_value() { - let mut bytecode = vec![0u8; 64]; - let refs = [ImmutableRef { - start: 0, - length: 32, - }]; - patch_u32(&mut bytecode, &refs, 1234); - - // uint32 1234 = 0x04D2, left-padded to 32 bytes - assert_eq!(bytecode[30], 0x04); - assert_eq!(bytecode[31], 0xD2); - assert_eq!(bytecode[0..30], [0u8; 30]); - } - #[test] #[should_panic(expected = "immutable ref out of bounds")] fn patch_out_of_bounds_panics() { diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs deleted file mode 100644 index 4f774587..00000000 --- a/bin/ev-deployer/src/contracts/mailbox.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! `Mailbox` bytecode and storage encoding. -//! -//! `Mailbox` is the core Hyperlane messaging hub. It dispatches and processes -//! cross-chain messages. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |-----------------|---------|----------------------| -//! | `deployedBlock` | uint256 | \[930\] | -//! | `localDomain` | uint32 | \[982, 2831, 5985\] | -//! -//! ## Storage layout (from `forge inspect Mailbox storageLayout`) -//! -//! | Slot | Variable | Type | -//! |-------|-----------------------------|-----------| -//! | 0 | `_initialized` + `_initializing` | uint8 + bool | -//! | 1-50 | `__gap` (Initializable) | — | -//! | 51 | `_owner` | address | -//! | 52-100| `__gap` (Ownable) | — | -//! | 101 | `nonce` | uint32 | -//! | 102 | `latestDispatchedId` | bytes32 | -//! | 103 | `defaultIsm` | address | -//! | 104 | `defaultHook` | address | -//! | 105 | `requiredHook` | address | -//! | 106 | `deliveries` (mapping) | — | - -use crate::{ - config::MailboxConfig, - contracts::{ - immutables::{patch_u256, patch_u32, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `Mailbox` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode -/// ``` -const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016109cd565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); - -// ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── - -/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. -const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 930, - length: 32, -}]; - -/// `localDomain` (uint32) — from `Mailbox.sol`. -const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 982, - length: 32, - }, - ImmutableRef { - start: 2831, - length: 32, - }, - ImmutableRef { - start: 5985, - length: 32, - }, -]; - -/// Build a genesis alloc entry for `Mailbox`. -pub(crate) fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { - let mut bytecode = MAILBOX_BYTECODE.to_vec(); - - // Patch immutables - patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); - patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); - - let mut storage = BTreeMap::new(); - - // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false - storage.insert(B256::ZERO, B256::from(U256::from(1u8))); - - // Slot 51: _owner - if !config.owner.is_zero() { - storage.insert( - B256::from(U256::from(51u64)), - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // Slot 103: defaultIsm - if !config.default_ism.is_zero() { - storage.insert( - B256::from(U256::from(103u64)), - B256::from(U256::from_be_bytes(config.default_ism.into_word().0)), - ); - } - - // Slot 104: defaultHook - if !config.default_hook.is_zero() { - storage.insert( - B256::from(U256::from(104u64)), - B256::from(U256::from_be_bytes(config.default_hook.into_word().0)), - ); - } - - // Slot 105: requiredHook - if !config.required_hook.is_zero() { - storage.insert( - B256::from(U256::from(105u64)), - B256::from(U256::from_be_bytes(config.required_hook.into_word().0)), - ); - } - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex, Address}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> MailboxConfig { - MailboxConfig { - address: address!("0000000000000000000000000000000000001200"), - owner: address!("000000000000000000000000000000000000ad00"), - default_ism: address!("0000000000000000000000000000000000002000"), - default_hook: address!("0000000000000000000000000000000000003000"), - required_hook: address!("0000000000000000000000000000000000004000"), - } - } - - #[test] - fn storage_has_initialized_flag() { - let contract = build(&test_config(), 1234); - assert_eq!( - contract.storage[&B256::ZERO], - B256::from(U256::from(1u8)), - "_initialized should be 1" - ); - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config(), 1234); - let owner_slot = B256::from(U256::from(51u64)); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&owner_slot], expected); - } - - #[test] - fn storage_has_default_ism() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(103u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000002000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn storage_has_default_hook() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(104u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000003000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn storage_has_required_hook() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(105u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000004000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn bytecode_is_patched_with_local_domain() { - let config = test_config(); - let contract = build(&config, 42); - let code = contract.code.to_vec(); - - for &offset in &[982, 2831, 5985] { - let word = &code[offset..offset + 32]; - assert_eq!(word[31], 42, "localDomain not patched at offset {offset}"); - assert_eq!( - word[0..31], - [0u8; 31], - "localDomain padding wrong at offset {offset}" - ); - } - } - - #[test] - fn bytecode_has_zero_deployed_block() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - let word = &code[930..930 + 32]; - assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); - } - - #[test] - fn storage_count_for_standard_config() { - let contract = build(&test_config(), 1234); - // _initialized (0) + _owner (51) + defaultIsm (103) + defaultHook (104) + requiredHook (105) - assert_eq!(contract.storage.len(), 5); - } - - #[test] - fn zero_addresses_omit_slots() { - let config = MailboxConfig { - address: address!("0000000000000000000000000000000000001200"), - owner: Address::ZERO, - default_ism: Address::ZERO, - default_hook: Address::ZERO, - required_hook: Address::ZERO, - }; - let contract = build(&config, 1234); - // Only _initialized (slot 0) should be present - assert_eq!(contract.storage.len(), 1); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(51u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(103u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(104u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(105u64)))); - } - - #[test] - #[ignore = "requires forge CLI"] - fn mailbox_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "Mailbox", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(MAILBOX_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "Mailbox bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs deleted file mode 100644 index 0723adab..00000000 --- a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! `MerkleTreeHook` bytecode and storage encoding. -//! -//! `MerkleTreeHook` is a Hyperlane post-dispatch hook that maintains an -//! incremental Merkle tree of dispatched message IDs. Validators sign -//! checkpoints against this tree to attest to the messages. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |---------------|---------|---------------------| -//! | `mailbox` | address | \[904, 3300\] | -//! | `localDomain` | uint32 | \[644\] | -//! | `deployedBlock`| uint256| \[578\] | -//! -//! ## Storage layout (from `forge inspect MerkleTreeHook storageLayout`) -//! -//! | Slot | Variable | Type | -//! |------|-----------------------------|---------| -//! | 0 | `_initialized` + `_initializing` | uint8 + bool | -//! | 1-50 | `__gap` (Initializable) | — | -//! | 51 | `_owner` | address | -//! | 52-100| `__gap` (Ownable) | — | -//! | 101 | `hook` | address | -//! | 102 | `_interchainSecurityModule` | address | -//! | 103-150| `__GAP` (MailboxClient) | — | -//! | 151-182| `_tree.branch\[0..31\]` | bytes32\[32\] | -//! | 183 | `_tree.count` | uint256 | - -use crate::{ - config::MerkleTreeHookConfig, - contracts::{ - immutables::{patch_address, patch_u256, patch_u32, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `MerkleTreeHook` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode -/// ``` -const MERKLE_TREE_HOOK_BYTECODE: &[u8] = &hex!("6080604052600436106101445760003560e01c8063907c0f92116100c0578063e445e7dd11610074578063ebf0c71711610059578063ebf0c7171461042c578063f2fde38b14610441578063fd54b2281461046157600080fd5b8063e445e7dd146103d5578063e5320bb9146103fc57600080fd5b8063aaccd230116100a5578063aaccd23014610356578063d5438eae14610376578063de523cf3146103aa57600080fd5b8063907c0f92146102d157806393c448471461030057600080fd5b8063715018a61161011757806382ea7bfe116100fc57806382ea7bfe146102305780638d3638f4146102725780638da5cb5b146102a657600080fd5b8063715018a6146101c95780637f5a7c7b146101de57600080fd5b806306661abd14610149578063086011b9146101745780630e72cc06146101895780633dfd3873146101a9575b600080fd5b34801561015557600080fd5b5060b7545b60405163ffffffff90911681526020015b60405180910390f35b6101876101823660046114c2565b610483565b005b34801561019557600080fd5b506101876101a436600461152e565b610530565b3480156101b557600080fd5b506101876101c436600461152e565b610679565b3480156101d557600080fd5b506101876107ba565b3480156101ea57600080fd5b5060655461020b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161016b565b34801561023c57600080fd5b506102647f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200161016b565b34801561027e57600080fd5b5061015a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102b257600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156102dd57600080fd5b506102e66107ce565b6040805192835263ffffffff90911660208301520161016b565b34801561030c57600080fd5b506103496040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161016b919061156b565b34801561036257600080fd5b506102646103713660046114c2565b6107f6565b34801561038257600080fd5b5061020b7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103b657600080fd5b5060665473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156103e157600080fd5b506103ea610899565b60405160ff909116815260200161016b565b34801561040857600080fd5b5061041c6104173660046115d8565b6108a3565b604051901515815260200161016b565b34801561043857600080fd5b506102646108c8565b34801561044d57600080fd5b5061018761045c36600461152e565b6108d4565b34801561046d57600080fd5b5061047661098b565b60405161016b919061161a565b61048d84846108a3565b61051e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b61052a848484846109da565b50505050565b8073ffffffffffffffffffffffffffffffffffffffff81163b15158061056a575073ffffffffffffffffffffffffffffffffffffffff8116155b6105f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b6105fe610b78565b606680547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527fc47cbcc588c67679e52261c45cc315e56562f8d0ccaba16facb9093ff9498799906020015b60405180910390a15050565b8073ffffffffffffffffffffffffffffffffffffffff81163b1515806106b3575073ffffffffffffffffffffffffffffffffffffffff8116155b61073f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b610747610b78565b606580547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527f4eab7b127c764308788622363ad3e9532de3dfba7845bd4f84c125a22544255a9060200161066d565b6107c2610b78565b6107cc6000610bf9565b565b6000806107d96108c8565b60016107e460b75490565b6107ee919061168c565b915091509091565b600061080285856108a3565b61088e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e7400000000000000000000000000006064820152608401610515565b600095945050505050565b600060035b905090565b60008115806108bf575060016108b98484610c70565b61ffff16145b90505b92915050565b600061089e6097610cc1565b6108dc610b78565b73ffffffffffffffffffffffffffffffffffffffff811661097f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610515565b61098881610bf9565b50565b61099361143a565b60408051610440810180835290916097918391820190839060209082845b8154815260200190600101908083116109b1575050509183525050602091820154910152919050565b3415610a68576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d65726b6c6554726565486f6f6b3a206e6f2076616c7565206578706563746560448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610515565b6000610aa983838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610cd492505050565b9050610ab481610cdf565b610b1a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6d657373616765206e6f74206469737061746368696e670000000000000000006044820152606401610515565b6000610b2560b75490565b9050610b32609783610d78565b6040805183815263ffffffff831660208201527f253a3a04cab70d47c1504809242d9350cd81627b4f1d50753e159cf8cd76ed33910160405180910390a1505050505050565b60335473ffffffffffffffffffffffffffffffffffffffff1633146107cc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610515565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000610c7d8160026116b0565b60ff16821015610c8f575060006108c2565b82600083610c9e8260026116b0565b60ff1692610cae939291906116c9565b610cb7916116f3565b60f01c9392505050565b60006108c282610ccf610eb2565b611373565b805160209091012090565b6000817f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663134fbb4f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d4d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d719190611739565b1492915050565b6001610d8660206002611872565b610d90919061187e565b826020015410610dfc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6d65726b6c6520747265652066756c6c000000000000000000000000000000006044820152606401610515565b6001826020016000828254610e119190611891565b9091555050602082015460005b6020811015610ea45781600116600103610e4d5782848260208110610e4557610e456118a4565b015550505050565b838160208110610e5f57610e5f6118a4565b01546040805160208101929092528101849052606001604051602081830303815290604052805190602001209250600282610e9a91906118d3565b9150600101610e1e565b50610ead61190e565b505050565b610eba61145a565b600081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560208201527fb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3060408201527f21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba8560608201527fe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a1934460808201527f0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d60a08201527f887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a196860c08201527fffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f8360e08201527f9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af6101008201527fcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e06101208201527ff9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a56101408201527ff8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8926101608201527f3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c6101808201527fc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb6101a08201527f5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc6101c08201527fda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d26101e08201527f2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f6102008201527fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a6102208201527f5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a06102408201527fb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa06102608201527fc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e26102808201527ff4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd96102a08201527f5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3776102c08201527f4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee6526102e08201527fcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef6103008201527f0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6103208201527fb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d06103408201527f838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e6103608201527f662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e6103808201527f388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea3226103a08201527f93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7356103c08201527f8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a96103e082015290565b6020820154600090815b602081101561143257600182821c1660008683602081106113a0576113a06118a4565b01549050816001036113dd576040805160208101839052908101869052606001604051602081830303815290604052805190602001209450611428565b848684602081106113f0576113f06118a4565b602002015160405160200161140f929190918252602082015260400190565b6040516020818303038152906040528051906020012094505b505060010161137d565b505092915050565b604051806040016040528061144d61145a565b8152602001600081525090565b6040518061040001604052806020906020820280368337509192915050565b60008083601f84011261148b57600080fd5b50813567ffffffffffffffff8111156114a357600080fd5b6020830191508360208285010111156114bb57600080fd5b9250929050565b600080600080604085870312156114d857600080fd5b843567ffffffffffffffff808211156114f057600080fd5b6114fc88838901611479565b9096509450602087013591508082111561151557600080fd5b5061152287828801611479565b95989497509550505050565b60006020828403121561154057600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461156457600080fd5b9392505050565b60006020808352835180602085015260005b818110156115995785810183015185820160400152820161157d565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b600080602083850312156115eb57600080fd5b823567ffffffffffffffff81111561160257600080fd5b61160e85828601611479565b90969095509350505050565b81516104208201908260005b60208082106116355750611649565b835183529283019290910190600101611626565b505050602083015161040083015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff8281168282160390808211156116a9576116a961165d565b5092915050565b60ff81811683821601908111156108c2576108c261165d565b600080858511156116d957600080fd5b838611156116e657600080fd5b5050820193919092039150565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156114325760029490940360031b84901b1690921692915050565b60006020828403121561174b57600080fd5b5051919050565b600181815b808511156117ab57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156117915761179161165d565b8085161561179e57918102915b93841c9390800290611757565b509250929050565b6000826117c2575060016108c2565b816117cf575060006108c2565b81600181146117e557600281146117ef5761180b565b60019150506108c2565b60ff8411156118005761180061165d565b50506001821b6108c2565b5060208310610133831016604e8410600b841016171561182e575081810a6108c2565b6118388383611752565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561186a5761186a61165d565b029392505050565b60006108bf83836117b3565b818103818111156108c2576108c261165d565b808201808211156108c2576108c261165d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082611909577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd"); - -// ── Immutable reference offsets (from `forge inspect MerkleTreeHook immutableReferences`) ── - -/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. -const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 578, - length: 32, -}]; - -/// `mailbox` (address) — from `MailboxClient.sol`. -const MAILBOX_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 904, - length: 32, - }, - ImmutableRef { - start: 3300, - length: 32, - }, -]; - -/// `localDomain` (uint32) — from `MailboxClient.sol`. -const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 644, - length: 32, -}]; - -/// Build a genesis alloc entry for `MerkleTreeHook`. -pub(crate) fn build(config: &MerkleTreeHookConfig, local_domain: u32) -> GenesisContract { - let mut bytecode = MERKLE_TREE_HOOK_BYTECODE.to_vec(); - - // Patch immutables - patch_address(&mut bytecode, MAILBOX_REFS, config.mailbox); - patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); - patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); - - let mut storage = BTreeMap::new(); - - // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false - // byte layout: [_initialized (1 byte)] [_initializing (1 byte)] [... 30 zero bytes] - // Packed at slot 0: value = 0x01 (just _initialized = 1) - storage.insert(B256::ZERO, B256::from(U256::from(1u8))); - - // Slot 51: _owner — set the owner so the contract can be administered post-genesis - if !config.owner.is_zero() { - storage.insert( - B256::from(U256::from(51u64)), - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // All other storage starts at zero: - // - hook (slot 101): zero = use mailbox default - // - _interchainSecurityModule (slot 102): zero = use mailbox default - // - _tree.branch[0..31] (slots 151-182): all zero (empty tree) - // - _tree.count (slot 183): 0 - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex, Address}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> MerkleTreeHookConfig { - MerkleTreeHookConfig { - address: address!("0000000000000000000000000000000000001100"), - owner: address!("000000000000000000000000000000000000ad00"), - mailbox: address!("0000000000000000000000000000000000001200"), - } - } - - #[test] - fn storage_has_initialized_flag() { - let contract = build(&test_config(), 1234); - assert_eq!( - contract.storage[&B256::ZERO], - B256::from(U256::from(1u8)), - "_initialized should be 1" - ); - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config(), 1234); - let owner_slot = B256::from(U256::from(51u64)); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&owner_slot], expected); - } - - #[test] - fn zero_owner_omits_slot() { - let config = MerkleTreeHookConfig { - address: address!("0000000000000000000000000000000000001100"), - owner: Address::ZERO, - mailbox: address!("0000000000000000000000000000000000001200"), - }; - let contract = build(&config, 1234); - let owner_slot = B256::from(U256::from(51u64)); - assert!( - !contract.storage.contains_key(&owner_slot), - "zero owner should not produce a storage entry" - ); - } - - #[test] - fn bytecode_is_patched_with_mailbox() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - // Check that mailbox address is patched at both offsets - for &offset in &[904, 3300] { - let word = &code[offset..offset + 32]; - // Address is in the last 20 bytes of the 32-byte word - let addr_bytes = &word[12..32]; - assert_eq!( - addr_bytes, - config.mailbox.as_slice(), - "mailbox not patched at offset {offset}" - ); - } - } - - #[test] - fn bytecode_is_patched_with_local_domain() { - let config = test_config(); - let contract = build(&config, 42); - let code = contract.code.to_vec(); - - let word = &code[644..644 + 32]; - // uint32(42) in big-endian, left-padded: ...00 00 00 2a - assert_eq!(word[31], 42); - assert_eq!(word[0..31], [0u8; 31]); - } - - #[test] - fn bytecode_has_zero_deployed_block() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - let word = &code[578..578 + 32]; - assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); - } - - #[test] - fn only_two_storage_slots_for_standard_config() { - let contract = build(&test_config(), 1234); - // Should have exactly 2 storage entries: _initialized (slot 0) and _owner (slot 51) - assert_eq!(contract.storage.len(), 2); - } - - #[test] - #[ignore = "requires forge CLI"] - fn merkle_tree_hook_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "MerkleTreeHook", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(MERKLE_TREE_HOOK_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "MerkleTreeHook bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 72b5fcae..9e61b58f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,13 +1,8 @@ //! Contract bytecode and storage encoding. pub(crate) mod admin_proxy; -pub(crate) mod fee_vault; pub(crate) mod immutables; -pub(crate) mod mailbox; -pub(crate) mod merkle_tree_hook; -pub(crate) mod noop_ism; pub(crate) mod permit2; -pub(crate) mod protocol_fee; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/noop_ism.rs b/bin/ev-deployer/src/contracts/noop_ism.rs deleted file mode 100644 index 9f17efe0..00000000 --- a/bin/ev-deployer/src/contracts/noop_ism.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! `NoopIsm` bytecode encoding. -//! -//! `NoopIsm` is a Hyperlane Interchain Security Module (ISM) that accepts all -//! messages without verification — `verify` always returns `true`. -//! -//! ## Immutables -//! -//! None. -//! -//! ## Storage layout -//! -//! None. - -use crate::{config::NoopIsmConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes}; -use std::collections::BTreeMap; - -/// `NoopIsm` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode -/// ``` -const NOOP_ISM_BYTECODE: &[u8] = &hex!("608060405234801561001057600080fd5b50600436106100415760003560e01c80636465e69f1461004657806393c4484714610065578063f7e83aee146100ae575b600080fd5b61004e600681565b60405160ff90911681526020015b60405180910390f35b6100a16040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161005c91906100d6565b6100c66100bc36600461018c565b6001949350505050565b604051901515815260200161005c565b60006020808352835180602085015260005b81811015610104578581018301518582016040015282016100e8565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008083601f84011261015557600080fd5b50813567ffffffffffffffff81111561016d57600080fd5b60208301915083602082850101111561018557600080fd5b9250929050565b600080600080604085870312156101a257600080fd5b843567ffffffffffffffff808211156101ba57600080fd5b6101c688838901610143565b909650945060208701359150808211156101df57600080fd5b506101ec87828801610143565b9598949750955050505056"); - -/// Build a genesis alloc entry for `NoopIsm`. -pub(crate) fn build(config: &NoopIsmConfig) -> GenesisContract { - GenesisContract { - address: config.address, - code: Bytes::from(NOOP_ISM_BYTECODE.to_vec()), - storage: BTreeMap::new(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> NoopIsmConfig { - NoopIsmConfig { - address: address!("0000000000000000000000000000000000001300"), - } - } - - #[test] - fn storage_is_empty() { - let contract = build(&test_config()); - assert!( - contract.storage.is_empty(), - "NoopIsm should have no storage" - ); - } - - #[test] - fn bytecode_is_present() { - let contract = build(&test_config()); - assert!( - !contract.code.is_empty(), - "NoopIsm should have non-empty bytecode" - ); - } - - #[test] - #[ignore = "requires forge CLI"] - fn noop_ism_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "NoopIsm", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(NOOP_ISM_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "NoopIsm bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs deleted file mode 100644 index d7b2135c..00000000 --- a/bin/ev-deployer/src/contracts/protocol_fee.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! `ProtocolFee` bytecode and storage encoding. -//! -//! `ProtocolFee` is a Hyperlane post-dispatch hook that charges a protocol fee -//! on message dispatches. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |-------------------|---------|-----------------| -//! | `MAX_PROTOCOL_FEE`| uint256 | \[655, 2248\] | -//! -//! ## Storage layout (from `forge inspect ProtocolFee storageLayout`) -//! -//! | Slot | Variable | Type | -//! |------|---------------|---------| -//! | 0 | `_owner` | address | -//! | 1 | `protocolFee` | uint256 | -//! | 2 | `beneficiary` | address | - -use crate::{ - config::ProtocolFeeConfig, - contracts::{ - immutables::{patch_u256, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `ProtocolFee` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode -/// ``` -const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); - -// ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── - -/// `MAX_PROTOCOL_FEE` (uint256) — maximum fee that can be set. -const MAX_PROTOCOL_FEE_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 655, - length: 32, - }, - ImmutableRef { - start: 2248, - length: 32, - }, -]; - -/// Build a genesis alloc entry for `ProtocolFee`. -pub(crate) fn build(config: &ProtocolFeeConfig) -> GenesisContract { - let mut bytecode = PROTOCOL_FEE_BYTECODE.to_vec(); - - // Patch immutables - patch_u256( - &mut bytecode, - MAX_PROTOCOL_FEE_REFS, - U256::from(config.max_protocol_fee), - ); - - let mut storage = BTreeMap::new(); - - // Slot 0: _owner - if !config.owner.is_zero() { - storage.insert( - B256::ZERO, - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // Slot 1: protocolFee - if config.protocol_fee > 0 { - storage.insert( - B256::from(U256::from(1u64)), - B256::from(U256::from(config.protocol_fee)), - ); - } - - // Slot 2: beneficiary - if !config.beneficiary.is_zero() { - storage.insert( - B256::from(U256::from(2u64)), - B256::from(U256::from_be_bytes(config.beneficiary.into_word().0)), - ); - } - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> ProtocolFeeConfig { - ProtocolFeeConfig { - address: address!("0000000000000000000000000000000000001300"), - owner: address!("000000000000000000000000000000000000ad00"), - max_protocol_fee: 1_000_000_000_000_000_000, - protocol_fee: 100_000, - beneficiary: address!("000000000000000000000000000000000000be00"), - } - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config()); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::ZERO], expected); - } - - #[test] - fn bytecode_is_patched_with_max_protocol_fee() { - let config = test_config(); - let contract = build(&config); - let code = contract.code.to_vec(); - - let expected = B256::from(U256::from(config.max_protocol_fee)); - for &offset in &[655, 2248] { - let word = &code[offset..offset + 32]; - assert_eq!( - word, - expected.as_slice(), - "max_protocol_fee not patched at offset {offset}" - ); - } - } - - #[test] - fn zero_protocol_fee_omits_slot_1() { - let config = ProtocolFeeConfig { - address: address!("0000000000000000000000000000000000001300"), - owner: address!("000000000000000000000000000000000000ad00"), - max_protocol_fee: 1_000_000_000_000_000_000, - protocol_fee: 0, - beneficiary: address!("000000000000000000000000000000000000be00"), - }; - let contract = build(&config); - let fee_slot = B256::from(U256::from(1u64)); - assert!( - !contract.storage.contains_key(&fee_slot), - "zero protocol_fee should not produce a storage entry" - ); - } - - #[test] - fn storage_count_for_standard_config() { - let contract = build(&test_config()); - // Should have exactly 3 storage entries: _owner (slot 0), protocolFee (slot 1), beneficiary (slot 2) - assert_eq!(contract.storage.len(), 3); - } - - #[test] - #[ignore = "requires forge CLI"] - fn protocol_fee_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "ProtocolFee", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(PROTOCOL_FEE_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "ProtocolFee bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index c3d7e926..e270b697 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -17,38 +17,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } - if let Some(ref fv_config) = config.contracts.fee_vault { - let contract = contracts::fee_vault::build(fv_config); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref mth_config) = config.contracts.merkle_tree_hook { - let local_domain = config.chain.chain_id as u32; - let contract = contracts::merkle_tree_hook::build(mth_config, local_domain); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref mb_config) = config.contracts.mailbox { - let local_domain = config.chain.chain_id as u32; - let contract = contracts::mailbox::build(mb_config, local_domain); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref ni_config) = config.contracts.noop_ism { - let contract = contracts::noop_ism::build(ni_config); - insert_contract(&mut alloc, &contract); - } - if let Some(ref p2_config) = config.contracts.permit2 { let contract = contracts::permit2::build(p2_config, config.chain.chain_id); insert_contract(&mut alloc, &contract); } - if let Some(ref pf_config) = config.contracts.protocol_fee { - let contract = contracts::protocol_fee::build(pf_config); - insert_contract(&mut alloc, &contract); - } - Value::Object(alloc) } @@ -134,12 +107,7 @@ mod tests { address: address!("000000000000000000000000000000000000ad00"), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), - fee_vault: None, - merkle_tree_hook: None, - mailbox: None, - noop_ism: None, permit2: None, - protocol_fee: None, }, } } diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 97f8eeb9..e44eb6fb 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -15,44 +15,6 @@ chain_id = 0 # address = "0x000000000000000000000000000000000000Ad00" # owner = "0x..." -# FeeVault: fee vault with Hyperlane bridging support. -# [contracts.fee_vault] -# address = "0x000000000000000000000000000000000000FE00" -# owner = "0x..." -# destination_domain = 0 -# recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -# minimum_amount = 0 -# call_fee = 0 -# bridge_share_bps = 10000 -# other_recipient = "0x0000000000000000000000000000000000000000" -# hyp_native_minter = "0x0000000000000000000000000000000000000000" - -# Mailbox: Hyperlane core messaging hub. -# [contracts.mailbox] -# address = "0x0000000000000000000000000000000000001200" -# owner = "0x..." -# default_ism = "0x0000000000000000000000000000000000000000" -# default_hook = "0x0000000000000000000000000000000000000000" -# required_hook = "0x0000000000000000000000000000000000000000" - -# MerkleTreeHook: Hyperlane required hook (Merkle tree for messages). -# [contracts.merkle_tree_hook] -# address = "0x0000000000000000000000000000000000001100" -# owner = "0x..." -# mailbox = "0x0000000000000000000000000000000000001200" - -# NoopIsm: Hyperlane ISM that accepts all messages. -# [contracts.noop_ism] -# address = "0x0000000000000000000000000000000000001300" - # Permit2: Uniswap canonical token approval manager. # [contracts.permit2] # address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" - -# ProtocolFee: Hyperlane post-dispatch hook that charges a protocol fee. -# [contracts.protocol_fee] -# address = "0x0000000000000000000000000000000000001400" -# owner = "0x..." -# max_protocol_fee = 1000000000000000000 -# protocol_fee = 0 -# beneficiary = "0x..." diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 8efe0d0c..5d87ae9b 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -119,42 +119,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, - "fee_vault" => cfg - .contracts - .fee_vault - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("fee_vault not configured"))?, - "merkle_tree_hook" => cfg - .contracts - .merkle_tree_hook - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, - "mailbox" => cfg - .contracts - .mailbox - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("mailbox not configured"))?, - "noop_ism" => cfg - .contracts - .noop_ism - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("noop_ism not configured"))?, "permit2" => cfg .contracts .permit2 .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, - "protocol_fee" => cfg - .contracts - .protocol_fee - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("protocol_fee not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 5e97dd4d..f683377a 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -14,34 +14,6 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - if let Some(ref fv) = config.contracts.fee_vault { - manifest.insert( - "fee_vault".to_string(), - Value::String(format!("{}", fv.address)), - ); - } - - if let Some(ref mth) = config.contracts.merkle_tree_hook { - manifest.insert( - "merkle_tree_hook".to_string(), - Value::String(format!("{}", mth.address)), - ); - } - - if let Some(ref mb) = config.contracts.mailbox { - manifest.insert( - "mailbox".to_string(), - Value::String(format!("{}", mb.address)), - ); - } - - if let Some(ref ni) = config.contracts.noop_ism { - manifest.insert( - "noop_ism".to_string(), - Value::String(format!("{}", ni.address)), - ); - } - if let Some(ref p2) = config.contracts.permit2 { manifest.insert( "permit2".to_string(), @@ -49,12 +21,5 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - if let Some(ref pf) = config.contracts.protocol_fee { - manifest.insert( - "protocol_fee".to_string(), - Value::String(format!("{}", pf.address)), - ); - } - Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index b1f318b3..b7c085ae 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -79,10 +79,6 @@ echo "Genesis written to $GENESIS" # Quick sanity: address should be in the alloc grep -qi "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" -grep -qi "000000000000000000000000000000000000FE00" "$GENESIS" \ - || fail "FeeVault address not found in genesis" -grep -qi "0000000000000000000000000000000000001100" "$GENESIS" \ - || fail "MerkleTreeHook address not found in genesis" grep -qi "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ || fail "Permit2 address not found in genesis" @@ -131,117 +127,7 @@ expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cff || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" pass "AdminProxy owner slot 0 = $ADMIN_OWNER" -# ── Step 5: Verify FeeVault ────────────────────────────── - -FEE_VAULT="0x000000000000000000000000000000000000FE00" -FEE_VAULT_OWNER="0x000000000000000000000000000000000000Ad00" - -echo "=== Verifying FeeVault at $FEE_VAULT ===" - -# Check code is present -fv_code=$(rpc_call "eth_getCode" "[\"$FEE_VAULT\", \"latest\"]") -[[ "$fv_code" != "0x" && "$fv_code" != "0x0" && ${#fv_code} -gt 10 ]] \ - || fail "FeeVault has no bytecode (got: $fv_code)" -pass "FeeVault has bytecode (${#fv_code} hex chars)" - -# Slot 0: hypNativeMinter (should be zero) -fv_slot0=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x0\", \"latest\"]") -expected_zero="0x0000000000000000000000000000000000000000000000000000000000000000" -[[ "$(echo "$fv_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_zero" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 0 (hypNativeMinter) should be zero, got $fv_slot0" -pass "FeeVault slot 0 (hypNativeMinter) = zero" - -# Slot 1: owner (lower 160 bits) + destinationDomain (upper bits) -# With domain=0 and owner=0x...Ad00, it's just the owner padded -fv_slot1=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x1\", \"latest\"]") -expected_slot1="0x000000000000000000000000000000000000000000000000000000000000ad00" -[[ "$(echo "$fv_slot1" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot1" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 1 (owner|domain) mismatch: got $fv_slot1, expected $expected_slot1" -pass "FeeVault slot 1 (owner|domain) correct" - -# Slot 6: bridgeShareBps = 10000 = 0x2710 -fv_slot6=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x6\", \"latest\"]") -expected_slot6="0x0000000000000000000000000000000000000000000000000000000000002710" -[[ "$(echo "$fv_slot6" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot6" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" -pass "FeeVault slot 6 (bridgeShareBps) = 10000" - -# ── Step 6: Verify MerkleTreeHook ──────────────────────── - -MERKLE_TREE_HOOK="0x0000000000000000000000000000000000001100" -MERKLE_TREE_HOOK_OWNER="0x000000000000000000000000000000000000Ad00" -MERKLE_TREE_HOOK_MAILBOX="0x0000000000000000000000000000000000001200" - -echo "=== Verifying MerkleTreeHook at $MERKLE_TREE_HOOK ===" - -# Check code is present -mth_code=$(rpc_call "eth_getCode" "[\"$MERKLE_TREE_HOOK\", \"latest\"]") -[[ "$mth_code" != "0x" && "$mth_code" != "0x0" && ${#mth_code} -gt 10 ]] \ - || fail "MerkleTreeHook has no bytecode (got: $mth_code)" -pass "MerkleTreeHook has bytecode (${#mth_code} hex chars)" - -# Compare full bytecode against genesis JSON -# Extract expected code from genesis for the MerkleTreeHook address -expected_mth_code=$(python3 -c " -import json, sys -with open('$GENESIS') as f: - genesis = json.load(f) -alloc = genesis['alloc'] -# Address key is checksummed without 0x prefix -entry = alloc.get('0000000000000000000000000000000000001100') -print(entry['code']) -") -[[ "$(echo "$mth_code" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_mth_code" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook bytecode from node does not match genesis JSON" -pass "MerkleTreeHook bytecode matches genesis JSON" - -# Slot 0: _initialized = 1 (OZ v4 Initializable) -mth_slot0=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x0\", \"latest\"]") -expected_init="0x0000000000000000000000000000000000000000000000000000000000000001" -[[ "$(echo "$mth_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_init" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook slot 0 (_initialized) mismatch: got $mth_slot0, expected $expected_init" -pass "MerkleTreeHook slot 0 (_initialized) = 1" - -# Slot 51 (0x33): _owner -mth_slot51=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x33\", \"latest\"]") -expected_owner="0x000000000000000000000000000000000000000000000000000000000000ad00" -[[ "$(echo "$mth_slot51" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook slot 51 (_owner) mismatch: got $mth_slot51, expected $expected_owner" -pass "MerkleTreeHook slot 51 (_owner) = $MERKLE_TREE_HOOK_OWNER" - -# Verify immutables are patched in bytecode: -# mailbox address at byte offsets 904 and 3300 (each is a 32-byte word, address in last 20 bytes) -# localDomain (chain_id=1234=0x04d2) at byte offset 644 -# The hex string has "0x" prefix, so byte N in the bytecode = hex chars at positions 2+2N..2+2N+2 -mth_hex="${mth_code#0x}" - -check_immutable() { - local name="$1" - local byte_offset="$2" - local expected_hex="$3" - local hex_offset=$((byte_offset * 2)) - local hex_len=${#expected_hex} - local actual="${mth_hex:$hex_offset:$hex_len}" - [[ "$(echo "$actual" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_hex" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "$name at byte offset $byte_offset mismatch: got $actual, expected $expected_hex" - pass "$name patched correctly at byte offset $byte_offset" -} - -# mailbox = 0x...1200 → 32-byte word with address in last 20 bytes -# Full 32-byte word: 000000000000000000000000 + 0000000000000000000000000000000000001200 -mailbox_word="0000000000000000000000000000000000000000000000000000000000001200" -check_immutable "mailbox" 904 "$mailbox_word" -check_immutable "mailbox (second ref)" 3300 "$mailbox_word" - -# localDomain = chain_id 1234 = 0x04d2 → 32-byte word -domain_word="00000000000000000000000000000000000000000000000000000000000004d2" -check_immutable "localDomain" 644 "$domain_word" - -# deployedBlock = 0 → 32 zero bytes -deployed_block_word="0000000000000000000000000000000000000000000000000000000000000000" -check_immutable "deployedBlock" 578 "$deployed_block_word" - -# ── Step 7: Verify Permit2 ───────────────────────────── +# ── Step 5: Verify Permit2 ────────────────────────────── PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3" From 04beb6bebf6f3938f3e839203677fe0d8cd22bf8 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 13:55:52 +0100 Subject: [PATCH 41/81] refactor(contracts): remove Hyperlane dependency from FeeVault Remove IHypNativeMinter interface and all Hyperlane-specific fields (destinationDomain, recipientAddress) from FeeVault. The contract now uses direct ETH transfers to a configurable bridgeRecipient instead of Hyperlane's transferRemote(). sendToCelestia() renamed to distribute(). Also removes the hyperlane-monorepo git submodule and updates all deployment scripts, tests, and documentation. --- .claude/skills/contracts.md | 8 +- .gitmodules | 3 - contracts/README.md | 53 +++----- contracts/foundry.lock | 3 + contracts/lib/hyperlane-monorepo | 1 - contracts/script/DeployFeeVault.s.sol | 18 +-- contracts/script/GenerateFeeVaultAlloc.s.sol | 54 +++----- contracts/src/FeeVault.sol | 78 +++-------- contracts/test/AdminProxy.t.sol | 2 - contracts/test/FeeVault.t.sol | 128 ++++++------------- docs/contracts/fee_vault.md | 60 ++++----- docs/guide/fee-systems.md | 8 +- 12 files changed, 132 insertions(+), 284 deletions(-) delete mode 160000 contracts/lib/hyperlane-monorepo diff --git a/.claude/skills/contracts.md b/.claude/skills/contracts.md index 292b7c8a..d93142f2 100644 --- a/.claude/skills/contracts.md +++ b/.claude/skills/contracts.md @@ -1,5 +1,5 @@ --- -description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee bridging to Celestia", "Hyperlane integration", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and bridged. +description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee distribution", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and distributed. --- # Contracts Onboarding @@ -9,13 +9,13 @@ description: This skill should be used when the user asks about "ev-reth contrac The contracts live in `contracts/` and use Foundry for development. There are two main contracts: 1. **AdminProxy** (`src/AdminProxy.sol`) - Bootstrap contract for admin addresses at genesis -2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees, bridges to Celestia via Hyperlane (cross-chain messaging protocol) +2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees and distributes them between configured recipients ## Key Files ### Contract Sources - `contracts/src/AdminProxy.sol` - Transparent proxy pattern for admin control -- `contracts/src/FeeVault.sol` - Fee collection and bridging logic +- `contracts/src/FeeVault.sol` - Fee collection and distribution logic ### Deployment Scripts - `contracts/script/DeployFeeVault.s.sol` - FeeVault deployment with CREATE2 @@ -34,7 +34,7 @@ The AdminProxy contract provides a bootstrap mechanism for setting admin address ### FeeVault The FeeVault serves as the destination for redirected base fees (instead of burning them). Key responsibilities: - Receive base fees from block production -- Bridge accumulated fees to Celestia via Hyperlane +- Distribute accumulated fees between configured recipients - Manage withdrawal permissions ## Connection to Rust Code diff --git a/.gitmodules b/.gitmodules index 0cebd2a9..9df9f949 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "contracts/lib/forge-std"] path = contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "contracts/lib/hyperlane-monorepo"] - path = contracts/lib/hyperlane-monorepo - url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git [submodule "contracts/lib/permit2"] path = contracts/lib/permit2 url = https://github.com/Uniswap/permit2 diff --git a/contracts/README.md b/contracts/README.md index 4c6e0a37..a04e3260 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,6 +1,6 @@ # EV-Reth Contracts -Smart contracts for EV-Reth, including the FeeVault for bridging collected fees to Celestia. +Smart contracts for EV-Reth, including the FeeVault for collecting and distributing fees. ## AdminProxy @@ -10,11 +10,11 @@ See [AdminProxy documentation](../docs/contracts/admin_proxy.md) for detailed se ## FeeVault -The FeeVault contract collects base fees and bridges them to Celestia via Hyperlane. It supports: +The FeeVault contract collects base fees and distributes them between a bridge recipient and an optional secondary recipient. It supports: - Configurable fee splitting between bridge and another recipient -- Minimum amount thresholds before bridging -- Call fee for incentivizing bridge calls +- Minimum amount thresholds before distributing +- Call fee for incentivizing distribution calls - Owner-controlled configuration ## Prerequisites @@ -45,17 +45,14 @@ All configuration is set via constructor arguments at deploy time: |----------|----------|-------------| | `OWNER` | Yes | Owner address (can configure the vault post-deployment) | | `SALT` | No | CREATE2 salt (default: `0x0`). Use any bytes32 value | -| `DESTINATION_DOMAIN` | Yes* | Hyperlane destination chain ID | -| `RECIPIENT_ADDRESS` | Yes* | Recipient on destination chain (bytes32, left-padded) | -| `MINIMUM_AMOUNT` | No | Minimum wei to bridge (default: 0) | -| `CALL_FEE` | No | Fee in wei for calling `sendToCelestia()` (default: 0) | +| `MINIMUM_AMOUNT` | No | Minimum wei to distribute (default: 0) | +| `CALL_FEE` | No | Fee in wei for calling `distribute()` (default: 0) | | `BRIDGE_SHARE_BPS` | No | Basis points to bridge (default: 10000 = 100%) | -| `OTHER_RECIPIENT` | No** | Address to receive non-bridged portion | +| `OTHER_RECIPIENT` | No* | Address to receive non-bridged portion | -*Required for the vault to be operational (can be set to 0 at deploy and configured later via setters) -**Required if `BRIDGE_SHARE_BPS` < 10000 +*Required if `BRIDGE_SHARE_BPS` < 10000 -**Note:** `HYP_NATIVE_MINTER` must be set via `setHypNativeMinter()` after deployment for the vault to be operational. +**Note:** `BRIDGE_RECIPIENT` must be set via `setBridgeRecipient()` after deployment for the vault to be operational. ### Choosing a Salt @@ -89,8 +86,6 @@ export OWNER=0xYourOwnerAddress export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 # Optional - configure at deploy time (can also be set later) -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x000000000000000000000000... # bytes32, left-padded cosmos address export MINIMUM_AMOUNT=1000000000000000000 # 1 ETH in wei export CALL_FEE=100000000000000 # 0.0001 ETH export BRIDGE_SHARE_BPS=8000 # 80% to bridge @@ -107,43 +102,25 @@ forge script script/DeployFeeVault.s.sol:DeployFeeVault \ --broadcast ``` -### Post-Deployment: Set HypNativeMinter +### Post-Deployment: Set Bridge Recipient -After deploying the HypNativeMinter contract, link it to the FeeVault: +After deployment, set the bridge recipient address: ```shell -cast send "setHypNativeMinter(address)" \ +cast send "setBridgeRecipient(address)" \ --rpc-url \ --private-key ``` -### Converting Cosmos Addresses to bytes32 - -The `recipientAddress` must be a bytes32. To convert a bech32 Cosmos address: - -1. Decode the bech32 to get the 20-byte address -2. Left-pad with zeros to 32 bytes - -Example using cast: - -```shell -# Left-pad a 20-byte address to 32 bytes -cast pad --left --len 32 1234567890abcdef1234567890abcdef12345678 -# Output: 0x0000000000000000000000001234567890abcdef1234567890abcdef12345678 -``` - -Note: When calling `transferRemote()` via cast, you may need to omit the `0x` prefix depending on your invocation method. - ## Admin Functions All functions are owner-only: | Function | Description | |----------|-------------| -| `setHypNativeMinter(address)` | Set the Hyperlane minter contract | -| `setRecipient(uint32, bytes32)` | Set destination domain and recipient | -| `setMinimumAmount(uint256)` | Set minimum amount to bridge | -| `setCallFee(uint256)` | Set fee for calling sendToCelestia | +| `setBridgeRecipient(address)` | Set the bridge recipient address | +| `setMinimumAmount(uint256)` | Set minimum amount to distribute | +| `setCallFee(uint256)` | Set fee for calling distribute | | `setBridgeShare(uint256)` | Set bridge percentage (basis points) | | `setOtherRecipient(address)` | Set recipient for non-bridged funds | | `transferOwnership(address)` | Transfer contract ownership | diff --git a/contracts/foundry.lock b/contracts/foundry.lock index aee2c9a8..19f75092 100644 --- a/contracts/foundry.lock +++ b/contracts/foundry.lock @@ -1,5 +1,8 @@ { "lib/forge-std": { "rev": "887e87251562513a7b5ab1ea517c039fe6ee0984" + }, + "lib/permit2": { + "rev": "cc56ad0f3439c502c246fc5cfcc3db92bb8b7219" } } \ No newline at end of file diff --git a/contracts/lib/hyperlane-monorepo b/contracts/lib/hyperlane-monorepo deleted file mode 160000 index bc401f7a..00000000 --- a/contracts/lib/hyperlane-monorepo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bc401f7a64f9e43aa25265dba12d80a33a19de21 diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index 54b7e0f8..4c6f7624 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -10,8 +10,6 @@ contract DeployFeeVault is Script { address owner = vm.envAddress("OWNER"); bytes32 salt = vm.envOr("SALT", bytes32(0)); - uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); // 0 defaults to 10000 in constructor @@ -21,34 +19,28 @@ contract DeployFeeVault is Script { vm.startBroadcast(); // Deploy FeeVault with CREATE2 - FeeVault feeVault = new FeeVault{salt: salt}( - owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient - ); + FeeVault feeVault = + new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); vm.stopBroadcast(); console.log("FeeVault deployed at:", address(feeVault)); console.log("Owner:", owner); - console.log("Destination domain:", destinationDomain); console.log("Minimum amount:", minimumAmount); console.log("Call fee:", callFee); console.log("Bridge share bps:", feeVault.bridgeShareBps()); console.log(""); - console.log("NOTE: Call setHypNativeMinter() after deploying HypNativeMinter"); + console.log("NOTE: Call setBridgeRecipient() to set the bridge destination"); } } /// @notice Compute FeeVault CREATE2 address off-chain -/// @dev Use this to predict the address before deploying -/// Requires env vars: DEPLOYER (EOA), OWNER, SALT (optional), and all constructor args contract ComputeFeeVaultAddress is Script { function run() external view { address deployer = vm.envAddress("DEPLOYER"); bytes32 salt = vm.envOr("SALT", bytes32(0)); address owner = vm.envAddress("OWNER"); - uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); @@ -57,9 +49,7 @@ contract ComputeFeeVaultAddress is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( type(FeeVault).creationCode, - abi.encode( - owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient - ) + abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) ) ); diff --git a/contracts/script/GenerateFeeVaultAlloc.s.sol b/contracts/script/GenerateFeeVaultAlloc.s.sol index 0215452f..bea158ec 100644 --- a/contracts/script/GenerateFeeVaultAlloc.s.sol +++ b/contracts/script/GenerateFeeVaultAlloc.s.sol @@ -8,27 +8,23 @@ abstract contract FeeVaultAllocBase is Script { struct Config { address feeVaultAddress; address owner; - uint32 destinationDomain; - bytes32 recipientAddress; + address bridgeRecipient; + address otherRecipient; uint256 minimumAmount; uint256 callFee; uint256 bridgeShareBpsRaw; uint256 bridgeShareBps; - address otherRecipient; - address hypNativeMinter; bytes32 salt; address deployer; } function loadConfig() internal view returns (Config memory cfg) { cfg.owner = vm.envAddress("OWNER"); - cfg.destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - cfg.recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); + cfg.bridgeRecipient = vm.envOr("BRIDGE_RECIPIENT", address(0)); + cfg.otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); cfg.minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); cfg.callFee = vm.envOr("CALL_FEE", uint256(0)); cfg.bridgeShareBpsRaw = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); - cfg.otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); - cfg.hypNativeMinter = vm.envOr("HYP_NATIVE_MINTER", address(0)); cfg.feeVaultAddress = vm.envOr("FEE_VAULT_ADDRESS", address(0)); cfg.deployer = vm.envOr("DEPLOYER", address(0)); cfg.salt = vm.envOr("SALT", bytes32(0)); @@ -43,13 +39,7 @@ abstract contract FeeVaultAllocBase is Script { abi.encodePacked( type(FeeVault).creationCode, abi.encode( - cfg.owner, - cfg.destinationDomain, - cfg.recipientAddress, - cfg.minimumAmount, - cfg.callFee, - cfg.bridgeShareBpsRaw, - cfg.otherRecipient + cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient ) ) ); @@ -64,29 +54,19 @@ abstract contract FeeVaultAllocBase is Script { function computeSlots(Config memory cfg) internal pure - returns ( - bytes32 slot0, - bytes32 slot1, - bytes32 slot2, - bytes32 slot3, - bytes32 slot4, - bytes32 slot5, - bytes32 slot6 - ) + returns (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) { - slot0 = bytes32(uint256(uint160(cfg.hypNativeMinter))); - slot1 = bytes32((uint256(cfg.destinationDomain) << 160) | uint256(uint160(cfg.owner))); - slot2 = cfg.recipientAddress; + slot0 = bytes32(uint256(uint160(cfg.owner))); + slot1 = bytes32(uint256(uint160(cfg.bridgeRecipient))); + slot2 = bytes32(uint256(uint160(cfg.otherRecipient))); slot3 = bytes32(cfg.minimumAmount); slot4 = bytes32(cfg.callFee); - slot5 = bytes32(uint256(uint160(cfg.otherRecipient))); - slot6 = bytes32(cfg.bridgeShareBps); + slot5 = bytes32(cfg.bridgeShareBps); } function addressKey(address addr) internal pure returns (string memory) { bytes memory full = bytes(vm.toString(addr)); bytes memory key = new bytes(40); - // Fixed-length copy for address key without 0x prefix. for (uint256 i = 0; i < 40; i++) { key[i] = full[i + 2]; } @@ -102,13 +82,12 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) = + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); console.log("========== FeeVault Genesis Alloc =========="); console.log("FeeVault address:", cfg.feeVaultAddress); console.log("Owner:", cfg.owner); - console.log("Destination domain:", cfg.destinationDomain); console.log("Bridge share bps (raw):", cfg.bridgeShareBpsRaw); console.log("Bridge share bps (effective):", cfg.bridgeShareBps); console.log(""); @@ -119,8 +98,8 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { if (cfg.bridgeShareBps < 10000 && cfg.otherRecipient == address(0)) { console.log("WARNING: OTHER_RECIPIENT is zero but bridge share < 10000."); } - if (cfg.hypNativeMinter == address(0)) { - console.log("NOTE: HYP_NATIVE_MINTER is zero; set it before calling sendToCelestia()."); + if (cfg.bridgeRecipient == address(0)) { + console.log("NOTE: BRIDGE_RECIPIENT is zero; set it before calling distribute()."); } console.log(""); @@ -137,8 +116,7 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { console.log(' "0x2": "%s",', vm.toString(slot2)); console.log(' "0x3": "%s",', vm.toString(slot3)); console.log(' "0x4": "%s",', vm.toString(slot4)); - console.log(' "0x5": "%s",', vm.toString(slot5)); - console.log(' "0x6": "%s"', vm.toString(slot6)); + console.log(' "0x5": "%s"', vm.toString(slot5)); console.log(" }"); console.log(" }"); console.log(" }"); @@ -157,7 +135,7 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) = + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); string memory json = string( @@ -178,8 +156,6 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { vm.toString(slot4), '","0x5":"', vm.toString(slot5), - '","0x6":"', - vm.toString(slot6), '"}}}' ) ); diff --git a/contracts/src/FeeVault.sol b/contracts/src/FeeVault.sol index 7069258c..b0ec6c10 100644 --- a/contracts/src/FeeVault.sol +++ b/contracts/src/FeeVault.sol @@ -1,35 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -interface IHypNativeMinter { - function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) - external - payable - returns (bytes32 messageId); -} - contract FeeVault { - IHypNativeMinter public hypNativeMinter; - address public owner; - uint32 public destinationDomain; - bytes32 public recipientAddress; + address public bridgeRecipient; + address public otherRecipient; uint256 public minimumAmount; uint256 public callFee; - - // Split accounting - address public otherRecipient; uint256 public bridgeShareBps; // Basis points (0-10000) for bridge share - event SentToCelestia(uint256 amount, bytes32 recipient, bytes32 messageId); + event FundsDistributed(uint256 total, uint256 bridgeAmount, uint256 otherAmount); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event HypNativeMinterUpdated(address hypNativeMinter); - event RecipientUpdated(uint32 destinationDomain, bytes32 recipientAddress); + event BridgeRecipientUpdated(address bridgeRecipient); event MinimumAmountUpdated(uint256 minimumAmount); event CallFeeUpdated(uint256 callFee); event BridgeShareUpdated(uint256 bridgeShareBps); event OtherRecipientUpdated(address otherRecipient); - event FundsSplit(uint256 totalNew, uint256 bridgeAmount, uint256 otherAmount); modifier onlyOwner() { require(msg.sender == owner, "FeeVault: caller is not the owner"); @@ -38,8 +24,6 @@ contract FeeVault { constructor( address _owner, - uint32 _destinationDomain, - bytes32 _recipientAddress, uint256 _minimumAmount, uint256 _callFee, uint256 _bridgeShareBps, @@ -49,8 +33,6 @@ contract FeeVault { require(_bridgeShareBps <= 10000, "FeeVault: invalid bps"); owner = _owner; - destinationDomain = _destinationDomain; - recipientAddress = _recipientAddress; minimumAmount = _minimumAmount; callFee = _callFee; bridgeShareBps = _bridgeShareBps == 0 ? 10000 : _bridgeShareBps; @@ -61,8 +43,8 @@ contract FeeVault { receive() external payable {} - function sendToCelestia() external payable { - require(address(hypNativeMinter) != address(0), "FeeVault: minter not set"); + function distribute() external payable { + require(bridgeRecipient != address(0), "FeeVault: bridge recipient not set"); require(msg.value >= callFee, "FeeVault: insufficient fee"); uint256 currentBalance = address(this).balance; @@ -73,20 +55,18 @@ contract FeeVault { require(bridgeAmount >= minimumAmount, "FeeVault: minimum amount not met"); - emit FundsSplit(currentBalance, bridgeAmount, otherAmount); + emit FundsDistributed(currentBalance, bridgeAmount, otherAmount); // Send other amount if any if (otherAmount > 0) { require(otherRecipient != address(0), "FeeVault: other recipient not set"); - (bool success,) = otherRecipient.call{value: otherAmount}(""); - require(success, "FeeVault: transfer failed"); + (bool sent,) = otherRecipient.call{value: otherAmount}(""); + require(sent, "FeeVault: transfer failed"); } - // Bridge the bridge amount - bytes32 messageId = - hypNativeMinter.transferRemote{value: bridgeAmount}(destinationDomain, recipientAddress, bridgeAmount); - - emit SentToCelestia(bridgeAmount, recipientAddress, messageId); + // Send bridge amount + (bool success,) = bridgeRecipient.call{value: bridgeAmount}(""); + require(success, "FeeVault: bridge transfer failed"); } // Admin functions @@ -97,10 +77,10 @@ contract FeeVault { owner = newOwner; } - function setRecipient(uint32 _destinationDomain, bytes32 _recipientAddress) external onlyOwner { - destinationDomain = _destinationDomain; - recipientAddress = _recipientAddress; - emit RecipientUpdated(_destinationDomain, _recipientAddress); + function setBridgeRecipient(address _bridgeRecipient) external onlyOwner { + require(_bridgeRecipient != address(0), "FeeVault: zero address"); + bridgeRecipient = _bridgeRecipient; + emit BridgeRecipientUpdated(_bridgeRecipient); } function setMinimumAmount(uint256 _minimumAmount) external onlyOwner { @@ -125,36 +105,18 @@ contract FeeVault { emit OtherRecipientUpdated(_otherRecipient); } - function setHypNativeMinter(address _hypNativeMinter) external onlyOwner { - require(_hypNativeMinter != address(0), "FeeVault: zero address"); - hypNativeMinter = IHypNativeMinter(_hypNativeMinter); - emit HypNativeMinterUpdated(_hypNativeMinter); - } - - /// @notice Return the full configuration currently stored in the contract. function getConfig() external view returns ( address _owner, - uint32 _destinationDomain, - bytes32 _recipientAddress, + address _bridgeRecipient, + address _otherRecipient, uint256 _minimumAmount, uint256 _callFee, - uint256 _bridgeShareBps, - address _otherRecipient, - address _hypNativeMinter + uint256 _bridgeShareBps ) { - return ( - owner, - destinationDomain, - recipientAddress, - minimumAmount, - callFee, - bridgeShareBps, - otherRecipient, - address(hypNativeMinter) - ); + return (owner, bridgeRecipient, otherRecipient, minimumAmount, callFee, bridgeShareBps); } } diff --git a/contracts/test/AdminProxy.t.sol b/contracts/test/AdminProxy.t.sol index 4404e4e7..d998ebcd 100644 --- a/contracts/test/AdminProxy.t.sol +++ b/contracts/test/AdminProxy.t.sol @@ -362,8 +362,6 @@ contract AdminProxyTest is Test { // Deploy FeeVault with proxy as owner FeeVault vault = new FeeVault( address(proxy), // proxy is owner - 1234, - bytes32(uint256(0xbeef)), 1 ether, 0.1 ether, 10000, diff --git a/contracts/test/FeeVault.t.sol b/contracts/test/FeeVault.t.sol index fdc379db..7814b339 100644 --- a/contracts/test/FeeVault.t.sol +++ b/contracts/test/FeeVault.t.sol @@ -4,71 +4,49 @@ pragma solidity ^0.8.24; import {Test, console} from "forge-std/Test.sol"; import {FeeVault} from "../src/FeeVault.sol"; -contract MockHypNativeMinter { - event TransferRemoteCalled(uint32 destination, bytes32 recipient, uint256 amount); - - function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) - external - payable - returns (bytes32 messageId) - { - require(msg.value == _amount, "MockHypNativeMinter: value mismatch"); - emit TransferRemoteCalled(_destination, _recipient, _amount); - return bytes32(uint256(1)); // Return a dummy messageId - } -} - contract FeeVaultTest is Test { FeeVault public feeVault; - MockHypNativeMinter public mockMinter; address public owner; address public user; + address public bridgeRecipient; address public otherRecipient; - uint32 public destination = 1234; - bytes32 public recipient = bytes32(uint256(0xdeadbeef)); uint256 public minAmount = 1 ether; uint256 public fee = 0.1 ether; function setUp() public { owner = address(this); user = address(0x1); + bridgeRecipient = address(0x42); otherRecipient = address(0x99); - mockMinter = new MockHypNativeMinter(); feeVault = new FeeVault( owner, - destination, - recipient, minAmount, fee, 10000, // 100% bridge share otherRecipient ); - feeVault.setHypNativeMinter(address(mockMinter)); + feeVault.setBridgeRecipient(bridgeRecipient); } - function test_GetConfig() public { + function test_GetConfig() public view { ( address cfgOwner, - uint32 cfgDestination, - bytes32 cfgRecipient, + address cfgBridgeRecipient, + address cfgOtherRecipient, uint256 cfgMinAmount, uint256 cfgCallFee, - uint256 cfgBridgeShare, - address cfgOtherRecipient, - address cfgHypNativeMinter + uint256 cfgBridgeShare ) = feeVault.getConfig(); assertEq(cfgOwner, owner); - assertEq(cfgDestination, destination); - assertEq(cfgRecipient, recipient); + assertEq(cfgBridgeRecipient, bridgeRecipient); + assertEq(cfgOtherRecipient, otherRecipient); assertEq(cfgMinAmount, minAmount); assertEq(cfgCallFee, fee); assertEq(cfgBridgeShare, 10000); - assertEq(cfgOtherRecipient, otherRecipient); - assertEq(cfgHypNativeMinter, address(mockMinter)); } function test_Receive() public { @@ -78,36 +56,28 @@ contract FeeVaultTest is Test { assertEq(address(feeVault).balance, amount, "Balance mismatch"); } - function test_SendToCelestia_100PercentBridge() public { + function test_Distribute_100PercentBridge() public { // Fund with minAmount (bool success,) = address(feeVault).call{value: minAmount}(""); require(success); uint256 totalAmount = minAmount + fee; - vm.expectEmit(true, true, true, true, address(mockMinter)); - emit MockHypNativeMinter.TransferRemoteCalled(destination, recipient, totalAmount); - - // Expect the event from FeeVault vm.expectEmit(true, true, true, true, address(feeVault)); - emit FeeVault.SentToCelestia(totalAmount, recipient, bytes32(uint256(1))); + emit FeeVault.FundsDistributed(totalAmount, totalAmount, 0); vm.prank(user); vm.deal(user, fee); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); - assertEq(address(feeVault).balance, 0, "Collector should be empty"); + assertEq(address(feeVault).balance, 0, "Vault should be empty"); + assertEq(bridgeRecipient.balance, totalAmount, "Bridge recipient should receive funds"); } - function test_SendToCelestia_Split5050() public { + function test_Distribute_Split5050() public { // Set split to 50% feeVault.setBridgeShare(5000); - // Fund with 2 ether. - // Fee is 0.1 ether. - // Total new funds = 2.1 ether. - // Bridge = 1.05 ether. Other = 1.05 ether. - // Min amount is 1 ether, so 1.05 >= 1.0 is OK. uint256 fundAmount = 2 ether; (bool success,) = address(feeVault).call{value: fundAmount}(""); require(success); @@ -116,46 +86,47 @@ contract FeeVaultTest is Test { uint256 expectedBridge = totalNew / 2; uint256 expectedOther = totalNew - expectedBridge; - vm.expectEmit(true, true, true, true, address(mockMinter)); - emit MockHypNativeMinter.TransferRemoteCalled(destination, recipient, expectedBridge); - vm.prank(user); vm.deal(user, fee); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); - assertEq(address(feeVault).balance, 0, "Collector should be empty"); + assertEq(address(feeVault).balance, 0, "Vault should be empty"); + assertEq(bridgeRecipient.balance, expectedBridge, "Bridge recipient should receive funds"); assertEq(otherRecipient.balance, expectedOther, "Other recipient should receive funds"); } - function test_SendToCelestia_InsufficientFee() public { + function test_Distribute_InsufficientFee() public { vm.prank(user); vm.deal(user, fee); - // Send less than fee vm.expectRevert("FeeVault: insufficient fee"); - feeVault.sendToCelestia{value: fee - 1}(); + feeVault.distribute{value: fee - 1}(); } - function test_SendToCelestia_BelowMinAmount_AfterSplit() public { + function test_Distribute_BelowMinAmount_AfterSplit() public { feeVault.setBridgeShare(1000); // 10% bridge - // Fund with 2 ether. Total 2.1. - // Bridge = 0.21. Other = 1.89. - // Min amount is 1.0. 0.21 < 1.0. Should revert. (bool success,) = address(feeVault).call{value: 2 ether}(""); require(success); vm.prank(user); vm.deal(user, fee); vm.expectRevert("FeeVault: minimum amount not met"); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); } - function test_AdminFunctions() public { - // Test setRecipient - feeVault.setRecipient(5678, bytes32(uint256(0xbeef))); - assertEq(feeVault.destinationDomain(), 5678); - assertEq(feeVault.recipientAddress(), bytes32(uint256(0xbeef))); + function test_Distribute_BridgeRecipientNotSet() public { + FeeVault freshVault = new FeeVault(owner, minAmount, fee, 10000, otherRecipient); + (bool success,) = address(freshVault).call{value: minAmount}(""); + require(success); + + vm.prank(user); + vm.deal(user, fee); + vm.expectRevert("FeeVault: bridge recipient not set"); + freshVault.distribute{value: fee}(); + } + + function test_AdminFunctions() public { // Test setMinimumAmount feeVault.setMinimumAmount(5 ether); assertEq(feeVault.minimumAmount(), 5 ether); @@ -190,10 +161,6 @@ contract FeeVaultTest is Test { } function test_AdminAccessControl() public { - vm.prank(user); - vm.expectRevert("FeeVault: caller is not the owner"); - feeVault.setRecipient(1, bytes32(0)); - vm.prank(user); vm.expectRevert("FeeVault: caller is not the owner"); feeVault.setMinimumAmount(1); @@ -216,30 +183,17 @@ contract FeeVaultTest is Test { vm.prank(user); vm.expectRevert("FeeVault: caller is not the owner"); - feeVault.setHypNativeMinter(address(0x123)); + feeVault.setBridgeRecipient(address(0x123)); } - function test_SetHypNativeMinter() public { - MockHypNativeMinter newMinter = new MockHypNativeMinter(); - feeVault.setHypNativeMinter(address(newMinter)); - assertEq(address(feeVault.hypNativeMinter()), address(newMinter)); + function test_SetBridgeRecipient() public { + address newRecipient = address(0x55); + feeVault.setBridgeRecipient(newRecipient); + assertEq(feeVault.bridgeRecipient(), newRecipient); } - function test_SetHypNativeMinter_ZeroAddress() public { + function test_SetBridgeRecipient_ZeroAddress() public { vm.expectRevert("FeeVault: zero address"); - feeVault.setHypNativeMinter(address(0)); - } - - function test_SendToCelestia_MinterNotSet() public { - // Deploy fresh vault without minter - FeeVault freshVault = new FeeVault(owner, destination, recipient, minAmount, fee, 10000, otherRecipient); - - (bool success,) = address(freshVault).call{value: minAmount}(""); - require(success); - - vm.prank(user); - vm.deal(user, fee); - vm.expectRevert("FeeVault: minter not set"); - freshVault.sendToCelestia{value: fee}(); + feeVault.setBridgeRecipient(address(0)); } } diff --git a/docs/contracts/fee_vault.md b/docs/contracts/fee_vault.md index ed4ffe54..a0569e15 100644 --- a/docs/contracts/fee_vault.md +++ b/docs/contracts/fee_vault.md @@ -2,33 +2,32 @@ ## Overview -The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between bridging to a specific destination chain (e.g., Celestia) and sending to a secondary recipient. +The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between a bridge recipient and a secondary recipient. ## Use Case -This contract serves as a **fee sink** and **bridging mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to another ecosystem while retaining a portion for other purposes (e.g., developer rewards, treasury). +This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to configured recipients while retaining a portion for other purposes (e.g., developer rewards, treasury). 1. **Fee Accumulation**: The contract receives funds from: - **Base Fee Redirect**: The chain's execution layer (e.g., `ev-revm`) can be configured to direct burned base fees directly to this contract's address. - **Direct Transfers**: Anyone can send native tokens to the contract via the `receive()` function. -2. **Splitting & Bridging**: Once sufficient funds have accumulated, any user can trigger the `sendToCelestia()` function. This splits the funds based on a configured percentage: - - **Bridge Share**: Sent to the destination chain (Celestia) via the `HypNativeMinter`. +2. **Splitting & Distribution**: Once sufficient funds have accumulated, any user can trigger the `distribute()` function. This splits the funds based on a configured percentage: + - **Bridge Share**: Sent to the configured `bridgeRecipient`. - **Other Share**: Immediately transferred to a configured `otherRecipient` address. ## Architecture ### Core Components -- **HypNativeMinter Integration**: The contract interacts with a Hyperlane `HypNativeMinter` to handle the cross-chain transfer logic. +- **Split Logic**: Configurable basis-point split between bridge and secondary recipient. - **Admin Controls**: An `owner` manages critical parameters to ensure security and flexibility. ### Key Features -- **Automatic Splitting**: Funds are split automatically upon calling `sendToCelestia`. No manual withdrawal is required for the secondary recipient. -- **Stored Recipient**: The destination domain (Chain ID) and recipient address are stored in the contract state. -- **Minimum Threshold**: A `minimumAmount` ensures that bridging only occurs when it is economically viable. -- **Caller Incentive/Fee**: A `callFee` is required to trigger the bridge function. +- **Automatic Splitting**: Funds are split automatically upon calling `distribute`. No manual withdrawal is required for the secondary recipient. +- **Minimum Threshold**: A `minimumAmount` ensures that distribution only occurs when it is economically viable. +- **Caller Incentive/Fee**: A `callFee` is required to trigger the distribution function. ## Workflow @@ -38,7 +37,7 @@ This contract serves as a **fee sink** and **bridging mechanism** for a rollup o 2. **Trigger Phase**: - A keeper or user notices the bridge portion exceeds `minimumAmount`. - - They call `sendToCelestia{value: callFee}()`. + - They call `distribute{value: callFee}()`. - The contract checks: - `msg.value >= callFee` - `bridgeAmount >= minimumAmount` @@ -46,19 +45,18 @@ This contract serves as a **fee sink** and **bridging mechanism** for a rollup o 3. **Execution Phase**: - The contract calculates the split based on `bridgeShareBps`. - **Other Share**: Transferred immediately to `otherRecipient`. - - **Bridge Share**: Bridged to Celestia via `hypNativeMinter.transferRemote`. - - `SentToCelestia` and `FundsSplit` events are emitted. + - **Bridge Share**: Sent to `bridgeRecipient`. + - `FundsDistributed` event is emitted. ## Configuration Parameters | Parameter | Description | Managed By | |-----------|-------------|------------| -| `destinationDomain` | Hyperlane domain ID of the target chain (e.g., Celestia). | Owner | -| `recipientAddress` | Address on the target chain to receive funds. | Owner | -| `minimumAmount` | Minimum bridge amount required to trigger a bridge tx. | Owner | +| `bridgeRecipient` | Address to receive the bridge share of funds. | Owner | +| `otherRecipient` | Address to receive the non-bridged portion of funds. | Owner | +| `minimumAmount` | Minimum bridge amount required to trigger distribution. | Owner | | `callFee` | Fee required from the caller to execute the function. | Owner | | `bridgeShareBps` | Basis points (0-10000) determining the % of funds to bridge. | Owner | -| `otherRecipient` | Address to receive the non-bridged portion of funds. | Owner | ## Embedding FeeVault in Genesis @@ -72,8 +70,6 @@ If you want a deterministic address across chains, compute the CREATE2 address a export OWNER=0xYourOwnerOrAdminProxy export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 export DEPLOYER=0xYourDeployerAddress -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x0000000000000000000000000000000000000000000000000000000000000000 export MINIMUM_AMOUNT=0 export CALL_FEE=0 export BRIDGE_SHARE_BPS=10000 @@ -106,13 +102,11 @@ export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 export FEE_VAULT_ADDRESS=0xYourFeeVaultAddress # Optional configuration (defaults to zero) -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x0000000000000000000000000000000000000000000000000000000000000000 +export BRIDGE_RECIPIENT=0x0000000000000000000000000000000000000000 +export OTHER_RECIPIENT=0x0000000000000000000000000000000000000000 export MINIMUM_AMOUNT=0 export CALL_FEE=0 export BRIDGE_SHARE_BPS=10000 -export OTHER_RECIPIENT=0x0000000000000000000000000000000000000000 -export HYP_NATIVE_MINTER=0x0000000000000000000000000000000000000000 forge script script/GenerateFeeVaultAlloc.s.sol -vvv ``` @@ -123,19 +117,18 @@ Storage layout is derived from declaration order in `FeeVault.sol`: | Slot | Variable | Encoding | |------|----------|----------| -| `0x0` | `hypNativeMinter` | Address (20 bytes, left-padded) | -| `0x1` | `owner` + `destinationDomain` | `0x0000000000000000` | -| `0x2` | `recipientAddress` | bytes32 | +| `0x0` | `owner` | Address (20 bytes, left-padded) | +| `0x1` | `bridgeRecipient` | Address (20 bytes, left-padded) | +| `0x2` | `otherRecipient` | Address (20 bytes, left-padded) | | `0x3` | `minimumAmount` | uint256 | | `0x4` | `callFee` | uint256 | -| `0x5` | `otherRecipient` | Address (20 bytes, left-padded) | -| `0x6` | `bridgeShareBps` | uint256 | +| `0x5` | `bridgeShareBps` | uint256 | Notes: - `owner` must be non-zero, otherwise no one can administer the vault. - The constructor default (`bridgeShareBps = 10000 when 0`) does **not** apply at genesis. Set `0x2710` (10000) explicitly if you want 100% bridging. The helper script applies this default for you when `BRIDGE_SHARE_BPS=0`. -- `hypNativeMinter` can be zero at genesis, but it must be set before calling `sendToCelestia()`. +- `bridgeRecipient` can be zero at genesis, but it must be set before calling `distribute()`. Example alloc entry (address key without `0x`): @@ -146,13 +139,12 @@ Example alloc entry (address key without `0x`): "balance": "0x0", "code": "0x", "storage": { - "0x0": "0x0000000000000000000000001111111111111111111111111111111111111111", - "0x1": "0x0000000000000000000004d22222222222222222222222222222222222222222", - "0x2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0": "0x0000000000000000000000002222222222222222222222222222222222222222", + "0x1": "0x0000000000000000000000001111111111111111111111111111111111111111", + "0x2": "0x0000000000000000000000000000000000000000", "0x3": "0x0", "0x4": "0x0", - "0x5": "0x0000000000000000000000000000000000000000", - "0x6": "0x2710" + "0x5": "0x2710" } } } @@ -169,7 +161,7 @@ cast code --rpc-url # Inspect full config in one call cast call \ - "getConfig()(address,uint32,bytes32,uint256,uint256,uint256,address,address)" \ + "getConfig()(address,address,address,uint256,uint256,uint256)" \ --rpc-url # Or read individual storage slots (optional) diff --git a/docs/guide/fee-systems.md b/docs/guide/fee-systems.md index d3740575..6e375b10 100644 --- a/docs/guide/fee-systems.md +++ b/docs/guide/fee-systems.md @@ -5,7 +5,7 @@ This guide connects three related components that move or manage native token value: - Base fee redirect: redirects EIP-1559 base fees to a configured sink instead of burning them. -- FeeVault: a contract that accumulates native tokens and can split and bridge them. +- FeeVault: a contract that accumulates native tokens and can split and distribute them. - Native minting precompile: a privileged mint/burn interface for controlled supply changes. These components are independent but commonly deployed together. The base fee redirect is a value transfer, not minting. Native minting is explicit supply change and should remain tightly controlled. @@ -33,13 +33,13 @@ See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. ## FeeVault (contract level) -**Purpose**: Accumulate native tokens and split them between a bridge destination and a secondary recipient. +**Purpose**: Accumulate native tokens and split them between a bridge recipient and a secondary recipient. **Mechanics**: - Receives base fees when `baseFeeSink` is set to the FeeVault address. -- Anyone can trigger `sendToCelestia` (or equivalent) once the minimum threshold is met. -- Splits balance by `bridgeShareBps`, sends the bridge share to `HypNativeMinter`, and transfers the remainder to `otherRecipient`. +- Anyone can trigger `distribute()` once the minimum threshold is met. +- Splits balance by `bridgeShareBps`, sends the bridge share to `bridgeRecipient`, and transfers the remainder to `otherRecipient`. **Why it pairs with base fee redirect**: the redirect funnels base fees into the FeeVault automatically, turning burned fees into recoverable value for treasury or bridging. From c6e679b2e81fd643c0bb53046509dbd93f943688 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 13:58:16 +0100 Subject: [PATCH 42/81] style(contracts): fix forge fmt formatting --- contracts/script/DeployFeeVault.s.sol | 6 ++---- contracts/script/GenerateFeeVaultAlloc.s.sol | 10 +++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index 4c6f7624..675010d4 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -19,8 +19,7 @@ contract DeployFeeVault is Script { vm.startBroadcast(); // Deploy FeeVault with CREATE2 - FeeVault feeVault = - new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); + FeeVault feeVault = new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); vm.stopBroadcast(); @@ -48,8 +47,7 @@ contract ComputeFeeVaultAddress is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( - type(FeeVault).creationCode, - abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) + type(FeeVault).creationCode, abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) ) ); diff --git a/contracts/script/GenerateFeeVaultAlloc.s.sol b/contracts/script/GenerateFeeVaultAlloc.s.sol index bea158ec..e332c1e5 100644 --- a/contracts/script/GenerateFeeVaultAlloc.s.sol +++ b/contracts/script/GenerateFeeVaultAlloc.s.sol @@ -38,9 +38,7 @@ abstract contract FeeVaultAllocBase is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( type(FeeVault).creationCode, - abi.encode( - cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient - ) + abi.encode(cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient) ) ); cfg.feeVaultAddress = address( @@ -82,8 +80,7 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = - computeSlots(cfg); + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); console.log("========== FeeVault Genesis Alloc =========="); console.log("FeeVault address:", cfg.feeVaultAddress); @@ -135,8 +132,7 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = - computeSlots(cfg); + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); string memory json = string( abi.encodePacked( From cae3723387ae2e2a2eeae991bdec6bdf829e1cff Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 14:35:42 +0100 Subject: [PATCH 43/81] docs: clarify FeeVault is optional, document when to use it vs plain baseFeeSink --- contracts/README.md | 15 +++++++++------ docs/contracts/fee_vault.md | 14 ++++++++++++-- docs/guide/fee-systems.md | 22 ++++++++++++---------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index a04e3260..1de36cea 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -8,14 +8,17 @@ The AdminProxy contract solves the bootstrap problem for admin addresses at gene See [AdminProxy documentation](../docs/contracts/admin_proxy.md) for detailed setup and usage instructions. -## FeeVault +## FeeVault (optional) -The FeeVault contract collects base fees and distributes them between a bridge recipient and an optional secondary recipient. It supports: +The FeeVault is an **optional** contract for chains that need on-chain fee splitting logic. The base fee redirect (`baseFeeSink`) works with any address — an EOA or multisig is sufficient if you just need fees sent to a single destination. -- Configurable fee splitting between bridge and another recipient -- Minimum amount thresholds before distributing -- Call fee for incentivizing distribution calls -- Owner-controlled configuration +FeeVault is useful when you need: + +- **Automatic splitting** of accumulated fees between two recipients (e.g., 80% to a bridge contract, 20% to a treasury) +- **Minimum threshold** to avoid distributing uneconomically small amounts +- **Keeper incentive** (`callFee`) so anyone can trigger distribution and get compensated + +If your chain only needs fees routed to a single address, skip FeeVault and point `baseFeeSink` directly at that address. ## Prerequisites diff --git a/docs/contracts/fee_vault.md b/docs/contracts/fee_vault.md index a0569e15..d701ed3d 100644 --- a/docs/contracts/fee_vault.md +++ b/docs/contracts/fee_vault.md @@ -2,11 +2,21 @@ ## Overview -The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between a bridge recipient and a secondary recipient. +The `FeeVault` is an **optional** smart contract for chains that need on-chain fee splitting logic. It accumulates native tokens (gas tokens) and automatically splits them between two configurable recipients. + +## When to Use FeeVault + +The base fee redirect (`baseFeeSink`) works with any address. You **do not need** FeeVault if fees should go to a single destination — just point `baseFeeSink` at an EOA or multisig. + +FeeVault adds value when you need: + +- **Splitting**: Automatically divide fees between two recipients (e.g., 80% to a bridge, 20% to treasury). +- **Minimum threshold**: Only distribute when enough has accumulated to be economically worthwhile. +- **Keeper incentive**: A `callFee` rewards anyone who triggers the distribution, removing the need for a centralized operator. ## Use Case -This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to configured recipients while retaining a portion for other purposes (e.g., developer rewards, treasury). +This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to multiple recipients. 1. **Fee Accumulation**: The contract receives funds from: - **Base Fee Redirect**: The chain's execution layer (e.g., `ev-revm`) can be configured to direct burned base fees directly to this contract's address. diff --git a/docs/guide/fee-systems.md b/docs/guide/fee-systems.md index 6e375b10..aa9dfa76 100644 --- a/docs/guide/fee-systems.md +++ b/docs/guide/fee-systems.md @@ -31,9 +31,11 @@ These components are independent but commonly deployed together. The base fee re See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. -## FeeVault (contract level) +## FeeVault (contract level, optional) -**Purpose**: Accumulate native tokens and split them between a bridge recipient and a secondary recipient. +**Purpose**: Accumulate native tokens and split them between two configurable recipients. + +FeeVault is **optional**. The base fee redirect works with any address — if fees should go to a single destination, point `baseFeeSink` at an EOA or multisig and skip FeeVault entirely. Use FeeVault when you need automatic on-chain splitting, minimum thresholds, or keeper incentives. **Mechanics**: @@ -41,8 +43,6 @@ See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. - Anyone can trigger `distribute()` once the minimum threshold is met. - Splits balance by `bridgeShareBps`, sends the bridge share to `bridgeRecipient`, and transfers the remainder to `otherRecipient`. -**Why it pairs with base fee redirect**: the redirect funnels base fees into the FeeVault automatically, turning burned fees into recoverable value for treasury or bridging. - See `docs/contracts/fee_vault.md` for parameters and deployment details. ## Native Token Minting Precompile @@ -69,17 +69,19 @@ See `docs/adr/ADR-0002-native-minting-precompile.md` for the full interface and ## How They Fit Together -1. **Base fee redirect** credits base fees to a sink address instead of burning them. -2. **FeeVault** can be that sink, so base fees accumulate in a contract with deterministic split logic. +1. **Base fee redirect** credits base fees to a sink address instead of burning them. The sink can be any address (EOA, multisig, or contract). +2. **FeeVault** is one option for that sink when you need automatic splitting between two recipients. If fees go to a single destination, skip it. 3. **Native minting** is separate and optional; it is used for controlled supply changes (bootstrapping liquidity, treasury operations), not for redirecting fees. In other words, base fee redirect and FeeVault are about re-routing existing value, while native minting explicitly changes total supply. Keep those responsibilities separate and limit minting access to minimize systemic risk. -## Suggested Deployment Pattern +## Suggested Deployment Patterns + +**Simple (no FeeVault):** Set `baseFeeSink` to an EOA or multisig. Fees accumulate there directly. + +**With splitting (FeeVault):** Set `baseFeeSink` to the FeeVault address. Configure the split between `bridgeRecipient` and `otherRecipient`. Use `AdminProxy` as the FeeVault owner if you need a safe, upgradeable admin. -- Set `baseFeeSink` to the FeeVault address. -- Use `AdminProxy` as the `mintAdmin` and FeeVault owner if you need a safe, upgradeable admin. -- Activate both features at a planned height for existing networks. +Both patterns can be combined with native minting if needed. Activate features at a planned height for existing networks. References: From 4653ccc998359da2243b07f3f65326b7ecdf6bc7 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 11:32:00 +0200 Subject: [PATCH 44/81] fix(ev-deployer): validate permit2 zero-address and duplicate deploy addresses Add zero-address check for permit2.address and detect duplicate deploy addresses across all configured contracts during config validation. --- bin/ev-deployer/src/config.rs | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bf1f8671..e83184b9 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -2,6 +2,7 @@ use alloy_primitives::Address; use serde::Deserialize; +use std::collections::HashSet; use std::path::Path; /// Top-level deploy configuration. @@ -32,6 +33,20 @@ pub(crate) struct ContractsConfig { pub permit2: Option, } +impl ContractsConfig { + /// Collect all configured deploy addresses. + fn all_addresses(&self) -> Vec
{ + let mut addrs = Vec::new(); + if let Some(ref ap) = self.admin_proxy { + addrs.push(ap.address); + } + if let Some(ref p2) = self.permit2 { + addrs.push(p2.address); + } + addrs + } +} + /// `AdminProxy` configuration. #[derive(Debug, Deserialize)] pub(crate) struct AdminProxyConfig { @@ -66,6 +81,22 @@ impl DeployConfig { ); } + if let Some(ref p2) = self.contracts.permit2 { + eyre::ensure!( + !p2.address.is_zero(), + "permit2.address must not be the zero address" + ); + } + + // Detect duplicate deploy addresses across all contracts. + let mut seen = HashSet::new(); + for addr in self.contracts.all_addresses() { + eyre::ensure!( + seen.insert(addr), + "duplicate deploy address: {addr}" + ); + } + Ok(()) } } @@ -115,6 +146,37 @@ chain_id = 1 assert!(config.contracts.admin_proxy.is_none()); } + #[test] + fn reject_zero_permit2_address() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.permit2] +address = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + + #[test] + fn reject_duplicate_deploy_address() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.permit2] +address = "0x000000000000000000000000000000000000Ad00" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + let err = config.validate().unwrap_err().to_string(); + assert!(err.contains("duplicate deploy address"), "{err}"); + } + #[test] fn admin_proxy_only() { let toml = r#" From 221aa28562c47bd6843bce88eff6c31a632bbcc2 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 11:39:41 +0200 Subject: [PATCH 45/81] style(ev-deployer): remove unnecessary #[allow(dead_code)] from config structs --- bin/ev-deployer/src/config.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index e83184b9..75831db9 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -7,7 +7,6 @@ use std::path::Path; /// Top-level deploy configuration. #[derive(Debug, Deserialize)] -#[allow(dead_code)] pub(crate) struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, @@ -18,7 +17,6 @@ pub(crate) struct DeployConfig { /// Chain-level settings. #[derive(Debug, Deserialize)] -#[allow(dead_code)] pub(crate) struct ChainConfig { /// The chain ID. pub chain_id: u64, From 877c66b860cf4573723a350430599c65a9a3f60b Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 11:43:53 +0200 Subject: [PATCH 46/81] chore(ev-deployer): add permit2 config tests and update contracts skill Add permit2_only and both_contracts parsing tests. Update contracts onboarding skill to include Permit2 contract documentation. --- .claude/skills/contracts.md | 7 ++++++- bin/ev-deployer/src/config.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.claude/skills/contracts.md b/.claude/skills/contracts.md index d93142f2..98832927 100644 --- a/.claude/skills/contracts.md +++ b/.claude/skills/contracts.md @@ -1,5 +1,5 @@ --- -description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee distribution", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and distributed. +description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "Permit2", "fee distribution", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and distributed. --- # Contracts Onboarding @@ -10,12 +10,14 @@ The contracts live in `contracts/` and use Foundry for development. There are tw 1. **AdminProxy** (`src/AdminProxy.sol`) - Bootstrap contract for admin addresses at genesis 2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees and distributes them between configured recipients +3. **Permit2** (`lib/permit2`) - Uniswap's canonical token approval manager, deployed at genesis via `ev-deployer` (no Foundry deploy script — bytecode is embedded in Rust) ## Key Files ### Contract Sources - `contracts/src/AdminProxy.sol` - Transparent proxy pattern for admin control - `contracts/src/FeeVault.sol` - Fee collection and distribution logic +- `contracts/lib/permit2` - Uniswap Permit2 submodule (bytecode used by ev-deployer) ### Deployment Scripts - `contracts/script/DeployFeeVault.s.sol` - FeeVault deployment with CREATE2 @@ -37,6 +39,9 @@ The FeeVault serves as the destination for redirected base fees (instead of burn - Distribute accumulated fees between configured recipients - Manage withdrawal permissions +### Permit2 +Uniswap's canonical token approval manager deployed at genesis. Unlike AdminProxy and FeeVault, Permit2 has no Foundry deploy script — its bytecode is embedded directly in the Rust `ev-deployer` (`bin/ev-deployer/src/contracts/permit2.rs`), which patches EIP-712 immutables (chain ID, domain separator) at genesis time. + ## Connection to Rust Code The contracts integrate with ev-reth through: diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 75831db9..16795f3f 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -175,6 +175,40 @@ address = "0x000000000000000000000000000000000000Ad00" assert!(err.contains("duplicate deploy address"), "{err}"); } + #[test] + fn permit2_only() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.permit2] +address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.permit2.is_some()); + assert!(config.contracts.admin_proxy.is_none()); + } + + #[test] + fn both_contracts() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.admin_proxy] +address = "0x000000000000000000000000000000000000Ad00" +owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +[contracts.permit2] +address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.admin_proxy.is_some()); + assert!(config.contracts.permit2.is_some()); + } + #[test] fn admin_proxy_only() { let toml = r#" From 588a46a3f2874e7ffa08a833e394e67707a15505 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 11:47:44 +0200 Subject: [PATCH 47/81] style(ev-deployer): fix rustfmt formatting in config.rs --- bin/ev-deployer/src/config.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 16795f3f..f289611a 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -2,8 +2,7 @@ use alloy_primitives::Address; use serde::Deserialize; -use std::collections::HashSet; -use std::path::Path; +use std::{collections::HashSet, path::Path}; /// Top-level deploy configuration. #[derive(Debug, Deserialize)] @@ -89,10 +88,7 @@ impl DeployConfig { // Detect duplicate deploy addresses across all contracts. let mut seen = HashSet::new(); for addr in self.contracts.all_addresses() { - eyre::ensure!( - seen.insert(addr), - "duplicate deploy address: {addr}" - ); + eyre::ensure!(seen.insert(addr), "duplicate deploy address: {addr}"); } Ok(()) From 8e8bf8c470bd84a95b6e3dd43c177bf657532c47 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 11:58:12 +0200 Subject: [PATCH 48/81] docs(ev-deployer): use full canonical Permit2 address in config reference --- bin/ev-deployer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index fe7ae4dc..4aad2972 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -33,7 +33,7 @@ EV Deployer uses a TOML config file to define what contracts to include and how | Field | Type | Description | |-----------|---------|----------------------------------------------------------| -| `address` | address | Address to deploy at (canonical: `0x000000000022D473...`) | +| `address` | address | Address to deploy at (canonical: `0x000000000022D473030F116dDEE9F6B43aC78BA3`) | ## Usage From 6070a3b5c850d4898abf2dd4ff25393366633343 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 16:46:30 +0200 Subject: [PATCH 49/81] refactor(ev-deployer): make address optional in contract configs Prepares for the deploy subcommand where addresses are determined by CREATE2, not by user config. Genesis mode validates addresses are present. --- bin/ev-deployer/src/config.rs | 75 +++++++++++++++----- bin/ev-deployer/src/contracts/admin_proxy.rs | 7 +- bin/ev-deployer/src/contracts/permit2.rs | 40 ++++++----- bin/ev-deployer/src/genesis.rs | 2 +- bin/ev-deployer/src/main.rs | 9 +-- bin/ev-deployer/src/output.rs | 20 +++--- 6 files changed, 103 insertions(+), 50 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index f289611a..ba02a624 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,11 +1,11 @@ //! TOML config types, parsing, and validation. use alloy_primitives::Address; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{collections::HashSet, path::Path}; /// Top-level deploy configuration. -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, @@ -15,14 +15,14 @@ pub(crate) struct DeployConfig { } /// Chain-level settings. -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct ChainConfig { /// The chain ID. pub chain_id: u64, } /// All contract configurations. -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] pub(crate) struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, @@ -35,29 +35,33 @@ impl ContractsConfig { fn all_addresses(&self) -> Vec
{ let mut addrs = Vec::new(); if let Some(ref ap) = self.admin_proxy { - addrs.push(ap.address); + if let Some(addr) = ap.address { + addrs.push(addr); + } } if let Some(ref p2) = self.permit2 { - addrs.push(p2.address); + if let Some(addr) = p2.address { + addrs.push(addr); + } } addrs } } /// `AdminProxy` configuration. -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct AdminProxyConfig { - /// Address to deploy at. - pub address: Address, + /// Address to deploy at (required for genesis, ignored for deploy). + pub address: Option
, /// Owner address. pub owner: Address, } /// `Permit2` configuration (Uniswap token approval manager). -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub(crate) struct Permit2Config { - /// Address to deploy at. - pub address: Address, + /// Address to deploy at (required for genesis, ignored for deploy). + pub address: Option
, } impl DeployConfig { @@ -70,7 +74,7 @@ impl DeployConfig { } /// Validate config values. - fn validate(&self) -> eyre::Result<()> { + pub(crate) fn validate(&self) -> eyre::Result<()> { if let Some(ref ap) = self.contracts.admin_proxy { eyre::ensure!( !ap.owner.is_zero(), @@ -79,10 +83,12 @@ impl DeployConfig { } if let Some(ref p2) = self.contracts.permit2 { - eyre::ensure!( - !p2.address.is_zero(), - "permit2.address must not be the zero address" - ); + if let Some(addr) = p2.address { + eyre::ensure!( + !addr.is_zero(), + "permit2.address must not be the zero address" + ); + } } // Detect duplicate deploy addresses across all contracts. @@ -93,6 +99,23 @@ impl DeployConfig { Ok(()) } + + /// Additional validation for genesis mode: all addresses must be specified. + pub(crate) fn validate_for_genesis(&self) -> eyre::Result<()> { + if let Some(ref ap) = self.contracts.admin_proxy { + eyre::ensure!( + ap.address.is_some(), + "admin_proxy.address is required for genesis mode" + ); + } + if let Some(ref p2) = self.contracts.permit2 { + eyre::ensure!( + p2.address.is_some(), + "permit2.address is required for genesis mode" + ); + } + Ok(()) + } } #[cfg(test)] @@ -205,6 +228,24 @@ address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" assert!(config.contracts.permit2.is_some()); } + #[test] + fn reject_missing_address_for_genesis() { + use alloy_primitives::address; + + let config = DeployConfig { + chain: ChainConfig { chain_id: 1 }, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: None, + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + permit2: None, + }, + }; + config.validate().unwrap(); // base validation passes + assert!(config.validate_for_genesis().is_err()); + } + #[test] fn admin_proxy_only() { let toml = r#" diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index ed187b12..33c4f54c 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -6,10 +6,11 @@ use std::collections::BTreeMap; /// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). /// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` -const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); +pub(crate) const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); /// Build a genesis alloc entry for `AdminProxy`. pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { + let address = config.address.expect("address required for genesis"); let mut storage = BTreeMap::new(); // Slot 0: owner (address left-padded to 32 bytes) @@ -17,7 +18,7 @@ pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { storage.insert(B256::ZERO, owner_value); GenesisContract { - address: config.address, + address, code: Bytes::from_static(ADMIN_PROXY_BYTECODE), storage, } @@ -32,7 +33,7 @@ mod tests { #[test] fn golden_admin_proxy_storage() { let config = AdminProxyConfig { - address: address!("000000000000000000000000000000000000Ad00"), + address: Some(address!("000000000000000000000000000000000000Ad00")), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }; let contract = build(&config); diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index ee56f85b..ef438b1f 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -28,7 +28,7 @@ use crate::{ GenesisContract, }, }; -use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; +use alloy_primitives::{hex, keccak256, Address, Bytes, B256, U256}; use std::collections::BTreeMap; /// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) @@ -41,47 +41,45 @@ use std::collections::BTreeMap; /// ```sh /// cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode /// ``` -const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); +pub(crate) const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); // ── Immutable reference offsets (from compiled artifact `immutableReferences`) ── /// `_CACHED_CHAIN_ID` (uint256) — from `EIP712.sol`. -const CACHED_CHAIN_ID_REFS: &[ImmutableRef] = &[ImmutableRef { +pub(crate) const CACHED_CHAIN_ID_REFS: &[ImmutableRef] = &[ImmutableRef { start: 6945, length: 32, }]; /// `_CACHED_DOMAIN_SEPARATOR` (bytes32) — from `EIP712.sol`. -const CACHED_DOMAIN_SEPARATOR_REFS: &[ImmutableRef] = &[ImmutableRef { +pub(crate) const CACHED_DOMAIN_SEPARATOR_REFS: &[ImmutableRef] = &[ImmutableRef { start: 6983, length: 32, }]; /// EIP-712 type hash: `keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)")` -const EIP712_TYPE_HASH: B256 = B256::new(hex!( +pub(crate) const EIP712_TYPE_HASH: B256 = B256::new(hex!( "8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866" )); /// `keccak256("Permit2")` -const HASHED_NAME: B256 = B256::new(hex!( +pub(crate) const HASHED_NAME: B256 = B256::new(hex!( "9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a" )); -/// Build a genesis alloc entry for `Permit2`. -pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { +/// Build the expected runtime bytecode for a Permit2 deployed at `address` on `chain_id`. +/// Used by the deploy pipeline to verify on-chain bytecode matches. +pub(crate) fn expected_runtime_bytecode(chain_id: u64, address: Address) -> Vec { let mut bytecode = PERMIT2_BYTECODE.to_vec(); - // Patch _CACHED_CHAIN_ID let chain_id_u256 = U256::from(chain_id); patch_u256(&mut bytecode, CACHED_CHAIN_ID_REFS, chain_id_u256); - // Compute and patch _CACHED_DOMAIN_SEPARATOR: - // keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, chainId, contractAddress)) let mut buf = [0u8; 128]; buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); - buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + buf[96..128].copy_from_slice(address.into_word().as_slice()); let domain_separator = keccak256(buf); patch_bytes( &mut bytecode, @@ -89,8 +87,16 @@ pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { &domain_separator.0, ); + bytecode +} + +/// Build a genesis alloc entry for `Permit2`. +pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { + let address = config.address.expect("address required for genesis"); + let bytecode = expected_runtime_bytecode(chain_id, address); + GenesisContract { - address: config.address, + address, code: Bytes::from(bytecode), storage: BTreeMap::new(), } @@ -104,7 +110,7 @@ mod tests { fn test_config() -> Permit2Config { Permit2Config { - address: address!("000000000022D473030F116dDEE9F6B43aC78BA3"), + address: Some(address!("000000000022D473030F116dDEE9F6B43aC78BA3")), } } @@ -138,7 +144,7 @@ mod tests { buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); buf[64..96].copy_from_slice(&B256::from(U256::from(chain_id)).0); - buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + buf[96..128].copy_from_slice(config.address.unwrap().into_word().as_slice()); let expected = keccak256(buf); let word = &code[6983..6983 + 32]; @@ -163,13 +169,13 @@ mod tests { fn domain_separator_changes_with_address() { let c1 = build( &Permit2Config { - address: Address::repeat_byte(0x01), + address: Some(Address::repeat_byte(0x01)), }, 1234, ); let c2 = build( &Permit2Config { - address: Address::repeat_byte(0x02), + address: Some(Address::repeat_byte(0x02)), }, 1234, ); diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index e270b697..322f8ca5 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -104,7 +104,7 @@ mod tests { chain: ChainConfig { chain_id: 1234 }, contracts: ContractsConfig { admin_proxy: Some(AdminProxyConfig { - address: address!("000000000000000000000000000000000000ad00"), + address: Some(address!("000000000000000000000000000000000000ad00")), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), permit2: None, diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 5d87ae9b..8ad8d7d6 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -73,6 +73,7 @@ fn main() -> eyre::Result<()> { addresses_out, } => { let cfg = config::DeployConfig::load(&config_path)?; + cfg.validate_for_genesis()?; let result = if let Some(ref genesis_path) = merge_into { genesis::merge_into(&cfg, genesis_path, force)? @@ -117,14 +118,14 @@ fn main() -> eyre::Result<()> { .contracts .admin_proxy .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, + .and_then(|c| c.address) + .ok_or_else(|| eyre::eyre!("admin_proxy not configured or address not set"))?, "permit2" => cfg .contracts .permit2 .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, + .and_then(|c| c.address) + .ok_or_else(|| eyre::eyre!("permit2 not configured or address not set"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index f683377a..d4349744 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -8,17 +8,21 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { let mut manifest = Map::new(); if let Some(ref ap) = config.contracts.admin_proxy { - manifest.insert( - "admin_proxy".to_string(), - Value::String(format!("{}", ap.address)), - ); + if let Some(addr) = ap.address { + manifest.insert( + "admin_proxy".to_string(), + Value::String(format!("{}", addr)), + ); + } } if let Some(ref p2) = config.contracts.permit2 { - manifest.insert( - "permit2".to_string(), - Value::String(format!("{}", p2.address)), - ); + if let Some(addr) = p2.address { + manifest.insert( + "permit2".to_string(), + Value::String(format!("{}", addr)), + ); + } } Value::Object(manifest) From 3c74168798e9f36e3b2fc01ca481de1657b910ce Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:17:35 +0200 Subject: [PATCH 50/81] feat(ev-deployer): add initcode constants for live deployment Embeds creation bytecodes alongside existing runtime bytecodes. Extracts expected_runtime_bytecode for Permit2 verification. --- bin/ev-deployer/src/contracts/admin_proxy.rs | 5 +++++ bin/ev-deployer/src/contracts/permit2.rs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index 33c4f54c..4709eb0d 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -7,6 +7,11 @@ use std::collections::BTreeMap; /// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). /// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` pub(crate) const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); +/// `AdminProxy` creation bytecode (initcode) compiled with solc 0.8.33 (`cbor_metadata=false`). +/// The constructor is empty; owner is set via storage, not constructor args. +/// Regenerate with: `cd contracts && forge inspect AdminProxy bytecode` +pub(crate) const ADMIN_PROXY_INITCODE: &[u8] = &hex!("6080604052348015600e575f5ffd5b506112ab8061001c5f395ff3fe60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); + /// Build a genesis alloc entry for `AdminProxy`. pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index ef438b1f..eabbbe70 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -43,6 +43,12 @@ use std::collections::BTreeMap; /// ``` pub(crate) const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); +/// `Permit2` creation bytecode (initcode) compiled from Uniswap/permit2 (commit cc56ad0) +/// with solc 0.8.17 (via-ir, optimizer `1_000_000` runs, `bytecode_hash="none"`). +/// No constructor arguments needed. +/// Regenerate with: `cd contracts/lib/permit2 && forge inspect Permit2 bytecode` +pub(crate) const PERMIT2_INITCODE: &[u8] = &hex!("60c0346100bb574660a052602081017f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a60408301524660608301523060808301526080825260a082019180831060018060401b038411176100a557826040525190206080526123c090816100c1823960805181611b47015260a05181611b210152f35b634e487b7160e01b600052604160045260246000fd5b600080fdfe6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); + // ── Immutable reference offsets (from compiled artifact `immutableReferences`) ── /// `_CACHED_CHAIN_ID` (uint256) — from `EIP712.sol`. From 1f7e2ccf8515ad5b590c7d74f91ae3e5de374e5a Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:18:23 +0200 Subject: [PATCH 51/81] feat(ev-deployer): add CREATE2 address computation module --- bin/ev-deployer/src/deploy/create2.rs | 74 +++++++++++++++++++++++++++ bin/ev-deployer/src/deploy/mod.rs | 1 + bin/ev-deployer/src/main.rs | 1 + 3 files changed, 76 insertions(+) create mode 100644 bin/ev-deployer/src/deploy/create2.rs create mode 100644 bin/ev-deployer/src/deploy/mod.rs diff --git a/bin/ev-deployer/src/deploy/create2.rs b/bin/ev-deployer/src/deploy/create2.rs new file mode 100644 index 00000000..b1d8deb7 --- /dev/null +++ b/bin/ev-deployer/src/deploy/create2.rs @@ -0,0 +1,74 @@ +//! CREATE2 address computation. + +use alloy_primitives::{Address, Bytes, B256, keccak256}; + +/// The deterministic deployer factory address (Nick's factory). +/// See: https://github.com/Arachnid/deterministic-deployment-proxy +pub(crate) const DETERMINISTIC_DEPLOYER: Address = + Address::new(alloy_primitives::hex!("4e59b44847b379578588920ca78fbf26c0b4956c")); + +/// Compute the CREATE2 address for a contract deployed via the deterministic deployer. +/// +/// The factory expects calldata `salt ++ initcode` and deploys via: +/// `CREATE2(value=0, offset, size, salt)` +/// +/// The resulting address is: +/// `keccak256(0xff ++ factory ++ salt ++ keccak256(initcode))[12..]` +pub(crate) fn compute_address(salt: B256, initcode: &[u8]) -> Address { + let init_code_hash = keccak256(initcode); + DETERMINISTIC_DEPLOYER.create2(salt, init_code_hash) +} + +/// Build the calldata to send to the deterministic deployer factory. +/// Format: `salt (32 bytes) ++ initcode` +pub(crate) fn build_factory_calldata(salt: B256, initcode: &[u8]) -> Bytes { + let mut data = Vec::with_capacity(32 + initcode.len()); + data.extend_from_slice(salt.as_slice()); + data.extend_from_slice(initcode); + Bytes::from(data) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + + #[test] + fn known_create2_address() { + let salt = B256::ZERO; + // Minimal initcode: PUSH1 0x00 PUSH1 0x00 RETURN (returns empty code) + let initcode = hex!("60006000f3"); + let addr = compute_address(salt, &initcode); + + let init_hash = keccak256(&initcode); + let expected = DETERMINISTIC_DEPLOYER.create2(salt, init_hash); + assert_eq!(addr, expected); + } + + #[test] + fn different_salts_different_addresses() { + let initcode = hex!("60006000f3"); + let addr1 = compute_address(B256::ZERO, &initcode); + let addr2 = compute_address(B256::with_last_byte(1), &initcode); + assert_ne!(addr1, addr2); + } + + #[test] + fn different_initcode_different_addresses() { + let salt = B256::ZERO; + let addr1 = compute_address(salt, &hex!("60006000f3")); + let addr2 = compute_address(salt, &hex!("60016000f3")); + assert_ne!(addr1, addr2); + } + + #[test] + fn factory_calldata_format() { + let salt = B256::with_last_byte(0x42); + let initcode = hex!("aabbcc"); + let calldata = build_factory_calldata(salt, &initcode); + + assert_eq!(calldata.len(), 32 + 3); + assert_eq!(&calldata[..32], salt.as_slice()); + assert_eq!(&calldata[32..], &hex!("aabbcc")); + } +} diff --git a/bin/ev-deployer/src/deploy/mod.rs b/bin/ev-deployer/src/deploy/mod.rs new file mode 100644 index 00000000..c099f388 --- /dev/null +++ b/bin/ev-deployer/src/deploy/mod.rs @@ -0,0 +1 @@ +pub(crate) mod create2; diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 8ad8d7d6..57ec4372 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -2,6 +2,7 @@ mod config; mod contracts; +mod deploy; mod genesis; mod output; From 826c10d897974e9c714c13a035fa9c2e70bf69f3 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:20:33 +0200 Subject: [PATCH 52/81] feat(ev-deployer): add deploy state file with immutability checks --- bin/ev-deployer/Cargo.toml | 1 + bin/ev-deployer/src/deploy/mod.rs | 1 + bin/ev-deployer/src/deploy/state.rs | 278 ++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 bin/ev-deployer/src/deploy/state.rs diff --git a/bin/ev-deployer/Cargo.toml b/bin/ev-deployer/Cargo.toml index b80d21a8..0b8d0720 100644 --- a/bin/ev-deployer/Cargo.toml +++ b/bin/ev-deployer/Cargo.toml @@ -15,6 +15,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } toml = "0.8" eyre = { workspace = true } +rand = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/bin/ev-deployer/src/deploy/mod.rs b/bin/ev-deployer/src/deploy/mod.rs index c099f388..2aa1071d 100644 --- a/bin/ev-deployer/src/deploy/mod.rs +++ b/bin/ev-deployer/src/deploy/mod.rs @@ -1 +1,2 @@ pub(crate) mod create2; +pub(crate) mod state; diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs new file mode 100644 index 00000000..a3adefd2 --- /dev/null +++ b/bin/ev-deployer/src/deploy/state.rs @@ -0,0 +1,278 @@ +//! Deploy state file: tracks deployment progress with resumability. + +use crate::config::DeployConfig; +use alloy_primitives::{Address, B256}; +use rand::RngExt; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +/// Current state file schema version. +const STATE_VERSION: u32 = 1; + +/// Overall deployment state, persisted to JSON. +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct DeployState { + /// Schema version. + pub version: u32, + /// Random salt for CREATE2 deployments. + pub create2_salt: B256, + /// Snapshot of the config at first run — used for immutability checks. + pub applied_intent: AppliedIntent, + /// Per-contract deployment state. + #[serde(default)] + pub contracts: ContractStates, +} + +/// Snapshot of the config that was used for the first deployment. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct AppliedIntent { + pub chain_id: u64, + pub admin_proxy: Option, + pub permit2: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct AppliedAdminProxy { + pub owner: Address, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) struct AppliedPermit2 {} + +/// Per-contract deployment states. +#[derive(Debug, Default, Serialize, Deserialize)] +pub(crate) struct ContractStates { + pub admin_proxy: Option, + pub permit2: Option, +} + +/// State of a single contract deployment. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct ContractState { + pub status: ContractStatus, + pub address: Address, + #[serde(skip_serializing_if = "Option::is_none")] + pub deploy_tx: Option, +} + +/// Contract deployment status progression. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub(crate) enum ContractStatus { + Pending, + Deployed, + Verified, +} + +impl DeployState { + /// Create a new state from config, generating a random salt. + pub(crate) fn new(config: &DeployConfig) -> Self { + let mut salt_bytes = [0u8; 32]; + let mut rng = rand::rng(); + rng.fill(&mut salt_bytes); + let salt = B256::from(salt_bytes); + + Self { + version: STATE_VERSION, + create2_salt: salt, + applied_intent: AppliedIntent::from_config(config), + contracts: ContractStates::default(), + } + } + + /// Load state from a JSON file. + pub(crate) fn load(path: &Path) -> eyre::Result { + let content = std::fs::read_to_string(path)?; + let state: Self = serde_json::from_str(&content)?; + eyre::ensure!( + state.version == STATE_VERSION, + "unsupported state version: {} (expected {})", + state.version, + STATE_VERSION + ); + Ok(state) + } + + /// Save state to a JSON file. + pub(crate) fn save(&self, path: &Path) -> eyre::Result<()> { + let json = serde_json::to_string_pretty(self)?; + std::fs::write(path, json)?; + Ok(()) + } + + /// Validate that the current config is compatible with the applied intent. + /// Immutable fields cannot change. New contracts can be added. + pub(crate) fn validate_immutability(&self, config: &DeployConfig) -> eyre::Result<()> { + let current = &self.applied_intent; + + eyre::ensure!( + config.chain.chain_id == current.chain_id, + "immutability violation: chain_id changed from {} to {}", + current.chain_id, + config.chain.chain_id + ); + + // If admin_proxy was in the original intent, its owner must not change + if let Some(ref original_ap) = current.admin_proxy { + if let Some(ref new_ap) = config.contracts.admin_proxy { + eyre::ensure!( + new_ap.owner == original_ap.owner, + "immutability violation: admin_proxy.owner changed from {} to {}", + original_ap.owner, + new_ap.owner + ); + } else { + eyre::bail!("immutability violation: admin_proxy was configured but is now missing"); + } + } + + // If permit2 was in the original intent, it must still be present + if current.permit2.is_some() { + eyre::ensure!( + config.contracts.permit2.is_some(), + "immutability violation: permit2 was configured but is now missing" + ); + } + + Ok(()) + } +} + +impl AppliedIntent { + fn from_config(config: &DeployConfig) -> Self { + Self { + chain_id: config.chain.chain_id, + admin_proxy: config + .contracts + .admin_proxy + .as_ref() + .map(|ap| AppliedAdminProxy { owner: ap.owner }), + permit2: config + .contracts + .permit2 + .as_ref() + .map(|_| AppliedPermit2 {}), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::*; + use alloy_primitives::address; + + fn test_config() -> DeployConfig { + DeployConfig { + chain: ChainConfig { chain_id: 1234 }, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: None, + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + permit2: Some(Permit2Config { address: None }), + }, + } + } + + #[test] + fn new_state_has_random_salt() { + let s1 = DeployState::new(&test_config()); + let s2 = DeployState::new(&test_config()); + assert_ne!(s1.create2_salt, s2.create2_salt); + } + + #[test] + fn new_state_snapshots_intent() { + let state = DeployState::new(&test_config()); + assert_eq!(state.applied_intent.chain_id, 1234); + assert!(state.applied_intent.admin_proxy.is_some()); + assert!(state.applied_intent.permit2.is_some()); + } + + #[test] + fn roundtrip_save_load() { + let state = DeployState::new(&test_config()); + let tmp = tempfile::NamedTempFile::new().unwrap(); + state.save(tmp.path()).unwrap(); + let loaded = DeployState::load(tmp.path()).unwrap(); + assert_eq!(loaded.create2_salt, state.create2_salt); + assert_eq!(loaded.applied_intent, state.applied_intent); + } + + #[test] + fn immutability_ok_same_config() { + let config = test_config(); + let state = DeployState::new(&config); + assert!(state.validate_immutability(&config).is_ok()); + } + + #[test] + fn immutability_rejects_chain_id_change() { + let config = test_config(); + let state = DeployState::new(&config); + let mut changed = config.clone(); + changed.chain.chain_id = 9999; + let err = state + .validate_immutability(&changed) + .unwrap_err() + .to_string(); + assert!(err.contains("chain_id changed"), "{err}"); + } + + #[test] + fn immutability_rejects_owner_change() { + let config = test_config(); + let state = DeployState::new(&config); + let mut changed = config.clone(); + changed.contracts.admin_proxy.as_mut().unwrap().owner = + address!("0000000000000000000000000000000000000001"); + let err = state + .validate_immutability(&changed) + .unwrap_err() + .to_string(); + assert!(err.contains("admin_proxy.owner changed"), "{err}"); + } + + #[test] + fn immutability_allows_adding_new_contract() { + let config = DeployConfig { + chain: ChainConfig { chain_id: 1234 }, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: None, + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + permit2: None, + }, + }; + let state = DeployState::new(&config); + + // Now add permit2 — this should be allowed + let mut extended = config.clone(); + extended.contracts.permit2 = Some(Permit2Config { address: None }); + assert!(state.validate_immutability(&extended).is_ok()); + } + + #[test] + fn immutability_rejects_removing_contract() { + let config = test_config(); + let state = DeployState::new(&config); + let mut changed = config.clone(); + changed.contracts.admin_proxy = None; + let err = state + .validate_immutability(&changed) + .unwrap_err() + .to_string(); + assert!( + err.contains("admin_proxy was configured but is now missing"), + "{err}" + ); + } + + #[test] + fn contract_status_ordering() { + assert!(ContractStatus::Pending < ContractStatus::Deployed); + assert!(ContractStatus::Deployed < ContractStatus::Verified); + } +} From f9054b76ff1d115a0035353b5c496dc3bd0cb76e Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:23:41 +0200 Subject: [PATCH 53/81] feat(ev-deployer): add ChainDeployer trait and LiveDeployer --- bin/ev-deployer/Cargo.toml | 5 ++ bin/ev-deployer/src/deploy/deployer.rs | 79 ++++++++++++++++++++++++++ bin/ev-deployer/src/deploy/mod.rs | 1 + 3 files changed, 85 insertions(+) create mode 100644 bin/ev-deployer/src/deploy/deployer.rs diff --git a/bin/ev-deployer/Cargo.toml b/bin/ev-deployer/Cargo.toml index 0b8d0720..ff9eae71 100644 --- a/bin/ev-deployer/Cargo.toml +++ b/bin/ev-deployer/Cargo.toml @@ -10,6 +10,11 @@ authors.workspace = true [dependencies] alloy-primitives = { workspace = true, features = ["serde"] } +alloy = { workspace = true } +alloy-rpc-types-eth = { workspace = true } +alloy-signer-local = { workspace = true } +async-trait = { workspace = true } +tokio = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/bin/ev-deployer/src/deploy/deployer.rs b/bin/ev-deployer/src/deploy/deployer.rs new file mode 100644 index 00000000..8fa95719 --- /dev/null +++ b/bin/ev-deployer/src/deploy/deployer.rs @@ -0,0 +1,79 @@ +//! ChainDeployer trait and LiveDeployer implementation. + +use crate::deploy::create2::{build_factory_calldata, DETERMINISTIC_DEPLOYER}; +use alloy::network::EthereumWallet; +use alloy::providers::{Provider, ProviderBuilder}; +use alloy_primitives::{Address, Bytes, B256}; +use alloy_rpc_types_eth::TransactionRequest; +use alloy_signer_local::PrivateKeySigner; +use async_trait::async_trait; + +/// Receipt from a confirmed transaction. +#[derive(Debug)] +pub(crate) struct TxReceipt { + pub tx_hash: B256, + pub success: bool, +} + +/// Abstracts on-chain operations for the deploy pipeline. +#[async_trait] +pub(crate) trait ChainDeployer: Send + Sync { + /// Get the chain ID of the connected chain. + async fn chain_id(&self) -> eyre::Result; + + /// Read the bytecode at an address. Returns empty bytes if no code. + async fn get_code(&self, address: Address) -> eyre::Result; + + /// Send a CREATE2 deployment transaction via the deterministic deployer. + /// Returns the tx hash once the tx is confirmed. + async fn deploy_create2(&self, salt: B256, initcode: &[u8]) -> eyre::Result; +} + +/// Live deployer using alloy provider + signer. +pub(crate) struct LiveDeployer { + provider: Box, +} + +impl LiveDeployer { + /// Create a new LiveDeployer from an RPC URL and a hex-encoded private key. + pub(crate) fn new(rpc_url: &str, private_key_hex: &str) -> eyre::Result { + let key_hex = private_key_hex.strip_prefix("0x").unwrap_or(private_key_hex); + let signer: PrivateKeySigner = key_hex.parse()?; + let wallet = EthereumWallet::from(signer); + + let provider = ProviderBuilder::new() + .wallet(wallet) + .connect_http(rpc_url.parse()?); + + Ok(Self { + provider: Box::new(provider), + }) + } +} + +#[async_trait] +impl ChainDeployer for LiveDeployer { + async fn chain_id(&self) -> eyre::Result { + Ok(self.provider.get_chain_id().await?) + } + + async fn get_code(&self, address: Address) -> eyre::Result { + Ok(self.provider.get_code_at(address).await?) + } + + async fn deploy_create2(&self, salt: B256, initcode: &[u8]) -> eyre::Result { + let calldata = build_factory_calldata(salt, initcode); + + let tx = TransactionRequest::default() + .to(DETERMINISTIC_DEPLOYER) + .input(calldata.into()); + + let pending = self.provider.send_transaction(tx).await?; + let receipt = pending.get_receipt().await?; + + Ok(TxReceipt { + tx_hash: receipt.transaction_hash, + success: receipt.status(), + }) + } +} diff --git a/bin/ev-deployer/src/deploy/mod.rs b/bin/ev-deployer/src/deploy/mod.rs index 2aa1071d..022531af 100644 --- a/bin/ev-deployer/src/deploy/mod.rs +++ b/bin/ev-deployer/src/deploy/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod create2; +pub(crate) mod deployer; pub(crate) mod state; From 2b90e825a7bd0ac0423748d6da36123af85e369f Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:25:59 +0200 Subject: [PATCH 54/81] feat(ev-deployer): add deploy pipeline with mock-tested flow --- bin/ev-deployer/src/deploy/mod.rs | 1 + bin/ev-deployer/src/deploy/pipeline.rs | 471 +++++++++++++++++++++++++ 2 files changed, 472 insertions(+) create mode 100644 bin/ev-deployer/src/deploy/pipeline.rs diff --git a/bin/ev-deployer/src/deploy/mod.rs b/bin/ev-deployer/src/deploy/mod.rs index 022531af..7990f24b 100644 --- a/bin/ev-deployer/src/deploy/mod.rs +++ b/bin/ev-deployer/src/deploy/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod create2; pub(crate) mod deployer; +pub(crate) mod pipeline; pub(crate) mod state; diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs new file mode 100644 index 00000000..5dbf5b0a --- /dev/null +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -0,0 +1,471 @@ +//! Deploy pipeline: orchestrates the full deployment flow. + +use crate::config::DeployConfig; +use crate::contracts; +use crate::deploy::create2::{compute_address, DETERMINISTIC_DEPLOYER}; +use crate::deploy::deployer::ChainDeployer; +use crate::deploy::state::{ContractState, ContractStatus, DeployState}; +use alloy_primitives::{Address, B256}; +use std::path::{Path, PathBuf}; + +/// Configuration for the deploy pipeline. +pub(crate) struct PipelineConfig { + pub config: DeployConfig, + pub state_path: PathBuf, + pub addresses_out: Option, +} + +/// Run the full deploy pipeline. +pub(crate) async fn run( + pipeline_cfg: &PipelineConfig, + deployer: &dyn ChainDeployer, +) -> eyre::Result<()> { + // ── Step 1: Init ── + eprintln!("[1/5] Connecting to RPC..."); + let chain_id = deployer.chain_id().await?; + eprintln!(" chain_id={chain_id}"); + + eyre::ensure!( + chain_id == pipeline_cfg.config.chain.chain_id, + "chain_id mismatch: config says {}, RPC reports {}", + pipeline_cfg.config.chain.chain_id, + chain_id + ); + + eprintln!("[2/5] Verifying deterministic deployer..."); + let deployer_code = deployer.get_code(DETERMINISTIC_DEPLOYER).await?; + eyre::ensure!( + !deployer_code.is_empty(), + "deterministic deployer not found at {} -- deploy it before running ev-deployer deploy", + DETERMINISTIC_DEPLOYER + ); + eprintln!(" OK"); + + // Load or create state + let mut state = if pipeline_cfg.state_path.exists() { + let state = DeployState::load(&pipeline_cfg.state_path)?; + state.validate_immutability(&pipeline_cfg.config)?; + state + } else { + let state = DeployState::new(&pipeline_cfg.config); + state.save(&pipeline_cfg.state_path)?; + state + }; + + let salt = state.create2_salt; + + // ── Step 2: Deploy AdminProxy ── + if let Some(ref ap_config) = pipeline_cfg.config.contracts.admin_proxy { + eprintln!("[3/5] Deploying AdminProxy..."); + + if ap_config.address.is_some() { + eprintln!(" WARN: contracts.admin_proxy.address is ignored in deploy mode"); + } + + let initcode = build_admin_proxy_initcode(ap_config.owner); + let address = compute_address(salt, &initcode); + + deploy_contract( + deployer, + &mut state, + "admin_proxy", + address, + salt, + &initcode, + contracts::admin_proxy::ADMIN_PROXY_BYTECODE, + &pipeline_cfg.state_path, + ) + .await?; + } else { + eprintln!("[3/5] AdminProxy not configured, skipping"); + } + + // ── Step 3: Deploy Permit2 ── + if pipeline_cfg.config.contracts.permit2.is_some() { + eprintln!("[4/5] Deploying Permit2..."); + + if pipeline_cfg + .config + .contracts + .permit2 + .as_ref() + .unwrap() + .address + .is_some() + { + eprintln!(" WARN: contracts.permit2.address is ignored in deploy mode"); + } + + let initcode = contracts::permit2::PERMIT2_INITCODE.to_vec(); + let address = compute_address(salt, &initcode); + + let expected_runtime = + contracts::permit2::expected_runtime_bytecode(chain_id, address); + + deploy_contract( + deployer, + &mut state, + "permit2", + address, + salt, + &initcode, + &expected_runtime, + &pipeline_cfg.state_path, + ) + .await?; + } else { + eprintln!("[4/5] Permit2 not configured, skipping"); + } + + // ── Step 4: Verify ── + eprintln!("[5/5] Verifying bytecodes..."); + verify_all(deployer, &mut state, &pipeline_cfg.config, chain_id).await?; + state.save(&pipeline_cfg.state_path)?; + eprintln!(" OK"); + + // ── Step 5: Output ── + eprintln!(); + eprintln!( + "Deploy complete. State saved to {}", + pipeline_cfg.state_path.display() + ); + + if let Some(ref addr_path) = pipeline_cfg.addresses_out { + let manifest = build_deploy_manifest(&state); + let json = serde_json::to_string_pretty(&manifest)?; + std::fs::write(addr_path, &json)?; + eprintln!("Wrote address manifest to {}", addr_path.display()); + } + + Ok(()) +} + +/// Build AdminProxy initcode with constructor argument. +fn build_admin_proxy_initcode(owner: Address) -> Vec { + let mut initcode = contracts::admin_proxy::ADMIN_PROXY_INITCODE.to_vec(); + // ABI-encode the owner address as a 32-byte word and append + initcode.extend_from_slice(owner.into_word().as_slice()); + initcode +} + +/// Deploy a single contract via CREATE2 with idempotency. +async fn deploy_contract( + deployer: &dyn ChainDeployer, + state: &mut DeployState, + name: &str, + address: Address, + salt: B256, + initcode: &[u8], + expected_runtime: &[u8], + state_path: &Path, +) -> eyre::Result<()> { + // Check if already deployed or verified in state + let current_status = get_contract_status(state, name); + if current_status >= Some(ContractStatus::Deployed) { + eprintln!(" already deployed at {address}, skipping"); + return Ok(()); + } + + // Idempotency: check if code already exists on-chain + let existing_code = deployer.get_code(address).await?; + if !existing_code.is_empty() { + if existing_code.as_ref() == expected_runtime { + eprintln!(" found matching bytecode at {address}, marking as deployed"); + set_contract_state( + state, + name, + ContractState { + status: ContractStatus::Deployed, + address, + deploy_tx: None, + }, + ); + state.save(state_path)?; + return Ok(()); + } else { + eyre::bail!( + "unexpected bytecode at {address}: expected {} bytes, found {} bytes", + expected_runtime.len(), + existing_code.len() + ); + } + } + + // Deploy + let receipt = deployer.deploy_create2(salt, initcode).await?; + eyre::ensure!( + receipt.success, + "CREATE2 deploy tx reverted for {name}: tx={}", + receipt.tx_hash + ); + + eprintln!(" tx={} address={address}", receipt.tx_hash); + + set_contract_state( + state, + name, + ContractState { + status: ContractStatus::Deployed, + address, + deploy_tx: Some(receipt.tx_hash), + }, + ); + state.save(state_path)?; + + Ok(()) +} + +/// Verify all deployed contracts have matching on-chain bytecode. +async fn verify_all( + deployer: &dyn ChainDeployer, + state: &mut DeployState, + _config: &DeployConfig, + chain_id: u64, +) -> eyre::Result<()> { + if let Some(ref cs) = state.contracts.admin_proxy { + if cs.status == ContractStatus::Deployed { + let on_chain = deployer.get_code(cs.address).await?; + let expected = contracts::admin_proxy::ADMIN_PROXY_BYTECODE; + eyre::ensure!( + on_chain.as_ref() == expected, + "bytecode mismatch at {}: expected {} bytes, got {} bytes", + cs.address, + expected.len(), + on_chain.len() + ); + let mut updated = cs.clone(); + updated.status = ContractStatus::Verified; + state.contracts.admin_proxy = Some(updated); + } + } + + if let Some(ref cs) = state.contracts.permit2 { + if cs.status == ContractStatus::Deployed { + let on_chain = deployer.get_code(cs.address).await?; + let expected = + contracts::permit2::expected_runtime_bytecode(chain_id, cs.address); + eyre::ensure!( + on_chain.as_ref() == expected.as_slice(), + "bytecode mismatch at {}: expected {} bytes, got {} bytes", + cs.address, + expected.len(), + on_chain.len() + ); + let mut updated = cs.clone(); + updated.status = ContractStatus::Verified; + state.contracts.permit2 = Some(updated); + } + } + + Ok(()) +} + +fn get_contract_status(state: &DeployState, name: &str) -> Option { + match name { + "admin_proxy" => state.contracts.admin_proxy.as_ref().map(|c| c.status), + "permit2" => state.contracts.permit2.as_ref().map(|c| c.status), + _ => None, + } +} + +fn set_contract_state(state: &mut DeployState, name: &str, cs: ContractState) { + match name { + "admin_proxy" => state.contracts.admin_proxy = Some(cs), + "permit2" => state.contracts.permit2 = Some(cs), + _ => {} + } +} + +fn build_deploy_manifest(state: &DeployState) -> serde_json::Value { + let mut manifest = serde_json::Map::new(); + if let Some(ref cs) = state.contracts.admin_proxy { + manifest.insert( + "admin_proxy".to_string(), + serde_json::Value::String(format!("{}", cs.address)), + ); + } + if let Some(ref cs) = state.contracts.permit2 { + manifest.insert( + "permit2".to_string(), + serde_json::Value::String(format!("{}", cs.address)), + ); + } + serde_json::Value::Object(manifest) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::*; + use crate::deploy::deployer::TxReceipt; + use alloy_primitives::{address, Bytes}; + use async_trait::async_trait; + use std::collections::HashMap; + use std::sync::Mutex; + + /// Mock deployer for testing the pipeline without a live chain. + struct MockDeployer { + chain_id: u64, + code: Mutex>, + deploys: Mutex)>>, + } + + impl MockDeployer { + fn new(chain_id: u64) -> Self { + let mut code = HashMap::new(); + code.insert(DETERMINISTIC_DEPLOYER, Bytes::from_static(&[0x01])); + Self { + chain_id, + code: Mutex::new(code), + deploys: Mutex::new(Vec::new()), + } + } + } + + #[async_trait] + impl ChainDeployer for MockDeployer { + async fn chain_id(&self) -> eyre::Result { + Ok(self.chain_id) + } + + async fn get_code(&self, address: Address) -> eyre::Result { + Ok(self + .code + .lock() + .unwrap() + .get(&address) + .cloned() + .unwrap_or_default()) + } + + async fn deploy_create2( + &self, + salt: B256, + initcode: &[u8], + ) -> eyre::Result { + self.deploys + .lock() + .unwrap() + .push((salt, initcode.to_vec())); + + // Simulate: place the expected runtime bytecode at the computed address + let address = compute_address(salt, initcode); + + // Determine which contract this is based on initcode + let runtime = + if initcode.len() > contracts::admin_proxy::ADMIN_PROXY_INITCODE.len() + && initcode[..contracts::admin_proxy::ADMIN_PROXY_INITCODE.len()] + == *contracts::admin_proxy::ADMIN_PROXY_INITCODE + { + Bytes::from_static(contracts::admin_proxy::ADMIN_PROXY_BYTECODE) + } else { + let runtime = contracts::permit2::expected_runtime_bytecode( + self.chain_id, + address, + ); + Bytes::from(runtime) + }; + + self.code.lock().unwrap().insert(address, runtime); + + Ok(TxReceipt { + tx_hash: B256::with_last_byte(0x01), + success: true, + }) + } + } + + fn test_config() -> DeployConfig { + DeployConfig { + chain: ChainConfig { chain_id: 1234 }, + contracts: ContractsConfig { + admin_proxy: Some(AdminProxyConfig { + address: None, + owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + }), + permit2: Some(Permit2Config { address: None }), + }, + } + } + + #[tokio::test] + async fn pipeline_deploys_both_contracts() { + let mock = MockDeployer::new(1234); + let tmp_state = tempfile::NamedTempFile::new().unwrap(); + std::fs::remove_file(tmp_state.path()).unwrap(); + + let cfg = PipelineConfig { + config: test_config(), + state_path: tmp_state.path().to_path_buf(), + addresses_out: None, + }; + + run(&cfg, &mock).await.unwrap(); + + let state = DeployState::load(tmp_state.path()).unwrap(); + assert_eq!( + state.contracts.admin_proxy.as_ref().unwrap().status, + ContractStatus::Verified + ); + assert_eq!( + state.contracts.permit2.as_ref().unwrap().status, + ContractStatus::Verified + ); + assert_eq!(mock.deploys.lock().unwrap().len(), 2); + } + + #[tokio::test] + async fn pipeline_skips_already_deployed() { + let mock = MockDeployer::new(1234); + let tmp_state = tempfile::NamedTempFile::new().unwrap(); + std::fs::remove_file(tmp_state.path()).unwrap(); + + let cfg = PipelineConfig { + config: test_config(), + state_path: tmp_state.path().to_path_buf(), + addresses_out: None, + }; + + // First run + run(&cfg, &mock).await.unwrap(); + assert_eq!(mock.deploys.lock().unwrap().len(), 2); + + // Second run — should skip both + run(&cfg, &mock).await.unwrap(); + assert_eq!(mock.deploys.lock().unwrap().len(), 2); // no new deploys + } + + #[tokio::test] + async fn pipeline_rejects_chain_id_mismatch() { + let mock = MockDeployer::new(9999); + let tmp_state = tempfile::NamedTempFile::new().unwrap(); + std::fs::remove_file(tmp_state.path()).unwrap(); + + let cfg = PipelineConfig { + config: test_config(), // chain_id = 1234 + state_path: tmp_state.path().to_path_buf(), + addresses_out: None, + }; + + let err = run(&cfg, &mock).await.unwrap_err().to_string(); + assert!(err.contains("chain_id mismatch"), "{err}"); + } + + #[tokio::test] + async fn pipeline_rejects_missing_deployer() { + let mock = MockDeployer::new(1234); + mock.code.lock().unwrap().remove(&DETERMINISTIC_DEPLOYER); + + let tmp_state = tempfile::NamedTempFile::new().unwrap(); + std::fs::remove_file(tmp_state.path()).unwrap(); + + let cfg = PipelineConfig { + config: test_config(), + state_path: tmp_state.path().to_path_buf(), + addresses_out: None, + }; + + let err = run(&cfg, &mock).await.unwrap_err().to_string(); + assert!(err.contains("deterministic deployer not found"), "{err}"); + } +} From 0c67446be63a71dd35ff6d69b530c9bdbd1ffdfc Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:32:30 +0200 Subject: [PATCH 55/81] feat(ev-deployer): wire up deploy subcommand in CLI --- bin/ev-deployer/src/main.rs | 48 ++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 57ec4372..d622e242 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -1,4 +1,4 @@ -//! EV Deployer — genesis alloc generator for ev-reth contracts. +//! EV Deployer — genesis alloc generator and live deployer for ev-reth contracts. mod config; mod contracts; @@ -9,11 +9,11 @@ mod output; use clap::{Parser, Subcommand}; use std::path::PathBuf; -/// EV Deployer: generate genesis alloc entries for ev-reth contracts. +/// EV Deployer: generate genesis alloc or deploy ev-reth contracts. #[derive(Parser)] #[command( name = "ev-deployer", - about = "Generate genesis alloc for ev-reth contracts" + about = "Generate genesis alloc or deploy ev-reth contracts" )] struct Cli { #[command(subcommand)] @@ -50,6 +50,28 @@ enum Command { #[arg(long)] addresses_out: Option, }, + /// Deploy contracts to a live chain via CREATE2. + Deploy { + /// Path to the deploy TOML config. + #[arg(long)] + config: PathBuf, + + /// RPC URL of the target chain. + #[arg(long, env = "EV_DEPLOYER_RPC_URL")] + rpc_url: String, + + /// Hex-encoded private key for signing transactions. + #[arg(long, env = "EV_DEPLOYER_PRIVATE_KEY")] + private_key: String, + + /// Path to the state file (created if absent, resumed if present). + #[arg(long)] + state: PathBuf, + + /// Write an address manifest to this file. + #[arg(long)] + addresses_out: Option, + }, /// Compute the address for a configured contract. ComputeAddress { /// Path to the deploy TOML config. @@ -98,6 +120,26 @@ fn main() -> eyre::Result<()> { eprintln!("Wrote address manifest to {}", addr_path.display()); } } + Command::Deploy { + config: config_path, + rpc_url, + private_key, + state: state_path, + addresses_out, + } => { + let cfg = config::DeployConfig::load(&config_path)?; + let deployer = deploy::deployer::LiveDeployer::new(&rpc_url, &private_key)?; + let pipeline_cfg = deploy::pipeline::PipelineConfig { + config: cfg, + state_path, + addresses_out, + }; + + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(deploy::pipeline::run(&pipeline_cfg, &deployer))?; + } Command::Init { output } => { let template = include_str!("init_template.toml"); From 308ac0efb07b1416c38d5084c597bcb5097dac0c Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:32:46 +0200 Subject: [PATCH 56/81] docs(ev-deployer): update init template for deploy mode --- bin/ev-deployer/src/init_template.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index e44eb6fb..966e5eb6 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -6,8 +6,9 @@ chain_id = 0 # ── Contracts ──────────────────────────────────────────── -# Uncomment and configure the contracts you want to include -# in the genesis alloc. +# Uncomment and configure the contracts you want to deploy. +# The `address` field is required for `genesis` mode but +# ignored in `deploy` mode (addresses come from CREATE2). # AdminProxy: transparent proxy with owner-based access control. # The owner address is stored in slot 0. From cbdaaa232fffed8dfd75c9be80fd3b8b0ec6a326 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:34:26 +0200 Subject: [PATCH 57/81] style(ev-deployer): fix rustfmt formatting --- bin/ev-deployer/src/contracts/admin_proxy.rs | 1 - bin/ev-deployer/src/deploy/create2.rs | 7 ++-- bin/ev-deployer/src/deploy/deployer.rs | 4 +- bin/ev-deployer/src/deploy/pipeline.rs | 39 +++++++------------- bin/ev-deployer/src/deploy/state.rs | 10 ++--- bin/ev-deployer/src/output.rs | 5 +-- 6 files changed, 25 insertions(+), 41 deletions(-) diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index 4709eb0d..fc1eea17 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -12,7 +12,6 @@ pub(crate) const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e57 /// Regenerate with: `cd contracts && forge inspect AdminProxy bytecode` pub(crate) const ADMIN_PROXY_INITCODE: &[u8] = &hex!("6080604052348015600e575f5ffd5b506112ab8061001c5f395ff3fe60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); - /// Build a genesis alloc entry for `AdminProxy`. pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { let address = config.address.expect("address required for genesis"); diff --git a/bin/ev-deployer/src/deploy/create2.rs b/bin/ev-deployer/src/deploy/create2.rs index b1d8deb7..970b8609 100644 --- a/bin/ev-deployer/src/deploy/create2.rs +++ b/bin/ev-deployer/src/deploy/create2.rs @@ -1,11 +1,12 @@ //! CREATE2 address computation. -use alloy_primitives::{Address, Bytes, B256, keccak256}; +use alloy_primitives::{keccak256, Address, Bytes, B256}; /// The deterministic deployer factory address (Nick's factory). /// See: https://github.com/Arachnid/deterministic-deployment-proxy -pub(crate) const DETERMINISTIC_DEPLOYER: Address = - Address::new(alloy_primitives::hex!("4e59b44847b379578588920ca78fbf26c0b4956c")); +pub(crate) const DETERMINISTIC_DEPLOYER: Address = Address::new(alloy_primitives::hex!( + "4e59b44847b379578588920ca78fbf26c0b4956c" +)); /// Compute the CREATE2 address for a contract deployed via the deterministic deployer. /// diff --git a/bin/ev-deployer/src/deploy/deployer.rs b/bin/ev-deployer/src/deploy/deployer.rs index 8fa95719..bff3bef8 100644 --- a/bin/ev-deployer/src/deploy/deployer.rs +++ b/bin/ev-deployer/src/deploy/deployer.rs @@ -37,7 +37,9 @@ pub(crate) struct LiveDeployer { impl LiveDeployer { /// Create a new LiveDeployer from an RPC URL and a hex-encoded private key. pub(crate) fn new(rpc_url: &str, private_key_hex: &str) -> eyre::Result { - let key_hex = private_key_hex.strip_prefix("0x").unwrap_or(private_key_hex); + let key_hex = private_key_hex + .strip_prefix("0x") + .unwrap_or(private_key_hex); let signer: PrivateKeySigner = key_hex.parse()?; let wallet = EthereumWallet::from(signer); diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 5dbf5b0a..5b965774 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -99,8 +99,7 @@ pub(crate) async fn run( let initcode = contracts::permit2::PERMIT2_INITCODE.to_vec(); let address = compute_address(salt, &initcode); - let expected_runtime = - contracts::permit2::expected_runtime_bytecode(chain_id, address); + let expected_runtime = contracts::permit2::expected_runtime_bytecode(chain_id, address); deploy_contract( deployer, @@ -242,8 +241,7 @@ async fn verify_all( if let Some(ref cs) = state.contracts.permit2 { if cs.status == ContractStatus::Deployed { let on_chain = deployer.get_code(cs.address).await?; - let expected = - contracts::permit2::expected_runtime_bytecode(chain_id, cs.address); + let expected = contracts::permit2::expected_runtime_bytecode(chain_id, cs.address); eyre::ensure!( on_chain.as_ref() == expected.as_slice(), "bytecode mismatch at {}: expected {} bytes, got {} bytes", @@ -338,33 +336,22 @@ mod tests { .unwrap_or_default()) } - async fn deploy_create2( - &self, - salt: B256, - initcode: &[u8], - ) -> eyre::Result { - self.deploys - .lock() - .unwrap() - .push((salt, initcode.to_vec())); + async fn deploy_create2(&self, salt: B256, initcode: &[u8]) -> eyre::Result { + self.deploys.lock().unwrap().push((salt, initcode.to_vec())); // Simulate: place the expected runtime bytecode at the computed address let address = compute_address(salt, initcode); // Determine which contract this is based on initcode - let runtime = - if initcode.len() > contracts::admin_proxy::ADMIN_PROXY_INITCODE.len() - && initcode[..contracts::admin_proxy::ADMIN_PROXY_INITCODE.len()] - == *contracts::admin_proxy::ADMIN_PROXY_INITCODE - { - Bytes::from_static(contracts::admin_proxy::ADMIN_PROXY_BYTECODE) - } else { - let runtime = contracts::permit2::expected_runtime_bytecode( - self.chain_id, - address, - ); - Bytes::from(runtime) - }; + let runtime = if initcode.len() > contracts::admin_proxy::ADMIN_PROXY_INITCODE.len() + && initcode[..contracts::admin_proxy::ADMIN_PROXY_INITCODE.len()] + == *contracts::admin_proxy::ADMIN_PROXY_INITCODE + { + Bytes::from_static(contracts::admin_proxy::ADMIN_PROXY_BYTECODE) + } else { + let runtime = contracts::permit2::expected_runtime_bytecode(self.chain_id, address); + Bytes::from(runtime) + }; self.code.lock().unwrap().insert(address, runtime); diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs index a3adefd2..8fad046a 100644 --- a/bin/ev-deployer/src/deploy/state.rs +++ b/bin/ev-deployer/src/deploy/state.rs @@ -122,7 +122,9 @@ impl DeployState { new_ap.owner ); } else { - eyre::bail!("immutability violation: admin_proxy was configured but is now missing"); + eyre::bail!( + "immutability violation: admin_proxy was configured but is now missing" + ); } } @@ -147,11 +149,7 @@ impl AppliedIntent { .admin_proxy .as_ref() .map(|ap| AppliedAdminProxy { owner: ap.owner }), - permit2: config - .contracts - .permit2 - .as_ref() - .map(|_| AppliedPermit2 {}), + permit2: config.contracts.permit2.as_ref().map(|_| AppliedPermit2 {}), } } } diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index d4349744..8053180c 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -18,10 +18,7 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { if let Some(ref p2) = config.contracts.permit2 { if let Some(addr) = p2.address { - manifest.insert( - "permit2".to_string(), - Value::String(format!("{}", addr)), - ); + manifest.insert("permit2".to_string(), Value::String(format!("{}", addr))); } } From 58dd78aecdad422137443d2a01b7093f0966d667 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:46:14 +0200 Subject: [PATCH 58/81] chore: update Cargo.lock for ev-deployer deploy dependencies --- Cargo.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ef8c2de4..48a65365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2921,12 +2921,18 @@ dependencies = [ name = "ev-deployer" version = "0.1.0" dependencies = [ + "alloy", "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer-local", + "async-trait", "clap", "eyre", + "rand 0.10.0", "serde", "serde_json", "tempfile", + "tokio", "toml 0.8.23", ] From 868bd3ad946fb07c1025fa05d08cd6a345edb9ec Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 17:54:33 +0200 Subject: [PATCH 59/81] fix(ev-deployer): address clippy lints and nightly rustfmt imports --- bin/ev-deployer/src/deploy/create2.rs | 4 +- bin/ev-deployer/src/deploy/deployer.rs | 10 ++- bin/ev-deployer/src/deploy/pipeline.rs | 108 ++++++++++++++----------- bin/ev-deployer/src/deploy/state.rs | 17 ++-- 4 files changed, 74 insertions(+), 65 deletions(-) diff --git a/bin/ev-deployer/src/deploy/create2.rs b/bin/ev-deployer/src/deploy/create2.rs index 970b8609..cb3b3cf6 100644 --- a/bin/ev-deployer/src/deploy/create2.rs +++ b/bin/ev-deployer/src/deploy/create2.rs @@ -3,7 +3,7 @@ use alloy_primitives::{keccak256, Address, Bytes, B256}; /// The deterministic deployer factory address (Nick's factory). -/// See: https://github.com/Arachnid/deterministic-deployment-proxy +/// See: pub(crate) const DETERMINISTIC_DEPLOYER: Address = Address::new(alloy_primitives::hex!( "4e59b44847b379578588920ca78fbf26c0b4956c" )); @@ -41,7 +41,7 @@ mod tests { let initcode = hex!("60006000f3"); let addr = compute_address(salt, &initcode); - let init_hash = keccak256(&initcode); + let init_hash = keccak256(initcode); let expected = DETERMINISTIC_DEPLOYER.create2(salt, init_hash); assert_eq!(addr, expected); } diff --git a/bin/ev-deployer/src/deploy/deployer.rs b/bin/ev-deployer/src/deploy/deployer.rs index bff3bef8..a673e2aa 100644 --- a/bin/ev-deployer/src/deploy/deployer.rs +++ b/bin/ev-deployer/src/deploy/deployer.rs @@ -1,8 +1,10 @@ -//! ChainDeployer trait and LiveDeployer implementation. +//! `ChainDeployer` trait and `LiveDeployer` implementation. use crate::deploy::create2::{build_factory_calldata, DETERMINISTIC_DEPLOYER}; -use alloy::network::EthereumWallet; -use alloy::providers::{Provider, ProviderBuilder}; +use alloy::{ + network::EthereumWallet, + providers::{Provider, ProviderBuilder}, +}; use alloy_primitives::{Address, Bytes, B256}; use alloy_rpc_types_eth::TransactionRequest; use alloy_signer_local::PrivateKeySigner; @@ -35,7 +37,7 @@ pub(crate) struct LiveDeployer { } impl LiveDeployer { - /// Create a new LiveDeployer from an RPC URL and a hex-encoded private key. + /// Create a new `LiveDeployer` from an RPC URL and a hex-encoded private key. pub(crate) fn new(rpc_url: &str, private_key_hex: &str) -> eyre::Result { let key_hex = private_key_hex .strip_prefix("0x") diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 5b965774..4b1256b8 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -1,10 +1,14 @@ //! Deploy pipeline: orchestrates the full deployment flow. -use crate::config::DeployConfig; -use crate::contracts; -use crate::deploy::create2::{compute_address, DETERMINISTIC_DEPLOYER}; -use crate::deploy::deployer::ChainDeployer; -use crate::deploy::state::{ContractState, ContractStatus, DeployState}; +use crate::{ + config::DeployConfig, + contracts, + deploy::{ + create2::{compute_address, DETERMINISTIC_DEPLOYER}, + deployer::ChainDeployer, + state::{ContractState, ContractStatus, DeployState}, + }, +}; use alloy_primitives::{Address, B256}; use std::path::{Path, PathBuf}; @@ -68,12 +72,14 @@ pub(crate) async fn run( deploy_contract( deployer, &mut state, - "admin_proxy", - address, - salt, - &initcode, - contracts::admin_proxy::ADMIN_PROXY_BYTECODE, - &pipeline_cfg.state_path, + &DeployContractParams { + name: "admin_proxy", + address, + salt, + initcode: &initcode, + expected_runtime: contracts::admin_proxy::ADMIN_PROXY_BYTECODE, + state_path: &pipeline_cfg.state_path, + }, ) .await?; } else { @@ -81,18 +87,10 @@ pub(crate) async fn run( } // ── Step 3: Deploy Permit2 ── - if pipeline_cfg.config.contracts.permit2.is_some() { + if let Some(ref p2_config) = pipeline_cfg.config.contracts.permit2 { eprintln!("[4/5] Deploying Permit2..."); - if pipeline_cfg - .config - .contracts - .permit2 - .as_ref() - .unwrap() - .address - .is_some() - { + if p2_config.address.is_some() { eprintln!(" WARN: contracts.permit2.address is ignored in deploy mode"); } @@ -104,12 +102,14 @@ pub(crate) async fn run( deploy_contract( deployer, &mut state, - "permit2", - address, - salt, - &initcode, - &expected_runtime, - &pipeline_cfg.state_path, + &DeployContractParams { + name: "permit2", + address, + salt, + initcode: &initcode, + expected_runtime: &expected_runtime, + state_path: &pipeline_cfg.state_path, + }, ) .await?; } else { @@ -139,7 +139,7 @@ pub(crate) async fn run( Ok(()) } -/// Build AdminProxy initcode with constructor argument. +/// Build `AdminProxy` initcode with constructor argument. fn build_admin_proxy_initcode(owner: Address) -> Vec { let mut initcode = contracts::admin_proxy::ADMIN_PROXY_INITCODE.to_vec(); // ABI-encode the owner address as a 32-byte word and append @@ -147,17 +147,30 @@ fn build_admin_proxy_initcode(owner: Address) -> Vec { initcode } +/// Parameters for deploying a single contract. +struct DeployContractParams<'a> { + name: &'a str, + address: Address, + salt: B256, + initcode: &'a [u8], + expected_runtime: &'a [u8], + state_path: &'a Path, +} + /// Deploy a single contract via CREATE2 with idempotency. async fn deploy_contract( deployer: &dyn ChainDeployer, state: &mut DeployState, - name: &str, - address: Address, - salt: B256, - initcode: &[u8], - expected_runtime: &[u8], - state_path: &Path, + params: &DeployContractParams<'_>, ) -> eyre::Result<()> { + let DeployContractParams { + name, + address, + salt, + initcode, + expected_runtime, + state_path, + } = params; // Check if already deployed or verified in state let current_status = get_contract_status(state, name); if current_status >= Some(ContractStatus::Deployed) { @@ -166,32 +179,31 @@ async fn deploy_contract( } // Idempotency: check if code already exists on-chain - let existing_code = deployer.get_code(address).await?; + let existing_code = deployer.get_code(*address).await?; if !existing_code.is_empty() { - if existing_code.as_ref() == expected_runtime { + if existing_code.as_ref() == *expected_runtime { eprintln!(" found matching bytecode at {address}, marking as deployed"); set_contract_state( state, name, ContractState { status: ContractStatus::Deployed, - address, + address: *address, deploy_tx: None, }, ); state.save(state_path)?; return Ok(()); - } else { - eyre::bail!( - "unexpected bytecode at {address}: expected {} bytes, found {} bytes", - expected_runtime.len(), - existing_code.len() - ); } + eyre::bail!( + "unexpected bytecode at {address}: expected {} bytes, found {} bytes", + expected_runtime.len(), + existing_code.len() + ); } // Deploy - let receipt = deployer.deploy_create2(salt, initcode).await?; + let receipt = deployer.deploy_create2(*salt, initcode).await?; eyre::ensure!( receipt.success, "CREATE2 deploy tx reverted for {name}: tx={}", @@ -205,7 +217,7 @@ async fn deploy_contract( name, ContractState { status: ContractStatus::Deployed, - address, + address: *address, deploy_tx: Some(receipt.tx_hash), }, ); @@ -294,12 +306,10 @@ fn build_deploy_manifest(state: &DeployState) -> serde_json::Value { #[cfg(test)] mod tests { use super::*; - use crate::config::*; - use crate::deploy::deployer::TxReceipt; + use crate::{config::*, deploy::deployer::TxReceipt}; use alloy_primitives::{address, Bytes}; use async_trait::async_trait; - use std::collections::HashMap; - use std::sync::Mutex; + use std::{collections::HashMap, sync::Mutex}; /// Mock deployer for testing the pipeline without a live chain. struct MockDeployer { diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs index 8fad046a..17759aa0 100644 --- a/bin/ev-deployer/src/deploy/state.rs +++ b/bin/ev-deployer/src/deploy/state.rs @@ -207,9 +207,8 @@ mod tests { #[test] fn immutability_rejects_chain_id_change() { - let config = test_config(); - let state = DeployState::new(&config); - let mut changed = config.clone(); + let mut changed = test_config(); + let state = DeployState::new(&changed); changed.chain.chain_id = 9999; let err = state .validate_immutability(&changed) @@ -220,9 +219,8 @@ mod tests { #[test] fn immutability_rejects_owner_change() { - let config = test_config(); - let state = DeployState::new(&config); - let mut changed = config.clone(); + let mut changed = test_config(); + let state = DeployState::new(&changed); changed.contracts.admin_proxy.as_mut().unwrap().owner = address!("0000000000000000000000000000000000000001"); let err = state @@ -247,16 +245,15 @@ mod tests { let state = DeployState::new(&config); // Now add permit2 — this should be allowed - let mut extended = config.clone(); + let mut extended = config; extended.contracts.permit2 = Some(Permit2Config { address: None }); assert!(state.validate_immutability(&extended).is_ok()); } #[test] fn immutability_rejects_removing_contract() { - let config = test_config(); - let state = DeployState::new(&config); - let mut changed = config.clone(); + let mut changed = test_config(); + let state = DeployState::new(&changed); changed.contracts.admin_proxy = None; let err = state .validate_immutability(&changed) From 77c098be4835caf20cf36c5c001d0231eab349ee Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 18:26:13 +0200 Subject: [PATCH 60/81] fix(ev-deployer): remove AdminProxy from deploy pipeline AdminProxy has an empty constructor and sets owner via genesis storage slot 0, making it incompatible with live CREATE2 deployment where the owner would remain address(0). Restrict AdminProxy to genesis-only mode. --- bin/ev-deployer/src/contracts/admin_proxy.rs | 5 - bin/ev-deployer/src/deploy/pipeline.rs | 125 ++++--------------- bin/ev-deployer/src/deploy/state.rs | 62 +-------- 3 files changed, 31 insertions(+), 161 deletions(-) diff --git a/bin/ev-deployer/src/contracts/admin_proxy.rs b/bin/ev-deployer/src/contracts/admin_proxy.rs index fc1eea17..defa9c28 100644 --- a/bin/ev-deployer/src/contracts/admin_proxy.rs +++ b/bin/ev-deployer/src/contracts/admin_proxy.rs @@ -7,11 +7,6 @@ use std::collections::BTreeMap; /// `AdminProxy` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). /// Regenerate with: `cd contracts && forge inspect AdminProxy deployedBytecode` pub(crate) const ADMIN_PROXY_BYTECODE: &[u8] = &hex!("60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); -/// `AdminProxy` creation bytecode (initcode) compiled with solc 0.8.33 (`cbor_metadata=false`). -/// The constructor is empty; owner is set via storage, not constructor args. -/// Regenerate with: `cd contracts && forge inspect AdminProxy bytecode` -pub(crate) const ADMIN_PROXY_INITCODE: &[u8] = &hex!("6080604052348015600e575f5ffd5b506112ab8061001c5f395ff3fe60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056"); - /// Build a genesis alloc entry for `AdminProxy`. pub(crate) fn build(config: &AdminProxyConfig) -> GenesisContract { let address = config.address.expect("address required for genesis"); diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 4b1256b8..9db2ead8 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -25,7 +25,7 @@ pub(crate) async fn run( deployer: &dyn ChainDeployer, ) -> eyre::Result<()> { // ── Step 1: Init ── - eprintln!("[1/5] Connecting to RPC..."); + eprintln!("[1/4] Connecting to RPC..."); let chain_id = deployer.chain_id().await?; eprintln!(" chain_id={chain_id}"); @@ -36,7 +36,7 @@ pub(crate) async fn run( chain_id ); - eprintln!("[2/5] Verifying deterministic deployer..."); + eprintln!("[2/4] Verifying deterministic deployer..."); let deployer_code = deployer.get_code(DETERMINISTIC_DEPLOYER).await?; eyre::ensure!( !deployer_code.is_empty(), @@ -58,37 +58,9 @@ pub(crate) async fn run( let salt = state.create2_salt; - // ── Step 2: Deploy AdminProxy ── - if let Some(ref ap_config) = pipeline_cfg.config.contracts.admin_proxy { - eprintln!("[3/5] Deploying AdminProxy..."); - - if ap_config.address.is_some() { - eprintln!(" WARN: contracts.admin_proxy.address is ignored in deploy mode"); - } - - let initcode = build_admin_proxy_initcode(ap_config.owner); - let address = compute_address(salt, &initcode); - - deploy_contract( - deployer, - &mut state, - &DeployContractParams { - name: "admin_proxy", - address, - salt, - initcode: &initcode, - expected_runtime: contracts::admin_proxy::ADMIN_PROXY_BYTECODE, - state_path: &pipeline_cfg.state_path, - }, - ) - .await?; - } else { - eprintln!("[3/5] AdminProxy not configured, skipping"); - } - - // ── Step 3: Deploy Permit2 ── + // ── Step 2: Deploy Permit2 ── if let Some(ref p2_config) = pipeline_cfg.config.contracts.permit2 { - eprintln!("[4/5] Deploying Permit2..."); + eprintln!("[3/4] Deploying Permit2..."); if p2_config.address.is_some() { eprintln!(" WARN: contracts.permit2.address is ignored in deploy mode"); @@ -113,11 +85,11 @@ pub(crate) async fn run( ) .await?; } else { - eprintln!("[4/5] Permit2 not configured, skipping"); + eprintln!("[3/4] Permit2 not configured, skipping"); } - // ── Step 4: Verify ── - eprintln!("[5/5] Verifying bytecodes..."); + // ── Step 3: Verify ── + eprintln!("[4/4] Verifying bytecodes..."); verify_all(deployer, &mut state, &pipeline_cfg.config, chain_id).await?; state.save(&pipeline_cfg.state_path)?; eprintln!(" OK"); @@ -139,14 +111,6 @@ pub(crate) async fn run( Ok(()) } -/// Build `AdminProxy` initcode with constructor argument. -fn build_admin_proxy_initcode(owner: Address) -> Vec { - let mut initcode = contracts::admin_proxy::ADMIN_PROXY_INITCODE.to_vec(); - // ABI-encode the owner address as a 32-byte word and append - initcode.extend_from_slice(owner.into_word().as_slice()); - initcode -} - /// Parameters for deploying a single contract. struct DeployContractParams<'a> { name: &'a str, @@ -233,23 +197,6 @@ async fn verify_all( _config: &DeployConfig, chain_id: u64, ) -> eyre::Result<()> { - if let Some(ref cs) = state.contracts.admin_proxy { - if cs.status == ContractStatus::Deployed { - let on_chain = deployer.get_code(cs.address).await?; - let expected = contracts::admin_proxy::ADMIN_PROXY_BYTECODE; - eyre::ensure!( - on_chain.as_ref() == expected, - "bytecode mismatch at {}: expected {} bytes, got {} bytes", - cs.address, - expected.len(), - on_chain.len() - ); - let mut updated = cs.clone(); - updated.status = ContractStatus::Verified; - state.contracts.admin_proxy = Some(updated); - } - } - if let Some(ref cs) = state.contracts.permit2 { if cs.status == ContractStatus::Deployed { let on_chain = deployer.get_code(cs.address).await?; @@ -271,29 +218,21 @@ async fn verify_all( } fn get_contract_status(state: &DeployState, name: &str) -> Option { - match name { - "admin_proxy" => state.contracts.admin_proxy.as_ref().map(|c| c.status), - "permit2" => state.contracts.permit2.as_ref().map(|c| c.status), - _ => None, + if name == "permit2" { + state.contracts.permit2.as_ref().map(|c| c.status) + } else { + None } } fn set_contract_state(state: &mut DeployState, name: &str, cs: ContractState) { - match name { - "admin_proxy" => state.contracts.admin_proxy = Some(cs), - "permit2" => state.contracts.permit2 = Some(cs), - _ => {} + if name == "permit2" { + state.contracts.permit2 = Some(cs); } } fn build_deploy_manifest(state: &DeployState) -> serde_json::Value { let mut manifest = serde_json::Map::new(); - if let Some(ref cs) = state.contracts.admin_proxy { - manifest.insert( - "admin_proxy".to_string(), - serde_json::Value::String(format!("{}", cs.address)), - ); - } if let Some(ref cs) = state.contracts.permit2 { manifest.insert( "permit2".to_string(), @@ -307,7 +246,7 @@ fn build_deploy_manifest(state: &DeployState) -> serde_json::Value { mod tests { use super::*; use crate::{config::*, deploy::deployer::TxReceipt}; - use alloy_primitives::{address, Bytes}; + use alloy_primitives::Bytes; use async_trait::async_trait; use std::{collections::HashMap, sync::Mutex}; @@ -352,18 +291,11 @@ mod tests { // Simulate: place the expected runtime bytecode at the computed address let address = compute_address(salt, initcode); - // Determine which contract this is based on initcode - let runtime = if initcode.len() > contracts::admin_proxy::ADMIN_PROXY_INITCODE.len() - && initcode[..contracts::admin_proxy::ADMIN_PROXY_INITCODE.len()] - == *contracts::admin_proxy::ADMIN_PROXY_INITCODE - { - Bytes::from_static(contracts::admin_proxy::ADMIN_PROXY_BYTECODE) - } else { - let runtime = contracts::permit2::expected_runtime_bytecode(self.chain_id, address); - Bytes::from(runtime) - }; - - self.code.lock().unwrap().insert(address, runtime); + let runtime = contracts::permit2::expected_runtime_bytecode(self.chain_id, address); + self.code + .lock() + .unwrap() + .insert(address, Bytes::from(runtime)); Ok(TxReceipt { tx_hash: B256::with_last_byte(0x01), @@ -376,17 +308,14 @@ mod tests { DeployConfig { chain: ChainConfig { chain_id: 1234 }, contracts: ContractsConfig { - admin_proxy: Some(AdminProxyConfig { - address: None, - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - }), + admin_proxy: None, permit2: Some(Permit2Config { address: None }), }, } } #[tokio::test] - async fn pipeline_deploys_both_contracts() { + async fn pipeline_deploys_permit2() { let mock = MockDeployer::new(1234); let tmp_state = tempfile::NamedTempFile::new().unwrap(); std::fs::remove_file(tmp_state.path()).unwrap(); @@ -400,15 +329,11 @@ mod tests { run(&cfg, &mock).await.unwrap(); let state = DeployState::load(tmp_state.path()).unwrap(); - assert_eq!( - state.contracts.admin_proxy.as_ref().unwrap().status, - ContractStatus::Verified - ); assert_eq!( state.contracts.permit2.as_ref().unwrap().status, ContractStatus::Verified ); - assert_eq!(mock.deploys.lock().unwrap().len(), 2); + assert_eq!(mock.deploys.lock().unwrap().len(), 1); } #[tokio::test] @@ -425,11 +350,11 @@ mod tests { // First run run(&cfg, &mock).await.unwrap(); - assert_eq!(mock.deploys.lock().unwrap().len(), 2); + assert_eq!(mock.deploys.lock().unwrap().len(), 1); - // Second run — should skip both + // Second run — should skip run(&cfg, &mock).await.unwrap(); - assert_eq!(mock.deploys.lock().unwrap().len(), 2); // no new deploys + assert_eq!(mock.deploys.lock().unwrap().len(), 1); // no new deploys } #[tokio::test] diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs index 17759aa0..87a6a63f 100644 --- a/bin/ev-deployer/src/deploy/state.rs +++ b/bin/ev-deployer/src/deploy/state.rs @@ -27,22 +27,15 @@ pub(crate) struct DeployState { #[derive(Debug, Serialize, Deserialize, PartialEq)] pub(crate) struct AppliedIntent { pub chain_id: u64, - pub admin_proxy: Option, pub permit2: Option, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub(crate) struct AppliedAdminProxy { - pub owner: Address, -} - #[derive(Debug, Serialize, Deserialize, PartialEq)] pub(crate) struct AppliedPermit2 {} /// Per-contract deployment states. #[derive(Debug, Default, Serialize, Deserialize)] pub(crate) struct ContractStates { - pub admin_proxy: Option, pub permit2: Option, } @@ -112,22 +105,6 @@ impl DeployState { config.chain.chain_id ); - // If admin_proxy was in the original intent, its owner must not change - if let Some(ref original_ap) = current.admin_proxy { - if let Some(ref new_ap) = config.contracts.admin_proxy { - eyre::ensure!( - new_ap.owner == original_ap.owner, - "immutability violation: admin_proxy.owner changed from {} to {}", - original_ap.owner, - new_ap.owner - ); - } else { - eyre::bail!( - "immutability violation: admin_proxy was configured but is now missing" - ); - } - } - // If permit2 was in the original intent, it must still be present if current.permit2.is_some() { eyre::ensure!( @@ -144,11 +121,6 @@ impl AppliedIntent { fn from_config(config: &DeployConfig) -> Self { Self { chain_id: config.chain.chain_id, - admin_proxy: config - .contracts - .admin_proxy - .as_ref() - .map(|ap| AppliedAdminProxy { owner: ap.owner }), permit2: config.contracts.permit2.as_ref().map(|_| AppliedPermit2 {}), } } @@ -158,16 +130,12 @@ impl AppliedIntent { mod tests { use super::*; use crate::config::*; - use alloy_primitives::address; fn test_config() -> DeployConfig { DeployConfig { chain: ChainConfig { chain_id: 1234 }, contracts: ContractsConfig { - admin_proxy: Some(AdminProxyConfig { - address: None, - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - }), + admin_proxy: None, permit2: Some(Permit2Config { address: None }), }, } @@ -184,7 +152,6 @@ mod tests { fn new_state_snapshots_intent() { let state = DeployState::new(&test_config()); assert_eq!(state.applied_intent.chain_id, 1234); - assert!(state.applied_intent.admin_proxy.is_some()); assert!(state.applied_intent.permit2.is_some()); } @@ -218,49 +185,32 @@ mod tests { } #[test] - fn immutability_rejects_owner_change() { - let mut changed = test_config(); - let state = DeployState::new(&changed); - changed.contracts.admin_proxy.as_mut().unwrap().owner = - address!("0000000000000000000000000000000000000001"); - let err = state - .validate_immutability(&changed) - .unwrap_err() - .to_string(); - assert!(err.contains("admin_proxy.owner changed"), "{err}"); - } - - #[test] - fn immutability_allows_adding_new_contract() { + fn immutability_allows_adding_permit2() { let config = DeployConfig { chain: ChainConfig { chain_id: 1234 }, contracts: ContractsConfig { - admin_proxy: Some(AdminProxyConfig { - address: None, - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - }), + admin_proxy: None, permit2: None, }, }; let state = DeployState::new(&config); - // Now add permit2 — this should be allowed let mut extended = config; extended.contracts.permit2 = Some(Permit2Config { address: None }); assert!(state.validate_immutability(&extended).is_ok()); } #[test] - fn immutability_rejects_removing_contract() { + fn immutability_rejects_removing_permit2() { let mut changed = test_config(); let state = DeployState::new(&changed); - changed.contracts.admin_proxy = None; + changed.contracts.permit2 = None; let err = state .validate_immutability(&changed) .unwrap_err() .to_string(); assert!( - err.contains("admin_proxy was configured but is now missing"), + err.contains("permit2 was configured but is now missing"), "{err}" ); } From 922af604558a160f0b4e89fbe07e6d8f72b850f0 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 30 Mar 2026 20:20:53 +0200 Subject: [PATCH 61/81] fix(ev-deployer): use atomic writes for deploy state file Write to a .tmp file first, then rename into place. Prevents state file corruption if the process crashes mid-write. --- bin/ev-deployer/src/deploy/state.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs index 87a6a63f..ea55371a 100644 --- a/bin/ev-deployer/src/deploy/state.rs +++ b/bin/ev-deployer/src/deploy/state.rs @@ -86,10 +86,12 @@ impl DeployState { Ok(state) } - /// Save state to a JSON file. + /// Save state to a JSON file atomically (write to tmp, then rename). pub(crate) fn save(&self, path: &Path) -> eyre::Result<()> { let json = serde_json::to_string_pretty(self)?; - std::fs::write(path, json)?; + let tmp = path.with_extension("tmp"); + std::fs::write(&tmp, &json)?; + std::fs::rename(&tmp, path)?; Ok(()) } From bb3522861e18049d3a674a776a805c13d25a742c Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 12:08:27 +0200 Subject: [PATCH 62/81] feat(ev-deployer): add CLI flags to init command --- bin/ev-deployer/src/main.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index d622e242..95eb3c43 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -6,6 +6,7 @@ mod deploy; mod genesis; mod output; +use alloy_primitives::Address; use clap::{Parser, Subcommand}; use std::path::PathBuf; @@ -22,11 +23,23 @@ struct Cli { #[derive(Subcommand)] enum Command { - /// Generate a starter config file with all supported contracts commented out. + /// Generate a starter config file with all supported contracts. Init { /// Write config to this file instead of stdout. #[arg(long)] output: Option, + + /// Set the chain ID (defaults to 0). + #[arg(long)] + chain_id: Option, + + /// Include Permit2 with its canonical address. + #[arg(long)] + permit2: bool, + + /// Include AdminProxy with the given owner address. + #[arg(long)] + admin_proxy_owner: Option
, }, /// Generate genesis alloc JSON from a deploy config. Genesis { @@ -140,7 +153,7 @@ fn main() -> eyre::Result<()> { .build()? .block_on(deploy::pipeline::run(&pipeline_cfg, &deployer))?; } - Command::Init { output } => { + Command::Init { output, .. } => { let template = include_str!("init_template.toml"); if let Some(ref out_path) = output { From 48a4d3998c6db1445c706ea5988725ee16ad592b Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 12:10:38 +0200 Subject: [PATCH 63/81] feat(ev-deployer): add template builder with parameterized tests --- bin/ev-deployer/src/init.rs | 145 ++++++++++++++++++++++++++++++++++++ bin/ev-deployer/src/main.rs | 1 + 2 files changed, 146 insertions(+) create mode 100644 bin/ev-deployer/src/init.rs diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs new file mode 100644 index 00000000..a45a5984 --- /dev/null +++ b/bin/ev-deployer/src/init.rs @@ -0,0 +1,145 @@ +//! Dynamic config template generation for the `init` command. + +/// Parameters for generating the init template. +pub(crate) struct InitParams { + pub chain_id: u64, + pub permit2: bool, + pub admin_proxy_owner: Option, +} + +/// Generate a TOML config template based on the given parameters. +pub(crate) fn generate_template(params: &InitParams) -> String { + let mut out = String::new(); + + // Header + out.push_str("# EV Deployer configuration\n"); + out.push_str("# See: bin/ev-deployer/README.md\n"); + out.push_str("\n"); + + // Chain + out.push_str("[chain]\n"); + out.push_str("# The chain ID for the target network.\n"); + out.push_str(&format!("chain_id = {}\n", params.chain_id)); + + // Contracts section header + out.push_str("\n"); + out.push_str("# ── Contracts ────────────────────────────────────────────\n"); + out.push_str("# Uncomment and configure the contracts you want to deploy.\n"); + out.push_str("# The `address` field is required for `genesis` mode but\n"); + out.push_str("# ignored in `deploy` mode (addresses come from CREATE2).\n"); + + // AdminProxy + out.push_str("\n"); + if let Some(ref owner) = params.admin_proxy_owner { + out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); + out.push_str("# The owner address is stored in slot 0.\n"); + out.push_str("[contracts.admin_proxy]\n"); + out.push_str("address = \"0x000000000000000000000000000000000000Ad00\"\n"); + out.push_str(&format!("owner = \"{owner}\"\n")); + } else { + out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); + out.push_str("# The owner address is stored in slot 0.\n"); + out.push_str("# [contracts.admin_proxy]\n"); + out.push_str("# address = \"0x000000000000000000000000000000000000Ad00\"\n"); + out.push_str("# owner = \"0x...\"\n"); + } + + // Permit2 + out.push_str("\n"); + if params.permit2 { + out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); + out.push_str("[contracts.permit2]\n"); + out.push_str("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + } else { + out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); + out.push_str("# [contracts.permit2]\n"); + out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + } + + out +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The static template that the old `init` used to emit. + const LEGACY_TEMPLATE: &str = include_str!("init_template.toml"); + + #[test] + fn default_params_match_legacy_template() { + let params = InitParams { + chain_id: 0, + permit2: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert_eq!(output, LEGACY_TEMPLATE); + } + + #[test] + fn custom_chain_id() { + let params = InitParams { + chain_id: 42170, + permit2: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!(output.contains("chain_id = 42170"), "{output}"); + assert!(output.contains("# [contracts.permit2]"), "{output}"); + assert!(output.contains("# [contracts.admin_proxy]"), "{output}"); + } + + #[test] + fn permit2_enabled() { + let params = InitParams { + chain_id: 0, + permit2: true, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!(output.contains("[contracts.permit2]\n"), "{output}"); + assert!( + output.contains("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\""), + "{output}" + ); + assert!(output.contains("# [contracts.admin_proxy]"), "{output}"); + } + + #[test] + fn admin_proxy_with_owner() { + let params = InitParams { + chain_id: 0, + permit2: false, + admin_proxy_owner: Some( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(), + ), + }; + let output = generate_template(¶ms); + assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); + assert!( + output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), + "{output}" + ); + assert!(output.contains("# [contracts.permit2]"), "{output}"); + } + + #[test] + fn all_flags_combined() { + let params = InitParams { + chain_id: 1234, + permit2: true, + admin_proxy_owner: Some( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(), + ), + }; + let output = generate_template(¶ms); + assert!(output.contains("chain_id = 1234"), "{output}"); + assert!(output.contains("[contracts.permit2]\n"), "{output}"); + assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); + assert!( + output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), + "{output}" + ); + } +} diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 95eb3c43..69ade86d 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -4,6 +4,7 @@ mod config; mod contracts; mod deploy; mod genesis; +mod init; mod output; use alloy_primitives::Address; From 9771899c0dcf11f614c37525e7a3a85b48531ba3 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 12:18:16 +0200 Subject: [PATCH 64/81] feat(ev-deployer): wire parameterized init into CLI --- bin/ev-deployer/src/main.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 69ade86d..08bf7a38 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -154,11 +154,21 @@ fn main() -> eyre::Result<()> { .build()? .block_on(deploy::pipeline::run(&pipeline_cfg, &deployer))?; } - Command::Init { output, .. } => { - let template = include_str!("init_template.toml"); + Command::Init { + output, + chain_id, + permit2, + admin_proxy_owner, + } => { + let params = init::InitParams { + chain_id: chain_id.unwrap_or(0), + permit2, + admin_proxy_owner: admin_proxy_owner.map(|a| format!("{a}")), + }; + let template = init::generate_template(¶ms); if let Some(ref out_path) = output { - std::fs::write(out_path, template)?; + std::fs::write(out_path, &template)?; eprintln!("Wrote config to {}", out_path.display()); } else { print!("{template}"); From 9c2fcb6758c58761cb5e5a8d18a537567b9347ef Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 12:19:53 +0200 Subject: [PATCH 65/81] docs(ev-deployer): rewrite README as comprehensive user guide --- bin/ev-deployer/README.md | 174 ++++++++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 56 deletions(-) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 4aad2972..f38e1864 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -1,6 +1,34 @@ # EV Deployer -CLI tool for generating genesis alloc entries for ev-reth contracts. It reads a declarative TOML config and produces the JSON needed to embed contracts into a chain's genesis state. +CLI tool for deploying ev-reth contracts. It reads a declarative TOML config and either embeds contracts into a chain's genesis state or deploys them to a live chain via CREATE2. + +## Modes of Operation + +EV Deployer has two deployment modes: + +| Mode | When to use | What it does | +|------|-------------|-------------| +| **genesis** | Before the chain starts | Produces JSON alloc entries to embed contracts into the genesis state. No RPC needed. | +| **deploy** | On a running chain | Deploys contracts via CREATE2 through the deterministic deployer. Requires RPC + signer. | + +Both modes read the same TOML config. The `address` field in each contract section is used by `genesis` to place the contract at that exact address. In `deploy` mode, addresses are computed deterministically via CREATE2 and the config `address` is ignored. + +## Quick Start + +```bash +# 1. Generate a config pre-populated for your chain +ev-deployer init --chain-id 42170 --permit2 --output deploy.toml + +# 2a. Genesis mode: embed into genesis state +ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json + +# 2b. Deploy mode: deploy to a live chain +ev-deployer deploy \ + --config deploy.toml \ + --rpc-url http://localhost:8545 \ + --private-key 0x... \ + --state deploy-state.json +``` ## Building @@ -10,98 +38,132 @@ just build-deployer The binary is output to `target/release/ev-deployer`. -## Configuration +## Commands -EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example. +### `init` -### Config reference +Generate a starter config file. -#### `[chain]` +```bash +# Bare template (all contracts commented out) +ev-deployer init + +# Pre-populated with chain ID and Permit2 +ev-deployer init --chain-id 42170 --permit2 + +# Full config with all contracts +ev-deployer init \ + --chain-id 42170 \ + --permit2 \ + --admin-proxy-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ + --output deploy.toml +``` -| Field | Type | Description | -|------------|------|-------------| -| `chain_id` | u64 | Chain ID | +| Flag | Description | +|------|-------------| +| `--output ` | Write to file instead of stdout | +| `--chain-id ` | Set the chain ID (defaults to 0) | +| `--permit2` | Enable Permit2 with its canonical address | +| `--admin-proxy-owner ` | Enable AdminProxy with the given owner | -#### `[contracts.admin_proxy]` +### `genesis` -| Field | Type | Description | -|-----------|---------|---------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner (must not be zero) | +Generate genesis alloc JSON from a config. -#### `[contracts.permit2]` +```bash +# Print alloc to stdout +ev-deployer genesis --config deploy.toml -| Field | Type | Description | -|-----------|---------|----------------------------------------------------------| -| `address` | address | Address to deploy at (canonical: `0x000000000022D473030F116dDEE9F6B43aC78BA3`) | +# Write to file +ev-deployer genesis --config deploy.toml --output alloc.json -## Usage +# Merge into an existing genesis file +ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json -### Generate a starter config +# Overwrite existing addresses when merging +ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json --force -```bash -ev-deployer init --output deploy.toml +# Also export an address manifest +ev-deployer genesis --config deploy.toml --addresses-out addresses.json ``` -This creates a TOML config template with all supported contracts commented out and documented. +In genesis mode, every configured contract must have an `address` field. -### Generate genesis alloc +### `deploy` -Print alloc JSON to stdout: +Deploy contracts to a live chain via CREATE2. ```bash -ev-deployer genesis --config deploy.toml +ev-deployer deploy \ + --config deploy.toml \ + --rpc-url http://localhost:8545 \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --state deploy-state.json \ + --addresses-out addresses.json ``` -Write to a file: +| Flag | Env var | Description | +|------|---------|-------------| +| `--config ` | | Path to the TOML config | +| `--rpc-url ` | `EV_DEPLOYER_RPC_URL` | RPC endpoint of the target chain | +| `--private-key ` | `EV_DEPLOYER_PRIVATE_KEY` | Hex-encoded private key for signing | +| `--state ` | | Path to the state file (created if absent) | +| `--addresses-out ` | | Write a JSON address manifest | -```bash -ev-deployer genesis --config deploy.toml --output alloc.json -``` +The deploy pipeline: -### Merge into an existing genesis file +1. Connects to the RPC and verifies the chain ID matches the config. +2. Checks that the deterministic deployer (`0x4e59b44847b379578588920ca78fbf26c0b4956c`) exists on-chain. +3. Deploys each configured contract via CREATE2. +4. Verifies that the on-chain bytecode matches the expected bytecode (including patched immutables). -Insert the generated entries into an existing `genesis.json`. The merged result is written to `--output` (or stdout if `--output` is omitted): +The `address` field in the config is **ignored** in deploy mode — addresses come from the CREATE2 computation. -```bash -ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json -``` +#### State file and resumability -If an address already exists in the genesis, the command fails. Use `--force` to overwrite: +The `--state` file tracks deployment progress. On first run it generates a random CREATE2 salt and records which contracts have been deployed. If the process is interrupted, re-running with the same state file resumes where it left off. -```bash -ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json --force -``` +Immutability rules protect against accidental misconfiguration on resume: + +- The `chain_id` cannot change between runs. +- A contract that was configured in the original run cannot be removed. +- New contracts can be added to subsequent runs. -### Export address manifest +### `compute-address` -Write a JSON mapping of contract names to their configured addresses: +Look up the configured address for a contract. ```bash -ev-deployer genesis --config deploy.toml --addresses-out addresses.json +ev-deployer compute-address --config deploy.toml --contract permit2 ``` -Output: +## Config Reference -```json -{ - "admin_proxy": "0x000000000000000000000000000000000000Ad00", - "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3" -} -``` +### `[chain]` -### Look up a contract address +| Field | Type | Description | +|-------|------|-------------| +| `chain_id` | u64 | Chain ID | -```bash -ev-deployer compute-address --config deploy.toml --contract admin_proxy -``` +### `[contracts.admin_proxy]` + +| Field | Type | Description | +|-------|------|-------------| +| `address` | address | Address to deploy at (required for genesis, ignored for deploy) | +| `owner` | address | Owner address (must not be zero) | + +### `[contracts.permit2]` + +| Field | Type | Description | +|-------|------|-------------| +| `address` | address | Address to deploy at (canonical: `0x000000000022D473030F116dDEE9F6B43aC78BA3`). Required for genesis, ignored for deploy. | ## Contracts -| Contract | Description | -|---------------|----------------------------------------------------| -| `admin_proxy` | Proxy contract with owner-based access control | -| `permit2` | Uniswap canonical token approval manager | +| Contract | Description | +|----------|-------------| +| `admin_proxy` | Transparent proxy with owner-based access control | +| `permit2` | Uniswap canonical token approval manager (same address on all chains) | Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. From 71afcec7a636a65089b3fb9ed2b09f8e669c48c4 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 13:04:22 +0200 Subject: [PATCH 66/81] fix(ev-deployer): address clippy lints and rustfmt in init module --- bin/ev-deployer/src/init.rs | 25 +++++++++---------------- bin/ev-deployer/src/main.rs | 4 ++-- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index a45a5984..f33509a9 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -14,7 +14,7 @@ pub(crate) fn generate_template(params: &InitParams) -> String { // Header out.push_str("# EV Deployer configuration\n"); out.push_str("# See: bin/ev-deployer/README.md\n"); - out.push_str("\n"); + out.push('\n'); // Chain out.push_str("[chain]\n"); @@ -22,36 +22,33 @@ pub(crate) fn generate_template(params: &InitParams) -> String { out.push_str(&format!("chain_id = {}\n", params.chain_id)); // Contracts section header - out.push_str("\n"); + out.push('\n'); out.push_str("# ── Contracts ────────────────────────────────────────────\n"); out.push_str("# Uncomment and configure the contracts you want to deploy.\n"); out.push_str("# The `address` field is required for `genesis` mode but\n"); out.push_str("# ignored in `deploy` mode (addresses come from CREATE2).\n"); // AdminProxy - out.push_str("\n"); + out.push('\n'); + out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); + out.push_str("# The owner address is stored in slot 0.\n"); if let Some(ref owner) = params.admin_proxy_owner { - out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); - out.push_str("# The owner address is stored in slot 0.\n"); out.push_str("[contracts.admin_proxy]\n"); out.push_str("address = \"0x000000000000000000000000000000000000Ad00\"\n"); out.push_str(&format!("owner = \"{owner}\"\n")); } else { - out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); - out.push_str("# The owner address is stored in slot 0.\n"); out.push_str("# [contracts.admin_proxy]\n"); out.push_str("# address = \"0x000000000000000000000000000000000000Ad00\"\n"); out.push_str("# owner = \"0x...\"\n"); } // Permit2 - out.push_str("\n"); + out.push('\n'); + out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); if params.permit2 { - out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); out.push_str("[contracts.permit2]\n"); out.push_str("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); } else { - out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); out.push_str("# [contracts.permit2]\n"); out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); } @@ -111,9 +108,7 @@ mod tests { let params = InitParams { chain_id: 0, permit2: false, - admin_proxy_owner: Some( - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(), - ), + admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), }; let output = generate_template(¶ms); assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); @@ -129,9 +124,7 @@ mod tests { let params = InitParams { chain_id: 1234, permit2: true, - admin_proxy_owner: Some( - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string(), - ), + admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), }; let output = generate_template(¶ms); assert!(output.contains("chain_id = 1234"), "{output}"); diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 08bf7a38..b51dc62d 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -34,11 +34,11 @@ enum Command { #[arg(long)] chain_id: Option, - /// Include Permit2 with its canonical address. + /// Include `Permit2` with its canonical address. #[arg(long)] permit2: bool, - /// Include AdminProxy with the given owner address. + /// Include `AdminProxy` with the given owner address. #[arg(long)] admin_proxy_owner: Option
, }, From 3be382b0365083b3b385a6b65e2364af30111e07 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 13:57:34 +0200 Subject: [PATCH 67/81] fix(ev-deployer): resolve clippy warnings for Eq, Debug, and missing docs --- bin/ev-deployer/src/config.rs | 6 +++--- bin/ev-deployer/src/deploy/deployer.rs | 8 ++++++++ bin/ev-deployer/src/deploy/pipeline.rs | 4 ++++ bin/ev-deployer/src/init.rs | 4 ++++ bin/ev-deployer/src/lib.rs | 1 + 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index fd2f8c1a..22b45cbf 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -15,7 +15,7 @@ pub struct DeployConfig { } /// Chain-level settings. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ChainConfig { /// The chain ID. pub chain_id: u64, @@ -49,7 +49,7 @@ impl ContractsConfig { } /// `AdminProxy` configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AdminProxyConfig { /// Address to deploy at (required for genesis, ignored for deploy). pub address: Option
, @@ -58,7 +58,7 @@ pub struct AdminProxyConfig { } /// `Permit2` configuration (Uniswap token approval manager). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Permit2Config { /// Address to deploy at (required for genesis, ignored for deploy). pub address: Option
, diff --git a/bin/ev-deployer/src/deploy/deployer.rs b/bin/ev-deployer/src/deploy/deployer.rs index 51fc57c0..8a2812ab 100644 --- a/bin/ev-deployer/src/deploy/deployer.rs +++ b/bin/ev-deployer/src/deploy/deployer.rs @@ -13,7 +13,9 @@ use async_trait::async_trait; /// Receipt from a confirmed transaction. #[derive(Debug)] pub struct TxReceipt { + /// Hash of the confirmed transaction. pub tx_hash: B256, + /// Whether the transaction executed successfully. pub success: bool, } @@ -36,6 +38,12 @@ pub struct LiveDeployer { provider: Box, } +impl std::fmt::Debug for LiveDeployer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LiveDeployer").finish_non_exhaustive() + } +} + impl LiveDeployer { /// Create a new `LiveDeployer` from an RPC URL and a hex-encoded private key. pub fn new(rpc_url: &str, private_key_hex: &str) -> eyre::Result { diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index d0b965a6..6d371810 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -13,9 +13,13 @@ use alloy_primitives::{Address, B256}; use std::path::{Path, PathBuf}; /// Configuration for the deploy pipeline. +#[derive(Debug)] pub struct PipelineConfig { + /// Parsed deploy configuration. pub config: DeployConfig, + /// Path to the persistent state file. pub state_path: PathBuf, + /// Optional path to write the address manifest. pub addresses_out: Option, } diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index 03848274..a9fe0ccf 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -1,9 +1,13 @@ //! Dynamic config template generation for the `init` command. /// Parameters for generating the init template. +#[derive(Debug)] pub struct InitParams { + /// Target chain ID. pub chain_id: u64, + /// Whether to include Permit2 with its canonical address. pub permit2: bool, + /// Optional AdminProxy owner address. pub admin_proxy_owner: Option, } diff --git a/bin/ev-deployer/src/lib.rs b/bin/ev-deployer/src/lib.rs index e6d429a1..d5d04c72 100644 --- a/bin/ev-deployer/src/lib.rs +++ b/bin/ev-deployer/src/lib.rs @@ -7,5 +7,6 @@ pub mod config; pub mod contracts; pub mod deploy; pub mod genesis; +/// Dynamic config template generation for the `init` command. pub mod init; pub mod output; From 36829a9554c8eb75de66c8c566ffedd5857ec2e2 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 14:22:14 +0200 Subject: [PATCH 68/81] fix(ev-deployer): resolve remaining clippy and rustfmt warnings --- bin/ev-deployer/src/config.rs | 4 ++-- bin/ev-deployer/src/deploy/pipeline.rs | 5 +---- bin/ev-deployer/src/init.rs | 2 +- bin/ev-deployer/src/lib.rs | 1 + 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 22b45cbf..1d2f8071 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashSet, path::Path}; /// Top-level deploy configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, @@ -22,7 +22,7 @@ pub struct ChainConfig { } /// All contract configurations. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] pub struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 6d371810..e03913e4 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -24,10 +24,7 @@ pub struct PipelineConfig { } /// Run the full deploy pipeline. -pub async fn run( - pipeline_cfg: &PipelineConfig, - deployer: &dyn ChainDeployer, -) -> eyre::Result<()> { +pub async fn run(pipeline_cfg: &PipelineConfig, deployer: &dyn ChainDeployer) -> eyre::Result<()> { // ── Step 1: Init ── eprintln!("[1/4] Connecting to RPC..."); let chain_id = deployer.chain_id().await?; diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index a9fe0ccf..34a98461 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -7,7 +7,7 @@ pub struct InitParams { pub chain_id: u64, /// Whether to include Permit2 with its canonical address. pub permit2: bool, - /// Optional AdminProxy owner address. + /// Optional `AdminProxy` owner address. pub admin_proxy_owner: Option, } diff --git a/bin/ev-deployer/src/lib.rs b/bin/ev-deployer/src/lib.rs index d5d04c72..8df95412 100644 --- a/bin/ev-deployer/src/lib.rs +++ b/bin/ev-deployer/src/lib.rs @@ -5,6 +5,7 @@ pub mod config; pub mod contracts; +/// CREATE2 deploy pipeline for live chain deployment. pub mod deploy; pub mod genesis; /// Dynamic config template generation for the `init` command. From f3531506e6d2240e9d34f8124eb6579c932d1aed Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 14:25:26 +0200 Subject: [PATCH 69/81] style(ev-deployer): fix clippy warnings and missing docs --- bin/ev-deployer/src/config.rs | 10 +++++----- bin/ev-deployer/src/deploy/deployer.rs | 8 ++++++++ bin/ev-deployer/src/deploy/mod.rs | 2 ++ bin/ev-deployer/src/deploy/pipeline.rs | 4 ++++ bin/ev-deployer/src/init.rs | 21 +++++++++++---------- bin/ev-deployer/src/main.rs | 2 +- 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index fd2f8c1a..1d2f8071 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashSet, path::Path}; /// Top-level deploy configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct DeployConfig { /// Chain configuration. pub chain: ChainConfig, @@ -15,14 +15,14 @@ pub struct DeployConfig { } /// Chain-level settings. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ChainConfig { /// The chain ID. pub chain_id: u64, } /// All contract configurations. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] pub struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, @@ -49,7 +49,7 @@ impl ContractsConfig { } /// `AdminProxy` configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AdminProxyConfig { /// Address to deploy at (required for genesis, ignored for deploy). pub address: Option
, @@ -58,7 +58,7 @@ pub struct AdminProxyConfig { } /// `Permit2` configuration (Uniswap token approval manager). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Permit2Config { /// Address to deploy at (required for genesis, ignored for deploy). pub address: Option
, diff --git a/bin/ev-deployer/src/deploy/deployer.rs b/bin/ev-deployer/src/deploy/deployer.rs index 51fc57c0..8a2812ab 100644 --- a/bin/ev-deployer/src/deploy/deployer.rs +++ b/bin/ev-deployer/src/deploy/deployer.rs @@ -13,7 +13,9 @@ use async_trait::async_trait; /// Receipt from a confirmed transaction. #[derive(Debug)] pub struct TxReceipt { + /// Hash of the confirmed transaction. pub tx_hash: B256, + /// Whether the transaction executed successfully. pub success: bool, } @@ -36,6 +38,12 @@ pub struct LiveDeployer { provider: Box, } +impl std::fmt::Debug for LiveDeployer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LiveDeployer").finish_non_exhaustive() + } +} + impl LiveDeployer { /// Create a new `LiveDeployer` from an RPC URL and a hex-encoded private key. pub fn new(rpc_url: &str, private_key_hex: &str) -> eyre::Result { diff --git a/bin/ev-deployer/src/deploy/mod.rs b/bin/ev-deployer/src/deploy/mod.rs index 5fae4445..e32bf5a4 100644 --- a/bin/ev-deployer/src/deploy/mod.rs +++ b/bin/ev-deployer/src/deploy/mod.rs @@ -1,3 +1,5 @@ +//! On-chain deployment pipeline: CREATE2 addressing, state tracking, and orchestration. + pub mod create2; pub mod deployer; pub mod pipeline; diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index d0b965a6..c51fa7cb 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -13,9 +13,13 @@ use alloy_primitives::{Address, B256}; use std::path::{Path, PathBuf}; /// Configuration for the deploy pipeline. +#[derive(Debug)] pub struct PipelineConfig { + /// Parsed deploy configuration (chain + contracts). pub config: DeployConfig, + /// Path to the JSON state file for idempotent deploys. pub state_path: PathBuf, + /// Optional path to write the final address manifest. pub addresses_out: Option, } diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index 5e432c81..d50a4f83 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -1,9 +1,13 @@ //! Dynamic config template generation for the `init` command. /// Parameters for generating the init template. +#[derive(Debug)] pub struct InitParams { + /// Target chain ID (written to `[chain]` section). pub chain_id: u64, + /// Whether to enable the Permit2 contract section. pub permit2: bool, + /// If set, enables `AdminProxy` with this owner address. pub admin_proxy_owner: Option, } @@ -14,7 +18,7 @@ pub fn generate_template(params: &InitParams) -> String { // Header out.push_str("# EV Deployer configuration\n"); out.push_str("# See: bin/ev-deployer/README.md\n"); - out.push_str("\n"); + out.push('\n'); // Chain out.push_str("[chain]\n"); @@ -22,36 +26,33 @@ pub fn generate_template(params: &InitParams) -> String { out.push_str(&format!("chain_id = {}\n", params.chain_id)); // Contracts section header - out.push_str("\n"); + out.push('\n'); out.push_str("# ── Contracts ────────────────────────────────────────────\n"); out.push_str("# Uncomment and configure the contracts you want to deploy.\n"); out.push_str("# The `address` field is required for `genesis` mode but\n"); out.push_str("# ignored in `deploy` mode (addresses come from CREATE2).\n"); // AdminProxy - out.push_str("\n"); + out.push('\n'); + out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); + out.push_str("# The owner address is stored in slot 0.\n"); if let Some(ref owner) = params.admin_proxy_owner { - out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); - out.push_str("# The owner address is stored in slot 0.\n"); out.push_str("[contracts.admin_proxy]\n"); out.push_str("address = \"0x000000000000000000000000000000000000Ad00\"\n"); out.push_str(&format!("owner = \"{owner}\"\n")); } else { - out.push_str("# AdminProxy: transparent proxy with owner-based access control.\n"); - out.push_str("# The owner address is stored in slot 0.\n"); out.push_str("# [contracts.admin_proxy]\n"); out.push_str("# address = \"0x000000000000000000000000000000000000Ad00\"\n"); out.push_str("# owner = \"0x...\"\n"); } // Permit2 - out.push_str("\n"); + out.push('\n'); + out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); if params.permit2 { - out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); out.push_str("[contracts.permit2]\n"); out.push_str("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); } else { - out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); out.push_str("# [contracts.permit2]\n"); out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 8a146b43..e9194b9d 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -32,7 +32,7 @@ enum Command { #[arg(long)] permit2: bool, - /// Include AdminProxy with the given owner address. + /// Include `AdminProxy` with the given owner address. #[arg(long)] admin_proxy_owner: Option
, }, From a0d9906eb452502026d7f1bd5959111e5c95c11f Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 21:27:07 +0200 Subject: [PATCH 70/81] feat(ev-dev): include deterministic deployer in devnet genesis Add Nick's CREATE2 factory (0x4e59b44...956c) to the devnet genesis so that ev-deployer deploy works against ev-dev out of the box. On post-merge chains the canonical keyless deployment transaction cannot be replayed, so the runtime bytecode is embedded directly in the genesis alloc. Document the live deployment workflow in both ev-dev and ev-deployer READMEs. --- bin/ev-deployer/README.md | 4 +++- bin/ev-dev/README.md | 20 ++++++++++++++++++++ bin/ev-dev/assets/devnet-genesis.json | 4 ++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index f38e1864..20efcc99 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -113,10 +113,12 @@ ev-deployer deploy \ The deploy pipeline: 1. Connects to the RPC and verifies the chain ID matches the config. -2. Checks that the deterministic deployer (`0x4e59b44847b379578588920ca78fbf26c0b4956c`) exists on-chain. +2. Checks that the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (`0x4e59b44847b379578588920ca78fbf26c0b4956c`) exists on-chain. 3. Deploys each configured contract via CREATE2. 4. Verifies that the on-chain bytecode matches the expected bytecode (including patched immutables). +> **Using with ev-dev**: The deterministic deployer is pre-included in the ev-dev genesis, so `ev-deployer deploy` works against ev-dev out of the box. See the [ev-dev README](../ev-dev/README.md#live-contract-deployment-create2) for examples. + The `address` field in the config is **ignored** in deploy mode — addresses come from the CREATE2 computation. #### State file and resumability diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md index 72d20bf8..726d169e 100644 --- a/bin/ev-dev/README.md +++ b/bin/ev-dev/README.md @@ -106,6 +106,26 @@ Genesis Contracts (from path/to/deploy.toml) See the [ev-deployer README](../ev-deployer/README.md) for full config reference and available contracts. +## Live Contract Deployment (CREATE2) + +You can also deploy contracts to a running ev-dev chain using `ev-deployer deploy`. This uses the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (Nick's CREATE2 factory at `0x4e59b44847b379578588920ca78fbf26c0b4956c`), which is pre-included in the devnet genesis. + +```bash +# Terminal 1: start the chain +ev-dev + +# Terminal 2: deploy contracts via CREATE2 +ev-deployer deploy \ + --config bin/ev-deployer/examples/devnet.toml \ + --rpc-url http://127.0.0.1:8545 \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --state /tmp/deploy-state.json +``` + +In deploy mode, contract addresses are computed deterministically via CREATE2 (the `address` field in the config is ignored). The `--state` file tracks progress and allows resuming interrupted deployments. + +**Genesis vs Deploy mode**: Use `--deploy-config` (genesis mode) when you want contracts available from block 0 with exact addresses. Use `ev-deployer deploy` when you want to test the deployment pipeline itself or need CREATE2-derived addresses. + ## Chain Details | Property | Value | diff --git a/bin/ev-dev/assets/devnet-genesis.json b/bin/ev-dev/assets/devnet-genesis.json index 967352d3..1e39739a 100644 --- a/bin/ev-dev/assets/devnet-genesis.json +++ b/bin/ev-dev/assets/devnet-genesis.json @@ -107,6 +107,10 @@ "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" } }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" + }, "0x0101010101010101010101010101010101010101": { "balance": "0x1" } From 2b9485c18a2c0361517363bee44903ded19b4cec Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 22:23:30 +0200 Subject: [PATCH 71/81] feat(ev-deployer): add deterministic deployer as genesis contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inject Nick's CREATE2 factory (0x4e59b44847b379578588920ca78fbf26c0b4956c) into genesis state so ev-deployer deploy works on post-merge chains where the canonical keyless transaction cannot land. Genesis-only — the deploy pipeline already validates its existence on-chain. --- bin/ev-deployer/README.md | 12 +- bin/ev-deployer/src/config.rs | 104 ++++++++++++++++++ .../src/contracts/deterministic_deployer.rs | 40 +++++++ bin/ev-deployer/src/contracts/mod.rs | 1 + bin/ev-deployer/src/deploy/pipeline.rs | 7 ++ bin/ev-deployer/src/deploy/state.rs | 2 + bin/ev-deployer/src/genesis.rs | 29 +++++ bin/ev-deployer/src/init.rs | 57 ++++++++++ bin/ev-deployer/src/init_template.toml | 5 + bin/ev-deployer/src/main.rs | 14 +++ bin/ev-deployer/src/output.rs | 9 ++ 11 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 bin/ev-deployer/src/contracts/deterministic_deployer.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index f38e1864..03b45d99 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -48,8 +48,8 @@ Generate a starter config file. # Bare template (all contracts commented out) ev-deployer init -# Pre-populated with chain ID and Permit2 -ev-deployer init --chain-id 42170 --permit2 +# Pre-populated with chain ID, Permit2, and deterministic deployer +ev-deployer init --chain-id 42170 --permit2 --deterministic-deployer # Full config with all contracts ev-deployer init \ @@ -64,6 +64,7 @@ ev-deployer init \ | `--output ` | Write to file instead of stdout | | `--chain-id ` | Set the chain ID (defaults to 0) | | `--permit2` | Enable Permit2 with its canonical address | +| `--deterministic-deployer` | Enable the deterministic deployer (Nick's factory) with its canonical address | | `--admin-proxy-owner ` | Enable AdminProxy with the given owner | ### `genesis` @@ -158,12 +159,19 @@ ev-deployer compute-address --config deploy.toml --contract permit2 |-------|------|-------------| | `address` | address | Address to deploy at (canonical: `0x000000000022D473030F116dDEE9F6B43aC78BA3`). Required for genesis, ignored for deploy. | +### `[contracts.deterministic_deployer]` + +| Field | Type | Description | +|-------|------|-------------| +| `address` | address | Address (canonical: `0x4e59b44847b379578588920cA78FbF26c0B4956C`). Required for genesis. Genesis-only — not used in deploy mode. | + ## Contracts | Contract | Description | |----------|-------------| | `admin_proxy` | Transparent proxy with owner-based access control | | `permit2` | Uniswap canonical token approval manager (same address on all chains) | +| `deterministic_deployer` | Nick's CREATE2 factory — genesis-only, needed on post-merge chains | Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index ba02a624..8640a34f 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -28,6 +28,8 @@ pub(crate) struct ContractsConfig { pub admin_proxy: Option, /// `Permit2` contract config (optional). pub permit2: Option, + /// Deterministic deployer (Nick's factory) config (optional). + pub deterministic_deployer: Option, } impl ContractsConfig { @@ -44,6 +46,11 @@ impl ContractsConfig { addrs.push(addr); } } + if let Some(ref dd) = self.deterministic_deployer { + if let Some(addr) = dd.address { + addrs.push(addr); + } + } addrs } } @@ -64,6 +71,14 @@ pub(crate) struct Permit2Config { pub address: Option
, } +/// Deterministic deployer (Nick's factory) configuration. +/// Only used in genesis mode — in deploy mode this is the CREATE2 factory itself. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct DeterministicDeployerConfig { + /// Address (defaults to the canonical `0x4e59b44847b379578588920ca78fbf26c0b4956c`). + pub address: Option
, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -91,6 +106,15 @@ impl DeployConfig { } } + if let Some(ref dd) = self.contracts.deterministic_deployer { + if let Some(addr) = dd.address { + eyre::ensure!( + !addr.is_zero(), + "deterministic_deployer.address must not be the zero address" + ); + } + } + // Detect duplicate deploy addresses across all contracts. let mut seen = HashSet::new(); for addr in self.contracts.all_addresses() { @@ -114,6 +138,12 @@ impl DeployConfig { "permit2.address is required for genesis mode" ); } + if let Some(ref dd) = self.contracts.deterministic_deployer { + eyre::ensure!( + dd.address.is_some(), + "deterministic_deployer.address is required for genesis mode" + ); + } Ok(()) } } @@ -240,6 +270,7 @@ address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), permit2: None, + deterministic_deployer: None, }, }; config.validate().unwrap(); // base validation passes @@ -260,4 +291,77 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" config.validate().unwrap(); assert!(config.contracts.admin_proxy.is_some()); } + + #[test] + fn deterministic_deployer_only() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.deterministic_deployer] +address = "0x4e59b44847b379578588920cA78FbF26c0B4956C" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.deterministic_deployer.is_some()); + assert!(config.contracts.admin_proxy.is_none()); + assert!(config.contracts.permit2.is_none()); + } + + #[test] + fn deterministic_deployer_without_address() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.deterministic_deployer] +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.contracts.deterministic_deployer.is_some()); + assert!(config.contracts.deterministic_deployer.unwrap().address.is_none()); + } + + #[test] + fn reject_zero_deterministic_deployer_address() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.deterministic_deployer] +address = "0x0000000000000000000000000000000000000000" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + assert!(config.validate().is_err()); + } + + #[test] + fn reject_duplicate_deterministic_deployer_address() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.permit2] +address = "0x4e59b44847b379578588920cA78FbF26c0B4956C" + +[contracts.deterministic_deployer] +address = "0x4e59b44847b379578588920cA78FbF26c0B4956C" +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + let err = config.validate().unwrap_err().to_string(); + assert!(err.contains("duplicate deploy address"), "{err}"); + } + + #[test] + fn reject_missing_deterministic_deployer_address_for_genesis() { + let toml = r#" +[chain] +chain_id = 1 + +[contracts.deterministic_deployer] +"#; + let config: DeployConfig = toml::from_str(toml).unwrap(); + config.validate().unwrap(); + assert!(config.validate_for_genesis().is_err()); + } } diff --git a/bin/ev-deployer/src/contracts/deterministic_deployer.rs b/bin/ev-deployer/src/contracts/deterministic_deployer.rs new file mode 100644 index 00000000..313a27b8 --- /dev/null +++ b/bin/ev-deployer/src/contracts/deterministic_deployer.rs @@ -0,0 +1,40 @@ +//! Deterministic deployer (Nick's factory) bytecode for genesis injection. + +use crate::{config::DeterministicDeployerConfig, contracts::GenesisContract}; +use alloy_primitives::{hex, Bytes}; +use std::collections::BTreeMap; + +/// Runtime bytecode of the deterministic deployer factory from Ethereum mainnet. +/// See: +pub(crate) const DETERMINISTIC_DEPLOYER_BYTECODE: &[u8] = &hex!( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b80825250506014600cf3" +); + +/// Build a genesis alloc entry for the deterministic deployer. +pub(crate) fn build(config: &DeterministicDeployerConfig) -> GenesisContract { + let address = config.address.expect("address required for genesis"); + + GenesisContract { + address, + code: Bytes::from_static(DETERMINISTIC_DEPLOYER_BYTECODE), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + + #[test] + fn build_produces_correct_bytecode() { + let config = DeterministicDeployerConfig { + address: Some(address!("4e59b44847b379578588920ca78fbf26c0b4956c")), + }; + let contract = build(&config); + + assert_eq!(contract.address, address!("4e59b44847b379578588920ca78fbf26c0b4956c")); + assert_eq!(contract.code.as_ref(), DETERMINISTIC_DEPLOYER_BYTECODE); + assert!(contract.storage.is_empty()); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 9e61b58f..58a566de 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,6 +1,7 @@ //! Contract bytecode and storage encoding. pub(crate) mod admin_proxy; +pub(crate) mod deterministic_deployer; pub(crate) mod immutables; pub(crate) mod permit2; diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 9db2ead8..68e3f258 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -14,8 +14,14 @@ use std::path::{Path, PathBuf}; /// Configuration for the deploy pipeline. pub(crate) struct PipelineConfig { + /// Parsed deployment intent loaded from the user-provided config file. pub config: DeployConfig, + /// Path to the persisted deploy state JSON file. + /// If the file exists, the pipeline resumes from it and reuses its CREATE2 salt. + /// If it does not exist, the pipeline creates it on first run. pub state_path: PathBuf, + /// Optional path for writing a JSON address manifest after a successful deploy. + /// This output is informational only and does not affect deployment behavior. pub addresses_out: Option, } @@ -310,6 +316,7 @@ mod tests { contracts: ContractsConfig { admin_proxy: None, permit2: Some(Permit2Config { address: None }), + deterministic_deployer: None, }, } } diff --git a/bin/ev-deployer/src/deploy/state.rs b/bin/ev-deployer/src/deploy/state.rs index ea55371a..d71d8bde 100644 --- a/bin/ev-deployer/src/deploy/state.rs +++ b/bin/ev-deployer/src/deploy/state.rs @@ -139,6 +139,7 @@ mod tests { contracts: ContractsConfig { admin_proxy: None, permit2: Some(Permit2Config { address: None }), + deterministic_deployer: None, }, } } @@ -193,6 +194,7 @@ mod tests { contracts: ContractsConfig { admin_proxy: None, permit2: None, + deterministic_deployer: None, }, }; let state = DeployState::new(&config); diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 322f8ca5..2c12ea42 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -22,6 +22,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref dd_config) = config.contracts.deterministic_deployer { + let contract = contracts::deterministic_deployer::build(dd_config); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -108,6 +113,7 @@ mod tests { owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), permit2: None, + deterministic_deployer: None, }, } } @@ -207,4 +213,27 @@ mod tests { let result = merge_into(&test_config(), tmp.path(), false); assert!(result.is_err()); } + + #[test] + fn deterministic_deployer_in_alloc() { + let config = DeployConfig { + chain: ChainConfig { chain_id: 1234 }, + contracts: ContractsConfig { + admin_proxy: None, + permit2: None, + deterministic_deployer: Some(DeterministicDeployerConfig { + address: Some(address!("4e59b44847b379578588920cA78FbF26c0B4956C")), + }), + }, + }; + let alloc = build_alloc(&config); + let obj = alloc.as_object().unwrap(); + let key = "4e59b44847b379578588920ca78fbf26c0b4956c"; + assert!(obj.contains_key(key), "missing key {key} in {obj:?}"); + + let entry = obj.get(key).unwrap().as_object().unwrap(); + assert_eq!(entry["balance"], "0x0"); + assert!(entry["code"].as_str().unwrap().starts_with("0x")); + assert!(entry["storage"].as_object().unwrap().is_empty()); + } } diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index f33509a9..e7359d73 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -4,6 +4,7 @@ pub(crate) struct InitParams { pub chain_id: u64, pub permit2: bool, + pub deterministic_deployer: bool, pub admin_proxy_owner: Option, } @@ -53,6 +54,18 @@ pub(crate) fn generate_template(params: &InitParams) -> String { out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); } + // Deterministic deployer + out.push('\n'); + out.push_str("# Deterministic deployer (Nick's factory): CREATE2 factory for deploy mode.\n"); + out.push_str("# Required in genesis for post-merge chains where the keyless tx cannot land.\n"); + if params.deterministic_deployer { + out.push_str("[contracts.deterministic_deployer]\n"); + out.push_str("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + } else { + out.push_str("# [contracts.deterministic_deployer]\n"); + out.push_str("# address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + } + out } @@ -68,6 +81,7 @@ mod tests { let params = InitParams { chain_id: 0, permit2: false, + deterministic_deployer: false, admin_proxy_owner: None, }; let output = generate_template(¶ms); @@ -79,6 +93,7 @@ mod tests { let params = InitParams { chain_id: 42170, permit2: false, + deterministic_deployer: false, admin_proxy_owner: None, }; let output = generate_template(¶ms); @@ -92,6 +107,7 @@ mod tests { let params = InitParams { chain_id: 0, permit2: true, + deterministic_deployer: false, admin_proxy_owner: None, }; let output = generate_template(¶ms); @@ -108,6 +124,7 @@ mod tests { let params = InitParams { chain_id: 0, permit2: false, + deterministic_deployer: false, admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), }; let output = generate_template(¶ms); @@ -124,6 +141,7 @@ mod tests { let params = InitParams { chain_id: 1234, permit2: true, + deterministic_deployer: true, admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), }; let output = generate_template(¶ms); @@ -134,5 +152,44 @@ mod tests { output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), "{output}" ); + assert!( + output.contains("[contracts.deterministic_deployer]\n"), + "{output}" + ); + } + + #[test] + fn deterministic_deployer_enabled() { + let params = InitParams { + chain_id: 0, + permit2: false, + deterministic_deployer: true, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!( + output.contains("[contracts.deterministic_deployer]\n"), + "{output}" + ); + assert!( + output.contains("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\""), + "{output}" + ); + assert!(output.contains("# [contracts.permit2]"), "{output}"); + } + + #[test] + fn deterministic_deployer_disabled() { + let params = InitParams { + chain_id: 0, + permit2: false, + deterministic_deployer: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!( + output.contains("# [contracts.deterministic_deployer]"), + "{output}" + ); } } diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 966e5eb6..79126c70 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -19,3 +19,8 @@ chain_id = 0 # Permit2: Uniswap canonical token approval manager. # [contracts.permit2] # address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" + +# Deterministic deployer (Nick's factory): CREATE2 factory for deploy mode. +# Required in genesis for post-merge chains where the keyless tx cannot land. +# [contracts.deterministic_deployer] +# address = "0x4e59b44847b379578588920cA78FbF26c0B4956C" diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index b51dc62d..20a6cc02 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -38,6 +38,10 @@ enum Command { #[arg(long)] permit2: bool, + /// Include the deterministic deployer (Nick's factory) with its canonical address. + #[arg(long)] + deterministic_deployer: bool, + /// Include `AdminProxy` with the given owner address. #[arg(long)] admin_proxy_owner: Option
, @@ -158,11 +162,13 @@ fn main() -> eyre::Result<()> { output, chain_id, permit2, + deterministic_deployer, admin_proxy_owner, } => { let params = init::InitParams { chain_id: chain_id.unwrap_or(0), permit2, + deterministic_deployer, admin_proxy_owner: admin_proxy_owner.map(|a| format!("{a}")), }; let template = init::generate_template(¶ms); @@ -193,6 +199,14 @@ fn main() -> eyre::Result<()> { .as_ref() .and_then(|c| c.address) .ok_or_else(|| eyre::eyre!("permit2 not configured or address not set"))?, + "deterministic_deployer" => cfg + .contracts + .deterministic_deployer + .as_ref() + .and_then(|c| c.address) + .ok_or_else(|| { + eyre::eyre!("deterministic_deployer not configured or address not set") + })?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 8053180c..8e612566 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -22,5 +22,14 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { } } + if let Some(ref dd) = config.contracts.deterministic_deployer { + if let Some(addr) = dd.address { + manifest.insert( + "deterministic_deployer".to_string(), + Value::String(format!("{}", addr)), + ); + } + } + Value::Object(manifest) } From 4e00b8a236baf92e41933d5051ed7913dad4ed84 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 31 Mar 2026 22:31:09 +0200 Subject: [PATCH 72/81] fix(ev-deployer): restrict immutables visibility to pub(crate) --- bin/ev-deployer/src/contracts/immutables.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/ev-deployer/src/contracts/immutables.rs b/bin/ev-deployer/src/contracts/immutables.rs index 9eab22e3..09341047 100644 --- a/bin/ev-deployer/src/contracts/immutables.rs +++ b/bin/ev-deployer/src/contracts/immutables.rs @@ -10,7 +10,7 @@ use alloy_primitives::{B256, U256}; /// A single immutable reference inside a bytecode blob. #[derive(Debug, Clone, Copy)] -pub struct ImmutableRef { +pub(crate) struct ImmutableRef { /// Byte offset into the **runtime** bytecode. pub start: usize, /// Number of bytes (always 32 for EVM words). @@ -22,7 +22,7 @@ pub struct ImmutableRef { /// # Panics /// /// Panics if any reference extends past the end of `bytecode`. -pub fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) { +pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) { for r in refs { assert!( r.start + r.length <= bytecode.len(), @@ -36,7 +36,7 @@ pub fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) } /// Convenience: patch with an ABI-encoded `uint256`. -pub fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { +pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { let word = B256::from(val); patch_bytes(bytecode, refs, &word.0); } From 304f60aaaf018c6b407571e3b457822db150b812 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 1 Apr 2026 13:42:31 +0200 Subject: [PATCH 73/81] feat(ev-deployer): use canonical Uniswap salt for Permit2 CREATE2 deploy Deploy mode now uses the original Uniswap salt so Permit2 lands at its canonical address (0x000000000022D473030F116dDEE9F6B43aC78BA3) instead of a random one. --- bin/ev-deployer/src/contracts/permit2.rs | 8 ++++++++ bin/ev-deployer/src/deploy/create2.rs | 10 ++++++++++ bin/ev-deployer/src/deploy/pipeline.rs | 7 +++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index eabbbe70..9e66e6ba 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -73,6 +73,14 @@ pub(crate) const HASHED_NAME: B256 = B256::new(hex!( "9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a" )); +/// The CREATE2 salt used by Uniswap to deploy Permit2 at its canonical address +/// (`0x000000000022D473030F116dDEE9F6B43aC78BA3`) via Nick's factory. +/// +/// Source: +pub(crate) const PERMIT2_CANONICAL_SALT: B256 = B256::new(hex!( + "0000000000000000000000000000000000000000d3af2663da51c10215000000" +)); + /// Build the expected runtime bytecode for a Permit2 deployed at `address` on `chain_id`. /// Used by the deploy pipeline to verify on-chain bytecode matches. pub(crate) fn expected_runtime_bytecode(chain_id: u64, address: Address) -> Vec { diff --git a/bin/ev-deployer/src/deploy/create2.rs b/bin/ev-deployer/src/deploy/create2.rs index cb3b3cf6..32b6d24f 100644 --- a/bin/ev-deployer/src/deploy/create2.rs +++ b/bin/ev-deployer/src/deploy/create2.rs @@ -62,6 +62,16 @@ mod tests { assert_ne!(addr1, addr2); } + #[test] + fn permit2_canonical_salt_produces_canonical_address() { + use crate::contracts::permit2::{PERMIT2_CANONICAL_SALT, PERMIT2_INITCODE}; + let addr = compute_address(PERMIT2_CANONICAL_SALT, PERMIT2_INITCODE); + let expected: Address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" + .parse() + .unwrap(); + assert_eq!(addr, expected); + } + #[test] fn factory_calldata_format() { let salt = B256::with_last_byte(0x42); diff --git a/bin/ev-deployer/src/deploy/pipeline.rs b/bin/ev-deployer/src/deploy/pipeline.rs index 56966386..2b190f55 100644 --- a/bin/ev-deployer/src/deploy/pipeline.rs +++ b/bin/ev-deployer/src/deploy/pipeline.rs @@ -60,8 +60,6 @@ pub async fn run(pipeline_cfg: &PipelineConfig, deployer: &dyn ChainDeployer) -> state }; - let salt = state.create2_salt; - // ── Step 2: Deploy Permit2 ── if let Some(ref p2_config) = pipeline_cfg.config.contracts.permit2 { eprintln!("[3/4] Deploying Permit2..."); @@ -70,8 +68,9 @@ pub async fn run(pipeline_cfg: &PipelineConfig, deployer: &dyn ChainDeployer) -> eprintln!(" WARN: contracts.permit2.address is ignored in deploy mode"); } + let permit2_salt = contracts::permit2::PERMIT2_CANONICAL_SALT; let initcode = contracts::permit2::PERMIT2_INITCODE.to_vec(); - let address = compute_address(salt, &initcode); + let address = compute_address(permit2_salt, &initcode); let expected_runtime = contracts::permit2::expected_runtime_bytecode(chain_id, address); @@ -81,7 +80,7 @@ pub async fn run(pipeline_cfg: &PipelineConfig, deployer: &dyn ChainDeployer) -> &DeployContractParams { name: "permit2", address, - salt, + salt: permit2_salt, initcode: &initcode, expected_runtime: &expected_runtime, state_path: &pipeline_cfg.state_path, From 01815ea19aa1d3334bc6cbffadd21546d016cfb5 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 2 Apr 2026 11:04:35 +0200 Subject: [PATCH 74/81] refactor(ev-deployer): split init into genesis and deploy subcommands `ev-deployer init genesis` generates config with address fields for genesis injection. `ev-deployer init deploy` generates config without addresses (computed via CREATE2) and auto-includes the deterministic deployer since it is required for deploy mode. --- bin/ev-deployer/src/init.rs | 183 +++++++++++++++++++++---- bin/ev-deployer/src/init_template.toml | 7 +- bin/ev-deployer/src/main.rs | 113 ++++++++++----- 3 files changed, 237 insertions(+), 66 deletions(-) diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index 8ddde273..1b9f7f1d 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -1,8 +1,19 @@ //! Dynamic config template generation for the `init` command. +/// Whether the config is for genesis injection or live CREATE2 deployment. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InitMode { + /// Config for genesis injection. + Genesis, + /// Config for live CREATE2 deployment. + Deploy, +} + /// Parameters for generating the init template. #[derive(Debug)] pub struct InitParams { + /// Genesis or deploy mode. + pub mode: InitMode, /// Target chain ID (written to `[chain]` section). pub chain_id: u64, /// Whether to enable the `Permit2` contract section. @@ -17,8 +28,14 @@ pub struct InitParams { pub fn generate_template(params: &InitParams) -> String { let mut out = String::new(); + let is_genesis = params.mode == InitMode::Genesis; + + // In deploy mode, deterministic deployer is always required. + let deterministic_deployer = params.deterministic_deployer || !is_genesis; + // Header - out.push_str("# EV Deployer configuration\n"); + let mode_label = if is_genesis { "genesis" } else { "deploy" }; + out.push_str(&format!("# EV Deployer configuration ({mode_label} mode)\n")); out.push_str("# See: bin/ev-deployer/README.md\n"); out.push('\n'); @@ -30,9 +47,13 @@ pub fn generate_template(params: &InitParams) -> String { // Contracts section header out.push('\n'); out.push_str("# ── Contracts ────────────────────────────────────────────\n"); - out.push_str("# Uncomment and configure the contracts you want to deploy.\n"); - out.push_str("# The `address` field is required for `genesis` mode but\n"); - out.push_str("# ignored in `deploy` mode (addresses come from CREATE2).\n"); + if is_genesis { + out.push_str("# Uncomment and configure the contracts you want in genesis.\n"); + out.push_str("# The `address` field is required for genesis mode.\n"); + } else { + out.push_str("# Uncomment the contracts you want to deploy via CREATE2.\n"); + out.push_str("# Addresses are computed deterministically; no `address` field needed.\n"); + } // AdminProxy out.push('\n'); @@ -40,11 +61,15 @@ pub fn generate_template(params: &InitParams) -> String { out.push_str("# The owner address is stored in slot 0.\n"); if let Some(ref owner) = params.admin_proxy_owner { out.push_str("[contracts.admin_proxy]\n"); - out.push_str("address = \"0x000000000000000000000000000000000000Ad00\"\n"); + if is_genesis { + out.push_str("address = \"0x000000000000000000000000000000000000Ad00\"\n"); + } out.push_str(&format!("owner = \"{owner}\"\n")); } else { out.push_str("# [contracts.admin_proxy]\n"); - out.push_str("# address = \"0x000000000000000000000000000000000000Ad00\"\n"); + if is_genesis { + out.push_str("# address = \"0x000000000000000000000000000000000000Ad00\"\n"); + } out.push_str("# owner = \"0x...\"\n"); } @@ -53,22 +78,39 @@ pub fn generate_template(params: &InitParams) -> String { out.push_str("# Permit2: Uniswap canonical token approval manager.\n"); if params.permit2 { out.push_str("[contracts.permit2]\n"); - out.push_str("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + if is_genesis { + out.push_str("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + } } else { out.push_str("# [contracts.permit2]\n"); - out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + if is_genesis { + out.push_str("# address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\"\n"); + } } // Deterministic deployer out.push('\n'); - out.push_str("# Deterministic deployer (Nick's factory): CREATE2 factory for deploy mode.\n"); - out.push_str("# Required in genesis for post-merge chains where the keyless tx cannot land.\n"); - if params.deterministic_deployer { + if is_genesis { + out.push_str( + "# Deterministic deployer (Nick's factory): CREATE2 factory for deploy mode.\n", + ); + out.push_str( + "# Required in genesis for post-merge chains where the keyless tx cannot land.\n", + ); + } else { + out.push_str("# Deterministic deployer (Nick's factory): required for CREATE2 deploys.\n"); + out.push_str("# Automatically included — must be present on-chain before deploying.\n"); + } + if deterministic_deployer { out.push_str("[contracts.deterministic_deployer]\n"); - out.push_str("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + if is_genesis { + out.push_str("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + } } else { out.push_str("# [contracts.deterministic_deployer]\n"); - out.push_str("# address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + if is_genesis { + out.push_str("# address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); + } } out @@ -78,24 +120,25 @@ pub fn generate_template(params: &InitParams) -> String { mod tests { use super::*; - /// The static template that the old `init` used to emit. - const LEGACY_TEMPLATE: &str = include_str!("init_template.toml"); + const LEGACY_GENESIS_TEMPLATE: &str = include_str!("init_template.toml"); #[test] - fn default_params_match_legacy_template() { + fn genesis_default_matches_legacy_template() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 0, permit2: false, deterministic_deployer: false, admin_proxy_owner: None, }; let output = generate_template(¶ms); - assert_eq!(output, LEGACY_TEMPLATE); + assert_eq!(output, LEGACY_GENESIS_TEMPLATE); } #[test] - fn custom_chain_id() { + fn genesis_custom_chain_id() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 42170, permit2: false, deterministic_deployer: false, @@ -108,8 +151,9 @@ mod tests { } #[test] - fn permit2_enabled() { + fn genesis_permit2_includes_address() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 0, permit2: true, deterministic_deployer: false, @@ -121,12 +165,12 @@ mod tests { output.contains("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\""), "{output}" ); - assert!(output.contains("# [contracts.admin_proxy]"), "{output}"); } #[test] - fn admin_proxy_with_owner() { + fn genesis_admin_proxy_with_owner() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 0, permit2: false, deterministic_deployer: false, @@ -134,29 +178,30 @@ mod tests { }; let output = generate_template(¶ms); assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); + assert!( + output.contains("address = \"0x000000000000000000000000000000000000Ad00\""), + "{output}" + ); assert!( output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), "{output}" ); - assert!(output.contains("# [contracts.permit2]"), "{output}"); } #[test] - fn all_flags_combined() { + fn genesis_all_flags() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 1234, permit2: true, deterministic_deployer: true, admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), }; let output = generate_template(¶ms); + assert!(output.contains("(genesis mode)"), "{output}"); assert!(output.contains("chain_id = 1234"), "{output}"); assert!(output.contains("[contracts.permit2]\n"), "{output}"); assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); - assert!( - output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), - "{output}" - ); assert!( output.contains("[contracts.deterministic_deployer]\n"), "{output}" @@ -164,8 +209,9 @@ mod tests { } #[test] - fn deterministic_deployer_enabled() { + fn genesis_deterministic_deployer_includes_address() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 0, permit2: false, deterministic_deployer: true, @@ -180,12 +226,12 @@ mod tests { output.contains("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\""), "{output}" ); - assert!(output.contains("# [contracts.permit2]"), "{output}"); } #[test] - fn deterministic_deployer_disabled() { + fn genesis_deterministic_deployer_disabled() { let params = InitParams { + mode: InitMode::Genesis, chain_id: 0, permit2: false, deterministic_deployer: false, @@ -197,4 +243,81 @@ mod tests { "{output}" ); } + + // ── Deploy mode tests ── + + #[test] + fn deploy_header() { + let params = InitParams { + mode: InitMode::Deploy, + chain_id: 1234, + permit2: false, + deterministic_deployer: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!(output.contains("(deploy mode)"), "{output}"); + assert!( + output.contains("Addresses are computed deterministically"), + "{output}" + ); + } + + #[test] + fn deploy_permit2_no_address() { + let params = InitParams { + mode: InitMode::Deploy, + chain_id: 1234, + permit2: true, + deterministic_deployer: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!(output.contains("[contracts.permit2]\n"), "{output}"); + assert!( + !output.contains("address = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\""), + "deploy mode should not include address for permit2\n{output}" + ); + } + + #[test] + fn deploy_auto_includes_deterministic_deployer() { + let params = InitParams { + mode: InitMode::Deploy, + chain_id: 1234, + permit2: true, + deterministic_deployer: false, + admin_proxy_owner: None, + }; + let output = generate_template(¶ms); + assert!( + output.contains("[contracts.deterministic_deployer]\n"), + "deploy mode should auto-include deterministic deployer\n{output}" + ); + assert!( + !output.contains("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\""), + "deploy mode should not include address for deterministic deployer\n{output}" + ); + } + + #[test] + fn deploy_admin_proxy_no_address() { + let params = InitParams { + mode: InitMode::Deploy, + chain_id: 1234, + permit2: false, + deterministic_deployer: false, + admin_proxy_owner: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_string()), + }; + let output = generate_template(¶ms); + assert!(output.contains("[contracts.admin_proxy]\n"), "{output}"); + assert!( + !output.contains("address = \"0x000000000000000000000000000000000000Ad00\""), + "deploy mode should not include address for admin_proxy\n{output}" + ); + assert!( + output.contains("owner = \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\""), + "{output}" + ); + } } diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 79126c70..e1686d58 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -1,4 +1,4 @@ -# EV Deployer configuration +# EV Deployer configuration (genesis mode) # See: bin/ev-deployer/README.md [chain] @@ -6,9 +6,8 @@ chain_id = 0 # ── Contracts ──────────────────────────────────────────── -# Uncomment and configure the contracts you want to deploy. -# The `address` field is required for `genesis` mode but -# ignored in `deploy` mode (addresses come from CREATE2). +# Uncomment and configure the contracts you want in genesis. +# The `address` field is required for genesis mode. # AdminProxy: transparent proxy with owner-based access control. # The owner address is stored in slot 0. diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 1f0e331e..89b8ca79 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -18,27 +18,10 @@ struct Cli { #[derive(Subcommand)] enum Command { - /// Generate a starter config file with all supported contracts. + /// Generate a starter config file for genesis or deploy mode. Init { - /// Write config to this file instead of stdout. - #[arg(long)] - output: Option, - - /// Set the chain ID (defaults to 0). - #[arg(long)] - chain_id: Option, - - /// Include `Permit2` with its canonical address. - #[arg(long)] - permit2: bool, - - /// Include the deterministic deployer (Nick's factory) with its canonical address. - #[arg(long)] - deterministic_deployer: bool, - - /// Include `AdminProxy` with the given owner address. - #[arg(long)] - admin_proxy_owner: Option
, + #[command(subcommand)] + subcommand: InitSubcommand, }, /// Generate genesis alloc JSON from a deploy config. Genesis { @@ -96,6 +79,50 @@ enum Command { }, } +#[derive(Subcommand)] +enum InitSubcommand { + /// Generate config for genesis injection (contracts embedded at chain start). + Genesis { + /// Write config to this file instead of stdout. + #[arg(long)] + output: Option, + + /// Set the chain ID (defaults to 0). + #[arg(long)] + chain_id: Option, + + /// Include `Permit2` with its canonical address. + #[arg(long)] + permit2: bool, + + /// Include the deterministic deployer (Nick's factory) with its canonical address. + #[arg(long)] + deterministic_deployer: bool, + + /// Include `AdminProxy` with the given owner address. + #[arg(long)] + admin_proxy_owner: Option
, + }, + /// Generate config for live CREATE2 deployment. + Deploy { + /// Write config to this file instead of stdout. + #[arg(long)] + output: Option, + + /// Set the chain ID (defaults to 0). + #[arg(long)] + chain_id: Option, + + /// Include `Permit2`. + #[arg(long)] + permit2: bool, + + /// Include `AdminProxy` with the given owner address. + #[arg(long)] + admin_proxy_owner: Option
, + }, +} + fn main() -> eyre::Result<()> { let cli = Cli::parse(); @@ -152,19 +179,41 @@ fn main() -> eyre::Result<()> { .build()? .block_on(deploy::pipeline::run(&pipeline_cfg, &deployer))?; } - Command::Init { - output, - chain_id, - permit2, - deterministic_deployer, - admin_proxy_owner, - } => { - let params = init::InitParams { - chain_id: chain_id.unwrap_or(0), - permit2, - deterministic_deployer, - admin_proxy_owner: admin_proxy_owner.map(|a| format!("{a}")), + Command::Init { subcommand } => { + let (params, output) = match subcommand { + InitSubcommand::Genesis { + output, + chain_id, + permit2, + deterministic_deployer, + admin_proxy_owner, + } => ( + init::InitParams { + mode: init::InitMode::Genesis, + chain_id: chain_id.unwrap_or(0), + permit2, + deterministic_deployer, + admin_proxy_owner: admin_proxy_owner.map(|a| format!("{a}")), + }, + output, + ), + InitSubcommand::Deploy { + output, + chain_id, + permit2, + admin_proxy_owner, + } => ( + init::InitParams { + mode: init::InitMode::Deploy, + chain_id: chain_id.unwrap_or(0), + permit2, + deterministic_deployer: false, + admin_proxy_owner: admin_proxy_owner.map(|a| format!("{a}")), + }, + output, + ), }; + let template = init::generate_template(¶ms); if let Some(ref out_path) = output { From 9ed902fd9e6ef279033cbc0991db7dca9ab0d51b Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 2 Apr 2026 11:22:18 +0200 Subject: [PATCH 75/81] fix(ev-deployer): remove deterministic deployer from deploy init template The deterministic deployer cannot be deployed via CREATE2 (circular dependency). The deploy pipeline already verifies it exists on-chain, so including it in the deploy config template was misleading. --- bin/ev-deployer/src/init.rs | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index 1b9f7f1d..501402b6 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -30,8 +30,9 @@ pub fn generate_template(params: &InitParams) -> String { let is_genesis = params.mode == InitMode::Genesis; - // In deploy mode, deterministic deployer is always required. - let deterministic_deployer = params.deterministic_deployer || !is_genesis; + // In deploy mode, the deterministic deployer must already exist on-chain + // (verified by the pipeline) — it cannot be deployed via CREATE2 itself. + let deterministic_deployer = params.deterministic_deployer && is_genesis; // Header let mode_label = if is_genesis { "genesis" } else { "deploy" }; @@ -88,27 +89,21 @@ pub fn generate_template(params: &InitParams) -> String { } } - // Deterministic deployer - out.push('\n'); + // Deterministic deployer (only relevant for genesis mode — in deploy mode + // the pipeline verifies it exists on-chain, it cannot be deployed via CREATE2). if is_genesis { + out.push('\n'); out.push_str( "# Deterministic deployer (Nick's factory): CREATE2 factory for deploy mode.\n", ); out.push_str( "# Required in genesis for post-merge chains where the keyless tx cannot land.\n", ); - } else { - out.push_str("# Deterministic deployer (Nick's factory): required for CREATE2 deploys.\n"); - out.push_str("# Automatically included — must be present on-chain before deploying.\n"); - } - if deterministic_deployer { - out.push_str("[contracts.deterministic_deployer]\n"); - if is_genesis { + if deterministic_deployer { + out.push_str("[contracts.deterministic_deployer]\n"); out.push_str("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); - } - } else { - out.push_str("# [contracts.deterministic_deployer]\n"); - if is_genesis { + } else { + out.push_str("# [contracts.deterministic_deployer]\n"); out.push_str("# address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\"\n"); } } @@ -281,7 +276,7 @@ mod tests { } #[test] - fn deploy_auto_includes_deterministic_deployer() { + fn deploy_excludes_deterministic_deployer() { let params = InitParams { mode: InitMode::Deploy, chain_id: 1234, @@ -291,12 +286,8 @@ mod tests { }; let output = generate_template(¶ms); assert!( - output.contains("[contracts.deterministic_deployer]\n"), - "deploy mode should auto-include deterministic deployer\n{output}" - ); - assert!( - !output.contains("address = \"0x4e59b44847b379578588920cA78FbF26c0B4956C\""), - "deploy mode should not include address for deterministic deployer\n{output}" + !output.contains("deterministic_deployer"), + "deploy mode should not include deterministic deployer section\n{output}" ); } From 160fd224921783b85d57b71909a87404a1e87629 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 2 Apr 2026 11:24:18 +0200 Subject: [PATCH 76/81] docs: update ev-deployer and ev-dev READMEs for init subcommands Reflect the init genesis/deploy split, canonical Permit2 CREATE2 salt, and removal of deterministic deployer from deploy config. --- bin/ev-deployer/README.md | 57 +++++++++++++++++++++++++++------------ bin/ev-dev/README.md | 19 +++++++------ 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 4318da4e..3d457509 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -16,13 +16,12 @@ Both modes read the same TOML config. The `address` field in each contract secti ## Quick Start ```bash -# 1. Generate a config pre-populated for your chain -ev-deployer init --chain-id 42170 --permit2 --output deploy.toml +# Genesis: embed contracts into the chain's genesis state +ev-deployer init genesis --chain-id 42170 --permit2 --deterministic-deployer --output genesis.toml +ev-deployer genesis --config genesis.toml --merge-into genesis.json --output genesis-out.json -# 2a. Genesis mode: embed into genesis state -ev-deployer genesis --config deploy.toml --merge-into genesis.json --output genesis-out.json - -# 2b. Deploy mode: deploy to a live chain +# Deploy: deploy contracts to a running chain via CREATE2 +ev-deployer init deploy --chain-id 42170 --permit2 --output deploy.toml ev-deployer deploy \ --config deploy.toml \ --rpc-url http://localhost:8545 \ @@ -40,23 +39,24 @@ The binary is output to `target/release/ev-deployer`. ## Commands -### `init` +### `init genesis` -Generate a starter config file. +Generate a starter config for **genesis mode** (contracts embedded at chain start). Includes `address` fields for each contract. ```bash # Bare template (all contracts commented out) -ev-deployer init +ev-deployer init genesis -# Pre-populated with chain ID, Permit2, and deterministic deployer -ev-deployer init --chain-id 42170 --permit2 --deterministic-deployer +# Pre-populated with Permit2 and deterministic deployer +ev-deployer init genesis --chain-id 42170 --permit2 --deterministic-deployer # Full config with all contracts -ev-deployer init \ +ev-deployer init genesis \ --chain-id 42170 \ --permit2 \ + --deterministic-deployer \ --admin-proxy-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ - --output deploy.toml + --output genesis.toml ``` | Flag | Description | @@ -64,7 +64,30 @@ ev-deployer init \ | `--output ` | Write to file instead of stdout | | `--chain-id ` | Set the chain ID (defaults to 0) | | `--permit2` | Enable Permit2 with its canonical address | -| `--deterministic-deployer` | Enable the deterministic deployer (Nick's factory) with its canonical address | +| `--deterministic-deployer` | Enable the deterministic deployer (Nick's factory) | +| `--admin-proxy-owner ` | Enable AdminProxy with the given owner | + +### `init deploy` + +Generate a starter config for **deploy mode** (contracts deployed via CREATE2 to a running chain). No `address` fields — addresses are computed deterministically. The deterministic deployer is not included in the config since it cannot be deployed via CREATE2 (it must already exist on-chain). + +```bash +# Config with Permit2 +ev-deployer init deploy --chain-id 42170 --permit2 + +# Full config +ev-deployer init deploy \ + --chain-id 42170 \ + --permit2 \ + --admin-proxy-owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ + --output deploy.toml +``` + +| Flag | Description | +|------|-------------| +| `--output ` | Write to file instead of stdout | +| `--chain-id ` | Set the chain ID (defaults to 0) | +| `--permit2` | Enable Permit2 | | `--admin-proxy-owner ` | Enable AdminProxy with the given owner | ### `genesis` @@ -118,13 +141,13 @@ The deploy pipeline: 3. Deploys each configured contract via CREATE2. 4. Verifies that the on-chain bytecode matches the expected bytecode (including patched immutables). -> **Using with ev-dev**: The deterministic deployer is pre-included in the ev-dev genesis, so `ev-deployer deploy` works against ev-dev out of the box. See the [ev-dev README](../ev-dev/README.md#live-contract-deployment-create2) for examples. +Permit2 is deployed using the [canonical Uniswap salt](https://github.com/Uniswap/permit2/blob/main/script/DeployPermit2.s.sol), so it lands at its well-known address `0x000000000022D473030F116dDEE9F6B43aC78BA3` on any chain. -The `address` field in the config is **ignored** in deploy mode — addresses come from the CREATE2 computation. +> **Using with ev-dev**: The deterministic deployer can be included in the ev-dev genesis via `ev-deployer init genesis --deterministic-deployer`, so `ev-deployer deploy` works against ev-dev. See the [ev-dev README](../ev-dev/README.md#live-contract-deployment-create2) for examples. #### State file and resumability -The `--state` file tracks deployment progress. On first run it generates a random CREATE2 salt and records which contracts have been deployed. If the process is interrupted, re-running with the same state file resumes where it left off. +The `--state` file tracks deployment progress and records which contracts have been deployed. If the process is interrupted, re-running with the same state file resumes where it left off. Contracts with well-known salts (e.g. Permit2) use their canonical salt; others use a random salt generated on first run. Immutability rules protect against accidental misconfiguration on resume: diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md index 726d169e..28ec39fd 100644 --- a/bin/ev-dev/README.md +++ b/bin/ev-dev/README.md @@ -76,7 +76,8 @@ ev-dev --host 0.0.0.0 ev-dev --port 9545 --block-time 2 # Start with genesis contracts deployed -ev-dev --deploy-config bin/ev-deployer/examples/devnet.toml +ev-deployer init genesis --permit2 --deterministic-deployer --chain-id 1234 --output genesis.toml +ev-dev --deploy-config genesis.toml ``` ## Genesis Contract Deployment @@ -108,23 +109,25 @@ See the [ev-deployer README](../ev-deployer/README.md) for full config reference ## Live Contract Deployment (CREATE2) -You can also deploy contracts to a running ev-dev chain using `ev-deployer deploy`. This uses the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (Nick's CREATE2 factory at `0x4e59b44847b379578588920ca78fbf26c0b4956c`), which is pre-included in the devnet genesis. +You can also deploy contracts to a running ev-dev chain using `ev-deployer deploy`. This uses the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (Nick's CREATE2 factory at `0x4e59b44847b379578588920ca78fbf26c0b4956c`), which must be included in the genesis via `--deploy-config`. ```bash -# Terminal 1: start the chain -ev-dev +# Terminal 1: start the chain with Nick's factory in genesis +ev-deployer init genesis --deterministic-deployer --chain-id 1234 --output genesis.toml +ev-dev --deploy-config genesis.toml -# Terminal 2: deploy contracts via CREATE2 +# Terminal 2: generate a deploy config and deploy Permit2 via CREATE2 +ev-deployer init deploy --permit2 --chain-id 1234 --output deploy.toml ev-deployer deploy \ - --config bin/ev-deployer/examples/devnet.toml \ + --config deploy.toml \ --rpc-url http://127.0.0.1:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --state /tmp/deploy-state.json ``` -In deploy mode, contract addresses are computed deterministically via CREATE2 (the `address` field in the config is ignored). The `--state` file tracks progress and allows resuming interrupted deployments. +Permit2 deploys to its canonical address (`0x000000000022D473030F116dDEE9F6B43aC78BA3`) using the original Uniswap CREATE2 salt. The `--state` file tracks progress and allows resuming interrupted deployments. -**Genesis vs Deploy mode**: Use `--deploy-config` (genesis mode) when you want contracts available from block 0 with exact addresses. Use `ev-deployer deploy` when you want to test the deployment pipeline itself or need CREATE2-derived addresses. +**Genesis vs Deploy mode**: Use `--deploy-config` (genesis mode) when you want contracts available from block 0 with exact addresses. Use `ev-deployer deploy` when you want to simulate a real deployment pipeline — contracts land at their canonical CREATE2 addresses. ## Chain Details From d2624bf34020ffcfc81d002f5dd90d67ebab724b Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 2 Apr 2026 15:31:57 +0200 Subject: [PATCH 77/81] refactor(ev-dev): rename --deploy-config to --genesis-config The flag passes a config for embedding contracts into genesis, not for deploying to a live chain. The old name was misleading after the init genesis/deploy split. --- bin/ev-dev/README.md | 20 ++++++++++---------- bin/ev-dev/src/main.rs | 18 +++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md index 28ec39fd..698a32fa 100644 --- a/bin/ev-dev/README.md +++ b/bin/ev-dev/README.md @@ -37,7 +37,7 @@ ev-dev [OPTIONS] | `--block-time` | `1` | Block time in seconds (`0` = mine on transaction) | | `--silent` | `false` | Suppress the startup banner | | `--accounts` | `10` | Number of accounts to display (1-20) | -| `--deploy-config` | — | Path to an ev-deployer TOML config to deploy contracts at genesis | +| `--genesis-config` | — | Path to an ev-deployer TOML config to deploy contracts at genesis | | `--tui` | `false` | Launch with an interactive terminal UI instead of plain log output | ### TUI Mode @@ -52,7 +52,7 @@ The TUI shows: - **Chain info** — chain ID, RPC URL, block time - **Accounts** — addresses, private keys, and real-time balances (polled every 2s) -- **Deployed contracts** — when using `--deploy-config` +- **Deployed contracts** — when using `--genesis-config` - **Logs** — live node logs with scrollback Keyboard shortcuts: @@ -77,18 +77,18 @@ ev-dev --port 9545 --block-time 2 # Start with genesis contracts deployed ev-deployer init genesis --permit2 --deterministic-deployer --chain-id 1234 --output genesis.toml -ev-dev --deploy-config genesis.toml +ev-dev --genesis-config genesis.toml ``` ## Genesis Contract Deployment -You can deploy contracts into the genesis state by passing a `--deploy-config` flag pointing to an [ev-deployer](../ev-deployer/README.md) TOML config file. +You can deploy contracts into the genesis state by passing a `--genesis-config` flag pointing to an [ev-deployer](../ev-deployer/README.md) TOML config file. ```bash -ev-dev --deploy-config path/to/deploy.toml +ev-dev --genesis-config path/to/deploy.toml ``` -When a deploy config is provided, ev-dev will: +When a genesis config is provided, ev-dev will: 1. Load and validate the config 2. Override the config's `chain_id` to match the devnet genesis (a warning is printed if they differ) @@ -109,12 +109,12 @@ See the [ev-deployer README](../ev-deployer/README.md) for full config reference ## Live Contract Deployment (CREATE2) -You can also deploy contracts to a running ev-dev chain using `ev-deployer deploy`. This uses the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (Nick's CREATE2 factory at `0x4e59b44847b379578588920ca78fbf26c0b4956c`), which must be included in the genesis via `--deploy-config`. +You can also deploy contracts to a running ev-dev chain using `ev-deployer deploy`. This uses the [deterministic deployer](https://github.com/Arachnid/deterministic-deployment-proxy) (Nick's CREATE2 factory at `0x4e59b44847b379578588920ca78fbf26c0b4956c`), which must be included in the genesis via `--genesis-config`. ```bash # Terminal 1: start the chain with Nick's factory in genesis ev-deployer init genesis --deterministic-deployer --chain-id 1234 --output genesis.toml -ev-dev --deploy-config genesis.toml +ev-dev --genesis-config genesis.toml # Terminal 2: generate a deploy config and deploy Permit2 via CREATE2 ev-deployer init deploy --permit2 --chain-id 1234 --output deploy.toml @@ -127,7 +127,7 @@ ev-deployer deploy \ Permit2 deploys to its canonical address (`0x000000000022D473030F116dDEE9F6B43aC78BA3`) using the original Uniswap CREATE2 salt. The `--state` file tracks progress and allows resuming interrupted deployments. -**Genesis vs Deploy mode**: Use `--deploy-config` (genesis mode) when you want contracts available from block 0 with exact addresses. Use `ev-deployer deploy` when you want to simulate a real deployment pipeline — contracts land at their canonical CREATE2 addresses. +**Genesis vs Deploy mode**: Use `--genesis-config` (genesis mode) when you want contracts available from block 0 with exact addresses. Use `ev-deployer deploy` when you want to simulate a real deployment pipeline — contracts land at their canonical CREATE2 addresses. ## Chain Details @@ -291,7 +291,7 @@ ev-dev includes all Evolve customizations out of the box: ev-dev is a thin wrapper around the full `ev-reth` node. On startup it: -1. If `--deploy-config` is provided, loads the config and merges contract alloc entries into the genesis +1. If `--genesis-config` is provided, loads the config and merges contract alloc entries into the genesis 2. Writes the (possibly extended) devnet genesis to a temp file 3. Creates a temporary data directory (clean state every run) 4. Launches `ev-reth` in `--dev` mode with networking disabled diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 4c8b660c..715b1c53 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -60,9 +60,9 @@ struct EvDevArgs { #[arg(long, default_value_t = 10, value_parser = parse_accounts)] accounts: usize, - /// Path to an ev-deployer TOML config to deploy contracts at genesis. + /// Path to an ev-deployer TOML config to embed contracts in genesis. #[arg(long, value_name = "PATH")] - deploy_config: Option, + genesis_config: Option, /// Launch with terminal UI instead of plain log output #[arg(long, default_value_t = false)] @@ -138,7 +138,7 @@ fn print_banner(args: &EvDevArgs, deploy_cfg: Option<&DeployConfig>) { println!(); if let Some(cfg) = deploy_cfg { - let config_path = args.deploy_config.as_ref().unwrap(); + let config_path = args.genesis_config.as_ref().unwrap(); println!("Genesis Contracts (from {})", config_path.display()); println!("=================="); let manifest = build_manifest(cfg); @@ -204,7 +204,7 @@ fn prepare_genesis( let genesis_json = if let Some(ref cfg) = deploy_cfg { let mut genesis: serde_json::Value = serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); - merge_alloc(cfg, &mut genesis, true).expect("failed to merge deploy config into genesis"); + merge_alloc(cfg, &mut genesis, true).expect("failed to merge genesis config into genesis"); serde_json::to_string(&genesis).expect("failed to serialize merged genesis") } else { DEVNET_GENESIS.to_string() @@ -221,15 +221,15 @@ fn prepare_genesis( (genesis_file, datadir) } -fn load_deploy_config(dev_args: &EvDevArgs) -> Option { - dev_args.deploy_config.as_ref().map(|config_path| { +fn load_genesis_config(dev_args: &EvDevArgs) -> Option { + dev_args.genesis_config.as_ref().map(|config_path| { let mut cfg = DeployConfig::load(config_path) - .unwrap_or_else(|e| panic!("failed to load deploy config: {e}")); + .unwrap_or_else(|e| panic!("failed to load genesis config: {e}")); let genesis_chain_id = chain_id_from_genesis(); if cfg.chain.chain_id != genesis_chain_id { eprintln!( - "WARNING: deploy config chain_id ({}) differs from devnet genesis ({}), overriding to {}", + "WARNING: genesis config chain_id ({}) differs from devnet genesis ({}), overriding to {}", cfg.chain.chain_id, genesis_chain_id, genesis_chain_id ); cfg.chain.chain_id = genesis_chain_id; @@ -260,7 +260,7 @@ fn main() { } let dev_args = EvDevArgs::parse(); - let deploy_cfg = load_deploy_config(&dev_args); + let deploy_cfg = load_genesis_config(&dev_args); if dev_args.tui { run_with_tui(dev_args, deploy_cfg); From 0c324a2b4e804e4cad89822bee29673ded48fbab Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 2 Apr 2026 16:06:31 +0200 Subject: [PATCH 78/81] style(ev-deployer): apply rustfmt to deterministic deployer --- bin/ev-deployer/src/config.rs | 7 ++++++- bin/ev-deployer/src/contracts/deterministic_deployer.rs | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 8640a34f..acdc4890 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -319,7 +319,12 @@ chain_id = 1 let config: DeployConfig = toml::from_str(toml).unwrap(); config.validate().unwrap(); assert!(config.contracts.deterministic_deployer.is_some()); - assert!(config.contracts.deterministic_deployer.unwrap().address.is_none()); + assert!(config + .contracts + .deterministic_deployer + .unwrap() + .address + .is_none()); } #[test] diff --git a/bin/ev-deployer/src/contracts/deterministic_deployer.rs b/bin/ev-deployer/src/contracts/deterministic_deployer.rs index 313a27b8..8644580a 100644 --- a/bin/ev-deployer/src/contracts/deterministic_deployer.rs +++ b/bin/ev-deployer/src/contracts/deterministic_deployer.rs @@ -33,7 +33,10 @@ mod tests { }; let contract = build(&config); - assert_eq!(contract.address, address!("4e59b44847b379578588920ca78fbf26c0b4956c")); + assert_eq!( + contract.address, + address!("4e59b44847b379578588920ca78fbf26c0b4956c") + ); assert_eq!(contract.code.as_ref(), DETERMINISTIC_DEPLOYER_BYTECODE); assert!(contract.storage.is_empty()); } From 1adf1b446d33f0f03da0628c02bd6b875aa5c11b Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 7 Apr 2026 15:20:45 +0200 Subject: [PATCH 79/81] style(ev-deployer): apply rustfmt to init.rs --- bin/ev-deployer/src/init.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/ev-deployer/src/init.rs b/bin/ev-deployer/src/init.rs index 501402b6..52634c66 100644 --- a/bin/ev-deployer/src/init.rs +++ b/bin/ev-deployer/src/init.rs @@ -36,7 +36,9 @@ pub fn generate_template(params: &InitParams) -> String { // Header let mode_label = if is_genesis { "genesis" } else { "deploy" }; - out.push_str(&format!("# EV Deployer configuration ({mode_label} mode)\n")); + out.push_str(&format!( + "# EV Deployer configuration ({mode_label} mode)\n" + )); out.push_str("# See: bin/ev-deployer/README.md\n"); out.push('\n'); From 17bd295ca198f6b091c7cd5872773f55c31255ff Mon Sep 17 00:00:00 2001 From: Randy Grok <98407738+randygrok@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:53:45 +0200 Subject: [PATCH 80/81] docs: clarify callFee is zero-fee safe in FeeVault doc --- docs/contracts/fee_vault.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts/fee_vault.md b/docs/contracts/fee_vault.md index d701ed3d..3d21f469 100644 --- a/docs/contracts/fee_vault.md +++ b/docs/contracts/fee_vault.md @@ -37,7 +37,7 @@ This contract serves as a **fee sink** and **distribution mechanism** for a roll - **Automatic Splitting**: Funds are split automatically upon calling `distribute`. No manual withdrawal is required for the secondary recipient. - **Minimum Threshold**: A `minimumAmount` ensures that distribution only occurs when it is economically viable. -- **Caller Incentive/Fee**: A `callFee` is required to trigger the distribution function. +- **Caller Incentive/Fee**: A caller may need to pay `callFee` to trigger the distribution function (no payment is needed when `callFee` is `0`). ## Workflow From 35cee5f9f74f87ab37c1abc91d4ba8070c98c0f3 Mon Sep 17 00:00:00 2001 From: Randy Grok <98407738+randygrok@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:07:29 +0200 Subject: [PATCH 81/81] fix(ev-deployer): make deterministic_deployer::build a const fn --- bin/ev-deployer/src/contracts/deterministic_deployer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ev-deployer/src/contracts/deterministic_deployer.rs b/bin/ev-deployer/src/contracts/deterministic_deployer.rs index 8644580a..0432ac4f 100644 --- a/bin/ev-deployer/src/contracts/deterministic_deployer.rs +++ b/bin/ev-deployer/src/contracts/deterministic_deployer.rs @@ -11,7 +11,7 @@ pub(crate) const DETERMINISTIC_DEPLOYER_BYTECODE: &[u8] = &hex!( ); /// Build a genesis alloc entry for the deterministic deployer. -pub(crate) fn build(config: &DeterministicDeployerConfig) -> GenesisContract { +pub(crate) const fn build(config: &DeterministicDeployerConfig) -> GenesisContract { let address = config.address.expect("address required for genesis"); GenesisContract {