From 89eccaa4f64a11797e376d53c46a4bbfa5022cef Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 14:30:39 +0100 Subject: [PATCH 01/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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/27] 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 4da01ea7c5f08ef8af9493ed1a1e9a70ea0d391e Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:37:14 +0100 Subject: [PATCH 13/27] 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 14/27] 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 15/27] 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 e8a39f8d90df936c6d6414d5872863db36f76779 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 24 Mar 2026 13:03:15 +0100 Subject: [PATCH 16/27] 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 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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 9729cc06dd15131b2f2839910c9674ffdeb5e784 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 06:49:11 +0100 Subject: [PATCH 27/27] fix(ev-deployer): use case-insensitive grep in e2e genesis address checks --- bin/ev-deployer/tests/e2e_genesis.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 8aee0796..9b34f249 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -77,11 +77,11 @@ 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" pass "genesis contains all three contract addresses"