From 89eccaa4f64a11797e376d53c46a4bbfa5022cef Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 13 Mar 2026 14:30:39 +0100 Subject: [PATCH 01/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] fix(ev-deployer): escape brackets in doc comments to fix rustdoc Rustdoc interprets [644], [578], and [32] as intra-doc links, causing the docs CI job to fail with -D warnings. --- bin/ev-deployer/src/contracts/merkle_tree_hook.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs index ea52cfbd..0723adab 100644 --- a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs +++ b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs @@ -8,9 +8,9 @@ //! //! | Variable | Type | Offsets | //! |---------------|---------|---------------------| -//! | `mailbox` | address | [904, 3300] | -//! | `localDomain` | uint32 | [644] | -//! | `deployedBlock`| uint256| [578] | +//! | `mailbox` | address | \[904, 3300\] | +//! | `localDomain` | uint32 | \[644\] | +//! | `deployedBlock`| uint256| \[578\] | //! //! ## Storage layout (from `forge inspect MerkleTreeHook storageLayout`) //! @@ -23,7 +23,7 @@ //! | 101 | `hook` | address | //! | 102 | `_interchainSecurityModule` | address | //! | 103-150| `__GAP` (MailboxClient) | — | -//! | 151-182| `_tree.branch[0..31]` | bytes32[32] | +//! | 151-182| `_tree.branch\[0..31\]` | bytes32\[32\] | //! | 183 | `_tree.count` | uint256 | use crate::{ From 67151c3a306a6642ec1a7456a6de622e9273664d Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:43:29 +0100 Subject: [PATCH 13/35] feat(ev-deployer): add Permit2 contract support Add Uniswap Permit2 as a genesis-deployable contract with EIP-712 immutable patching (_CACHED_CHAIN_ID, _CACHED_DOMAIN_SEPARATOR). --- .gitmodules | 3 + bin/ev-deployer/examples/devnet.toml | 3 + bin/ev-deployer/src/config.rs | 9 + bin/ev-deployer/src/contracts/mod.rs | 1 + bin/ev-deployer/src/contracts/permit2.rs | 223 +++++++++++++++++++++++ bin/ev-deployer/src/genesis.rs | 6 + bin/ev-deployer/src/main.rs | 6 + bin/ev-deployer/src/output.rs | 7 + bin/ev-deployer/tests/e2e_genesis.sh | 24 ++- contracts/lib/permit2 | 1 + 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 bin/ev-deployer/src/contracts/permit2.rs create mode 160000 contracts/lib/permit2 diff --git a/.gitmodules b/.gitmodules index 735b8dc8..0cebd2a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "contracts/lib/hyperlane-monorepo"] path = contracts/lib/hyperlane-monorepo url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git +[submodule "contracts/lib/permit2"] + path = contracts/lib/permit2 + url = https://github.com/Uniswap/permit2 diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 87bafe09..0cf8d453 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -20,3 +20,6 @@ hyp_native_minter = "0x0000000000000000000000000000000000000000" address = "0x0000000000000000000000000000000000001100" owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" + +[contracts.permit2] +address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bed1e625..c3f674b1 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -31,6 +31,8 @@ pub(crate) struct ContractsConfig { pub fee_vault: Option, /// `MerkleTreeHook` contract config (optional). pub merkle_tree_hook: Option, + /// `Permit2` contract config (optional). + pub permit2: Option, } /// `AdminProxy` configuration. @@ -84,6 +86,13 @@ pub(crate) struct MerkleTreeHookConfig { pub mailbox: Address, } +/// `Permit2` configuration (Uniswap token approval manager). +#[derive(Debug, Deserialize)] +pub(crate) struct Permit2Config { + /// Address to deploy at. + pub address: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c142cc0c..43bfaf5f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; pub(crate) mod immutables; pub(crate) mod merkle_tree_hook; +pub(crate) mod permit2; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs new file mode 100644 index 00000000..fd62f2ea --- /dev/null +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -0,0 +1,223 @@ +//! `Permit2` bytecode and immutable encoding. +//! +//! Uniswap's `Permit2` provides gas-efficient token approval management +//! via signature-based permits (EIP-2612 style) for any ERC-20 token. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offset | +//! |-----------------------------|---------|--------| +//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! +//! Both come from the EIP-712 base contract (`src/EIP712.sol`). The +//! constructor normally caches `block.chainid` and the resulting domain +//! separator so that `DOMAIN_SEPARATOR()` can skip recomputation when the +//! chain ID hasn't changed. For a genesis deploy we patch the correct +//! values directly into the bytecode. +//! +//! ## Storage layout +//! +//! Permit2 has no storage that needs initialization at genesis. All state +//! (allowances, nonces, …) starts at zero. + +use crate::{ + config::Permit2Config, + contracts::{ + immutables::{patch_bytes, patch_u256, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) +/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode +/// ``` +const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); + +// ── Immutable reference offsets (from compiled artifact `immutableReferences`) ── + +/// `_CACHED_CHAIN_ID` (uint256) — from `EIP712.sol`. +const CACHED_CHAIN_ID_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6945, + length: 32, +}]; + +/// `_CACHED_DOMAIN_SEPARATOR` (bytes32) — from `EIP712.sol`. +const CACHED_DOMAIN_SEPARATOR_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6983, + length: 32, +}]; + +/// EIP-712 type hash: `keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)")` +const EIP712_TYPE_HASH: B256 = B256::new(hex!( + "8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866" +)); + +/// `keccak256("Permit2")` +const HASHED_NAME: B256 = B256::new(hex!( + "9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a" +)); + +/// Build a genesis alloc entry for `Permit2`. +pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { + let mut bytecode = PERMIT2_BYTECODE.to_vec(); + + // Patch _CACHED_CHAIN_ID + let chain_id_u256 = U256::from(chain_id); + patch_u256(&mut bytecode, CACHED_CHAIN_ID_REFS, chain_id_u256); + + // Compute and patch _CACHED_DOMAIN_SEPARATOR: + // keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, chainId, contractAddress)) + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let domain_separator = keccak256(buf); + patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> Permit2Config { + Permit2Config { + address: address!("000000000022D473030F116dDEE9F6B43aC78BA3"), + } + } + + #[test] + fn no_storage_entries() { + let contract = build(&test_config(), 1234); + assert!( + contract.storage.is_empty(), + "Permit2 should have no storage at genesis" + ); + } + + #[test] + fn bytecode_is_patched_with_chain_id() { + let contract = build(&test_config(), 42); + let code = contract.code.to_vec(); + let word = &code[6945..6945 + 32]; + assert_eq!(word[31], 42); + assert_eq!(word[0..31], [0u8; 31]); + } + + #[test] + fn bytecode_is_patched_with_domain_separator() { + let config = test_config(); + let chain_id: u64 = 1234; + let contract = build(&config, chain_id); + let code = contract.code.to_vec(); + + // Compute expected domain separator + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(U256::from(chain_id)).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let expected = keccak256(buf); + + let word = &code[6983..6983 + 32]; + assert_eq!(word, expected.as_slice()); + } + + #[test] + fn domain_separator_changes_with_chain_id() { + let config = test_config(); + let c1 = build(&config, 1); + let c2 = build(&config, 2); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + } + + #[test] + fn domain_separator_changes_with_address() { + let c1 = build( + &Permit2Config { + address: Address::repeat_byte(0x01), + }, + 1234, + ); + let c2 = build( + &Permit2Config { + address: Address::repeat_byte(0x02), + }, + 1234, + ); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + } + + #[test] + fn eip712_constants_are_correct() { + // Verify the hardcoded constants match the expected values + assert_eq!( + EIP712_TYPE_HASH, + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), + ); + assert_eq!(HASHED_NAME, keccak256("Permit2")); + } + + #[test] + #[ignore = "requires forge CLI"] + fn permit2_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("permit2"); + + let output = Command::new("forge") + .args(["inspect", "Permit2", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(PERMIT2_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "Permit2 bytecode mismatch! Regenerate with: \ + cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fee43554..ff5b4ec4 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -28,6 +28,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref p2_config) = config.contracts.permit2 { + let contract = contracts::permit2::build(p2_config, config.chain.chain_id); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -105,6 +110,7 @@ mod tests { }), fee_vault: None, merkle_tree_hook: None, + permit2: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5c9d30e..aab6e6e4 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -115,6 +115,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, + "permit2" => cfg + .contracts + .permit2 + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 59ae29af..10e41672 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -28,5 +28,12 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref p2) = config.contracts.permit2 { + manifest.insert( + "permit2".to_string(), + Value::String(format!("{}", p2.address)), + ); + } + Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 79900e52..2d47481e 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -81,8 +81,10 @@ grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" +grep -q "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ + || fail "Permit2 address not found in genesis" -pass "genesis contains both contract addresses" +pass "genesis contains all contract addresses" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -162,6 +164,26 @@ expected_slot6="0x00000000000000000000000000000000000000000000000000000000000027 || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" pass "FeeVault slot 6 (bridgeShareBps) = 10000" +# ── Step 6: Verify Permit2 ───────────────────────────── + +PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3" + +echo "=== Verifying Permit2 at $PERMIT2 ===" + +# Check code is present +p2_code=$(rpc_call "eth_getCode" "[\"$PERMIT2\", \"latest\"]") +[[ "$p2_code" != "0x" && "$p2_code" != "0x0" && ${#p2_code} -gt 10 ]] \ + || fail "Permit2 has no bytecode (got: $p2_code)" +pass "Permit2 has bytecode (${#p2_code} hex chars)" + +# Call DOMAIN_SEPARATOR() — selector 0x3644e515 +# Should return the cached domain separator matching chain_id=1234 and the contract address +p2_domain_sep=$(rpc_call "eth_call" "[{\"to\":\"$PERMIT2\",\"data\":\"0x3644e515\"}, \"latest\"]") +expected_domain_sep="0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95" +[[ "$(echo "$p2_domain_sep" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_domain_sep" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "Permit2 DOMAIN_SEPARATOR() mismatch: got $p2_domain_sep, expected $expected_domain_sep" +pass "Permit2 DOMAIN_SEPARATOR() correct for chain_id=1234" + # ── Done ───────────────────────────────────────────────── echo "" diff --git a/contracts/lib/permit2 b/contracts/lib/permit2 new file mode 160000 index 00000000..cc56ad0f --- /dev/null +++ b/contracts/lib/permit2 @@ -0,0 +1 @@ +Subproject commit cc56ad0f3439c502c246fc5cfcc3db92bb8b7219 From 56548ec8819f5ba33ec716dd75410f109c0aa407 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:44:28 +0100 Subject: [PATCH 14/35] docs(ev-deployer): add comment explaining canonical Permit2 address --- bin/ev-deployer/examples/devnet.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 0cf8d453..1c56e361 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -22,4 +22,6 @@ owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" [contracts.permit2] +# Canonical Uniswap Permit2 address (same on all chains via CREATE2). +# Using it here so frontends, SDKs and routers work out of the box. address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" From bfa5a23bd2889dbc21342fd0b1c32454b5f105b7 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:02:05 +0100 Subject: [PATCH 15/35] style(ev-deployer): fix fmt, clippy and rustdoc warnings in permit2 --- bin/ev-deployer/src/contracts/permit2.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index fd62f2ea..ee56f85b 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -7,8 +7,8 @@ //! //! | Variable | Type | Offset | //! |-----------------------------|---------|--------| -//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | -//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! | `_CACHED_CHAIN_ID` | uint256 | \[6945\] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | \[6983\] | //! //! Both come from the EIP-712 base contract (`src/EIP712.sol`). The //! constructor normally caches `block.chainid` and the resulting domain @@ -32,7 +32,7 @@ use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; use std::collections::BTreeMap; /// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) -/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// with solc 0.8.17 (via-ir, optimizer `1_000_000` runs, `bytecode_hash="none"`). /// /// Compiled with placeholder immutables (all zeros). Actual values are patched /// at genesis time via [`build`]. @@ -83,7 +83,11 @@ pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); buf[96..128].copy_from_slice(config.address.into_word().as_slice()); let domain_separator = keccak256(buf); - patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + patch_bytes( + &mut bytecode, + CACHED_DOMAIN_SEPARATOR_REFS, + &domain_separator.0, + ); GenesisContract { address: config.address, @@ -149,7 +153,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different chain IDs should produce different domain separators" + ); } #[test] @@ -169,7 +176,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different addresses should produce different domain separators" + ); } #[test] From 4da01ea7c5f08ef8af9493ed1a1e9a70ea0d391e Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:37:14 +0100 Subject: [PATCH 16/35] feat(ev-deployer): add Mailbox, NoopIsm, and ProtocolFee genesis contracts Add three Hyperlane core contracts to ev-deployer so the full messaging stack can be embedded at genesis without post-deploy transactions. - Mailbox: core messaging hub with localDomain/deployedBlock immutables - NoopIsm: stateless ISM that accepts all messages (for devnet) - ProtocolFee: post-dispatch hook with MAX_PROTOCOL_FEE immutable --- bin/ev-deployer/examples/devnet.toml | 17 + bin/ev-deployer/src/config.rs | 61 ++++ bin/ev-deployer/src/contracts/mailbox.rs | 295 ++++++++++++++++++ bin/ev-deployer/src/contracts/mod.rs | 3 + bin/ev-deployer/src/contracts/noop_ism.rs | 109 +++++++ bin/ev-deployer/src/contracts/protocol_fee.rs | 209 +++++++++++++ bin/ev-deployer/src/genesis.rs | 19 ++ bin/ev-deployer/src/main.rs | 18 ++ bin/ev-deployer/src/output.rs | 21 ++ 9 files changed, 752 insertions(+) create mode 100644 bin/ev-deployer/src/contracts/mailbox.rs create mode 100644 bin/ev-deployer/src/contracts/noop_ism.rs create mode 100644 bin/ev-deployer/src/contracts/protocol_fee.rs diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 87bafe09..91f033ca 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -16,7 +16,24 @@ bridge_share_bps = 10000 other_recipient = "0x0000000000000000000000000000000000000000" hyp_native_minter = "0x0000000000000000000000000000000000000000" +[contracts.mailbox] +address = "0x0000000000000000000000000000000000001200" +owner = "0x000000000000000000000000000000000000Ad00" +default_ism = "0x0000000000000000000000000000000000001300" +default_hook = "0x0000000000000000000000000000000000001400" +required_hook = "0x0000000000000000000000000000000000001100" + [contracts.merkle_tree_hook] address = "0x0000000000000000000000000000000000001100" owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" + +[contracts.noop_ism] +address = "0x0000000000000000000000000000000000001300" + +[contracts.protocol_fee] +address = "0x0000000000000000000000000000000000001400" +owner = "0x000000000000000000000000000000000000Ad00" +max_protocol_fee = 1000000000000000000 +protocol_fee = 0 +beneficiary = "0x000000000000000000000000000000000000Ad00" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bed1e625..b6f606cd 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -31,6 +31,12 @@ pub(crate) struct ContractsConfig { pub fee_vault: Option, /// `MerkleTreeHook` contract config (optional). pub merkle_tree_hook: Option, + /// `Mailbox` contract config (optional). + pub mailbox: Option, + /// `NoopIsm` contract config (optional). + pub noop_ism: Option, + /// `ProtocolFee` contract config (optional). + pub protocol_fee: Option, } /// `AdminProxy` configuration. @@ -84,6 +90,50 @@ pub(crate) struct MerkleTreeHookConfig { pub mailbox: Address, } +/// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee). +#[derive(Debug, Deserialize)] +pub(crate) struct ProtocolFeeConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + #[serde(default)] + pub owner: Address, + /// Maximum protocol fee in wei. + pub max_protocol_fee: u64, + /// Protocol fee charged per dispatch in wei. + #[serde(default)] + pub protocol_fee: u64, + /// Beneficiary address that receives collected fees. + #[serde(default)] + pub beneficiary: Address, +} + +/// `Mailbox` configuration (Hyperlane core messaging hub). +#[derive(Debug, Deserialize)] +pub(crate) struct MailboxConfig { + /// Address to deploy at. + pub address: Address, + /// Owner address. + #[serde(default)] + pub owner: Address, + /// Default interchain security module. + #[serde(default)] + pub default_ism: Address, + /// Default post-dispatch hook. + #[serde(default)] + pub default_hook: Address, + /// Required post-dispatch hook (e.g. `MerkleTreeHook`). + #[serde(default)] + pub required_hook: Address, +} + +/// `NoopIsm` configuration (Hyperlane ISM that accepts all messages). +#[derive(Debug, Deserialize)] +pub(crate) struct NoopIsmConfig { + /// Address to deploy at. + pub address: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -121,6 +171,17 @@ impl DeployConfig { ); } + if let Some(ref pf) = self.contracts.protocol_fee { + eyre::ensure!( + !pf.owner.is_zero(), + "protocol_fee.owner must not be the zero address" + ); + eyre::ensure!( + !pf.beneficiary.is_zero(), + "protocol_fee.beneficiary must not be the zero address" + ); + } + Ok(()) } } diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs new file mode 100644 index 00000000..64cdf3d4 --- /dev/null +++ b/bin/ev-deployer/src/contracts/mailbox.rs @@ -0,0 +1,295 @@ +//! `Mailbox` bytecode and storage encoding. +//! +//! `Mailbox` is the core Hyperlane messaging hub. It dispatches and processes +//! cross-chain messages. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offsets | +//! |-----------------|---------|----------------------| +//! | `deployedBlock` | uint256 | \[930\] | +//! | `localDomain` | uint32 | \[982, 2831, 5985\] | +//! +//! ## Storage layout (from `forge inspect Mailbox storageLayout`) +//! +//! | Slot | Variable | Type | +//! |-------|-----------------------------|-----------| +//! | 0 | `_initialized` + `_initializing` | uint8 + bool | +//! | 1-50 | `__gap` (Initializable) | — | +//! | 51 | `_owner` | address | +//! | 52-100| `__gap` (Ownable) | — | +//! | 101 | `nonce` | uint32 | +//! | 102 | `latestDispatchedId` | bytes32 | +//! | 103 | `defaultIsm` | address | +//! | 104 | `defaultHook` | address | +//! | 105 | `requiredHook` | address | +//! | 106 | `deliveries` (mapping) | — | + +use crate::{ + config::MailboxConfig, + contracts::{ + immutables::{patch_u256, patch_u32, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `Mailbox` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode +/// ``` +const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); + +// ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── + +/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. +const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 930, + length: 32, +}]; + +/// `localDomain` (uint32) — from `Mailbox.sol`. +const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ + ImmutableRef { + start: 982, + length: 32, + }, + ImmutableRef { + start: 2831, + length: 32, + }, + ImmutableRef { + start: 5985, + length: 32, + }, +]; + +/// Build a genesis alloc entry for `Mailbox`. +pub(crate) fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { + let mut bytecode = MAILBOX_BYTECODE.to_vec(); + + // Patch immutables + patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); + patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); + + let mut storage = BTreeMap::new(); + + // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false + storage.insert(B256::ZERO, B256::from(U256::from(1u8))); + + // Slot 51: _owner + if !config.owner.is_zero() { + storage.insert( + B256::from(U256::from(51u64)), + B256::from(U256::from_be_bytes(config.owner.into_word().0)), + ); + } + + // Slot 103: defaultIsm + if !config.default_ism.is_zero() { + storage.insert( + B256::from(U256::from(103u64)), + B256::from(U256::from_be_bytes(config.default_ism.into_word().0)), + ); + } + + // Slot 104: defaultHook + if !config.default_hook.is_zero() { + storage.insert( + B256::from(U256::from(104u64)), + B256::from(U256::from_be_bytes(config.default_hook.into_word().0)), + ); + } + + // Slot 105: requiredHook + if !config.required_hook.is_zero() { + storage.insert( + B256::from(U256::from(105u64)), + B256::from(U256::from_be_bytes(config.required_hook.into_word().0)), + ); + } + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> MailboxConfig { + MailboxConfig { + address: address!("0000000000000000000000000000000000001200"), + owner: address!("000000000000000000000000000000000000ad00"), + default_ism: address!("0000000000000000000000000000000000002000"), + default_hook: address!("0000000000000000000000000000000000003000"), + required_hook: address!("0000000000000000000000000000000000004000"), + } + } + + #[test] + fn storage_has_initialized_flag() { + let contract = build(&test_config(), 1234); + assert_eq!( + contract.storage[&B256::ZERO], + B256::from(U256::from(1u8)), + "_initialized should be 1" + ); + } + + #[test] + fn storage_has_owner() { + let contract = build(&test_config(), 1234); + let owner_slot = B256::from(U256::from(51u64)); + let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&owner_slot], expected); + } + + #[test] + fn storage_has_default_ism() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(103u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000002000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn storage_has_default_hook() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(104u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000003000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn storage_has_required_hook() { + let contract = build(&test_config(), 1234); + let slot = B256::from(U256::from(105u64)); + let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000004000" + .parse() + .unwrap(); + assert_eq!(contract.storage[&slot], expected); + } + + #[test] + fn bytecode_is_patched_with_local_domain() { + let config = test_config(); + let contract = build(&config, 42); + let code = contract.code.to_vec(); + + for &offset in &[982, 2831, 5985] { + let word = &code[offset..offset + 32]; + assert_eq!(word[31], 42, "localDomain not patched at offset {offset}"); + assert_eq!( + word[0..31], + [0u8; 31], + "localDomain padding wrong at offset {offset}" + ); + } + } + + #[test] + fn bytecode_has_zero_deployed_block() { + let config = test_config(); + let contract = build(&config, 1234); + let code = contract.code.to_vec(); + + let word = &code[930..930 + 32]; + assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); + } + + #[test] + fn storage_count_for_standard_config() { + let contract = build(&test_config(), 1234); + // _initialized (0) + _owner (51) + defaultIsm (103) + defaultHook (104) + requiredHook (105) + assert_eq!(contract.storage.len(), 5); + } + + #[test] + fn zero_addresses_omit_slots() { + let config = MailboxConfig { + address: address!("0000000000000000000000000000000000001200"), + owner: Address::ZERO, + default_ism: Address::ZERO, + default_hook: Address::ZERO, + required_hook: Address::ZERO, + }; + let contract = build(&config, 1234); + // Only _initialized (slot 0) should be present + assert_eq!(contract.storage.len(), 1); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(51u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(103u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(104u64)))); + assert!(!contract + .storage + .contains_key(&B256::from(U256::from(105u64)))); + } + + #[test] + #[ignore = "requires forge CLI"] + fn mailbox_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "Mailbox", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(MAILBOX_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "Mailbox bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c142cc0c..d2c6500f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -3,7 +3,10 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; pub(crate) mod immutables; +pub(crate) mod mailbox; pub(crate) mod merkle_tree_hook; +pub(crate) mod noop_ism; +pub(crate) mod protocol_fee; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/noop_ism.rs b/bin/ev-deployer/src/contracts/noop_ism.rs new file mode 100644 index 00000000..9f17efe0 --- /dev/null +++ b/bin/ev-deployer/src/contracts/noop_ism.rs @@ -0,0 +1,109 @@ +//! `NoopIsm` bytecode encoding. +//! +//! `NoopIsm` is a Hyperlane Interchain Security Module (ISM) that accepts all +//! messages without verification — `verify` always returns `true`. +//! +//! ## Immutables +//! +//! None. +//! +//! ## Storage layout +//! +//! None. + +use crate::{config::NoopIsmConfig, contracts::GenesisContract}; +use alloy_primitives::{hex, Bytes}; +use std::collections::BTreeMap; + +/// `NoopIsm` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode +/// ``` +const NOOP_ISM_BYTECODE: &[u8] = &hex!("608060405234801561001057600080fd5b50600436106100415760003560e01c80636465e69f1461004657806393c4484714610065578063f7e83aee146100ae575b600080fd5b61004e600681565b60405160ff90911681526020015b60405180910390f35b6100a16040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161005c91906100d6565b6100c66100bc36600461018c565b6001949350505050565b604051901515815260200161005c565b60006020808352835180602085015260005b81811015610104578581018301518582016040015282016100e8565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008083601f84011261015557600080fd5b50813567ffffffffffffffff81111561016d57600080fd5b60208301915083602082850101111561018557600080fd5b9250929050565b600080600080604085870312156101a257600080fd5b843567ffffffffffffffff808211156101ba57600080fd5b6101c688838901610143565b909650945060208701359150808211156101df57600080fd5b506101ec87828801610143565b9598949750955050505056"); + +/// Build a genesis alloc entry for `NoopIsm`. +pub(crate) fn build(config: &NoopIsmConfig) -> GenesisContract { + GenesisContract { + address: config.address, + code: Bytes::from(NOOP_ISM_BYTECODE.to_vec()), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> NoopIsmConfig { + NoopIsmConfig { + address: address!("0000000000000000000000000000000000001300"), + } + } + + #[test] + fn storage_is_empty() { + let contract = build(&test_config()); + assert!( + contract.storage.is_empty(), + "NoopIsm should have no storage" + ); + } + + #[test] + fn bytecode_is_present() { + let contract = build(&test_config()); + assert!( + !contract.code.is_empty(), + "NoopIsm should have non-empty bytecode" + ); + } + + #[test] + #[ignore = "requires forge CLI"] + fn noop_ism_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "NoopIsm", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(NOOP_ISM_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "NoopIsm bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs new file mode 100644 index 00000000..9dea5d59 --- /dev/null +++ b/bin/ev-deployer/src/contracts/protocol_fee.rs @@ -0,0 +1,209 @@ +//! `ProtocolFee` bytecode and storage encoding. +//! +//! `ProtocolFee` is a Hyperlane post-dispatch hook that charges a protocol fee +//! on message dispatches. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offsets | +//! |-------------------|---------|-----------------| +//! | `MAX_PROTOCOL_FEE`| uint256 | \[655, 2248\] | +//! +//! ## Storage layout (from `forge inspect ProtocolFee storageLayout`) +//! +//! | Slot | Variable | Type | +//! |------|---------------|---------| +//! | 0 | `_owner` | address | +//! | 1 | `protocolFee` | uint256 | +//! | 2 | `beneficiary` | address | + +use crate::{ + config::ProtocolFeeConfig, + contracts::{ + immutables::{patch_u256, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `ProtocolFee` runtime bytecode compiled with Hyperlane v11.0.3, +/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/hyperlane-monorepo/solidity && \ +/// forge soldeer install && \ +/// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode +/// ``` +const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401516103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); + +// ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── + +/// `MAX_PROTOCOL_FEE` (uint256) — maximum fee that can be set. +const MAX_PROTOCOL_FEE_REFS: &[ImmutableRef] = &[ + ImmutableRef { + start: 655, + length: 32, + }, + ImmutableRef { + start: 2248, + length: 32, + }, +]; + +/// Build a genesis alloc entry for `ProtocolFee`. +pub(crate) fn build(config: &ProtocolFeeConfig) -> GenesisContract { + let mut bytecode = PROTOCOL_FEE_BYTECODE.to_vec(); + + // Patch immutables + patch_u256( + &mut bytecode, + MAX_PROTOCOL_FEE_REFS, + U256::from(config.max_protocol_fee), + ); + + let mut storage = BTreeMap::new(); + + // Slot 0: _owner + if !config.owner.is_zero() { + storage.insert( + B256::ZERO, + B256::from(U256::from_be_bytes(config.owner.into_word().0)), + ); + } + + // Slot 1: protocolFee + if config.protocol_fee > 0 { + storage.insert( + B256::from(U256::from(1u64)), + B256::from(U256::from(config.protocol_fee)), + ); + } + + // Slot 2: beneficiary + if !config.beneficiary.is_zero() { + storage.insert( + B256::from(U256::from(2u64)), + B256::from(U256::from_be_bytes(config.beneficiary.into_word().0)), + ); + } + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> ProtocolFeeConfig { + ProtocolFeeConfig { + address: address!("0000000000000000000000000000000000001300"), + owner: address!("000000000000000000000000000000000000ad00"), + max_protocol_fee: 1_000_000_000_000_000_000, + protocol_fee: 100_000, + beneficiary: address!("000000000000000000000000000000000000be00"), + } + } + + #[test] + fn storage_has_owner() { + let contract = build(&test_config()); + let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" + .parse() + .unwrap(); + assert_eq!(contract.storage[&B256::ZERO], expected); + } + + #[test] + fn bytecode_is_patched_with_max_protocol_fee() { + let config = test_config(); + let contract = build(&config); + let code = contract.code.to_vec(); + + let expected = B256::from(U256::from(config.max_protocol_fee)); + for &offset in &[655, 2248] { + let word = &code[offset..offset + 32]; + assert_eq!( + word, + expected.as_slice(), + "max_protocol_fee not patched at offset {offset}" + ); + } + } + + #[test] + fn zero_protocol_fee_omits_slot_1() { + let config = ProtocolFeeConfig { + address: address!("0000000000000000000000000000000000001300"), + owner: address!("000000000000000000000000000000000000ad00"), + max_protocol_fee: 1_000_000_000_000_000_000, + protocol_fee: 0, + beneficiary: address!("000000000000000000000000000000000000be00"), + }; + let contract = build(&config); + let fee_slot = B256::from(U256::from(1u64)); + assert!( + !contract.storage.contains_key(&fee_slot), + "zero protocol_fee should not produce a storage entry" + ); + } + + #[test] + fn storage_count_for_standard_config() { + let contract = build(&test_config()); + // Should have exactly 3 storage entries: _owner (slot 0), protocolFee (slot 1), beneficiary (slot 2) + assert_eq!(contract.storage.len(), 3); + } + + #[test] + #[ignore = "requires forge CLI"] + fn protocol_fee_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("hyperlane-monorepo") + .join("solidity"); + + let output = Command::new("forge") + .args(["inspect", "ProtocolFee", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .env("FOUNDRY_PROFILE", "ci") + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(PROTOCOL_FEE_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "ProtocolFee bytecode mismatch! Regenerate with: \ + cd contracts/lib/hyperlane-monorepo/solidity && \ + FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fee43554..eb3b7283 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -28,6 +28,22 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref mb_config) = config.contracts.mailbox { + let local_domain = config.chain.chain_id as u32; + let contract = contracts::mailbox::build(mb_config, local_domain); + insert_contract(&mut alloc, &contract); + } + + if let Some(ref ni_config) = config.contracts.noop_ism { + let contract = contracts::noop_ism::build(ni_config); + insert_contract(&mut alloc, &contract); + } + + if let Some(ref pf_config) = config.contracts.protocol_fee { + let contract = contracts::protocol_fee::build(pf_config); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -105,6 +121,9 @@ mod tests { }), fee_vault: None, merkle_tree_hook: None, + mailbox: None, + noop_ism: None, + protocol_fee: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5c9d30e..2fb6f901 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -115,6 +115,24 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, + "mailbox" => cfg + .contracts + .mailbox + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("mailbox not configured"))?, + "noop_ism" => cfg + .contracts + .noop_ism + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("noop_ism not configured"))?, + "protocol_fee" => cfg + .contracts + .protocol_fee + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("protocol_fee not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 59ae29af..56e62608 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -28,5 +28,26 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref mb) = config.contracts.mailbox { + manifest.insert( + "mailbox".to_string(), + Value::String(format!("{}", mb.address)), + ); + } + + if let Some(ref ni) = config.contracts.noop_ism { + manifest.insert( + "noop_ism".to_string(), + Value::String(format!("{}", ni.address)), + ); + } + + if let Some(ref pf) = config.contracts.protocol_fee { + manifest.insert( + "protocol_fee".to_string(), + Value::String(format!("{}", pf.address)), + ); + } + Value::Object(manifest) } From 3faa62955aaf21e7d9effb7bcaecb4f82670c9e1 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:42:35 +0100 Subject: [PATCH 17/35] fix(ev-deployer): regenerate Mailbox and ProtocolFee bytecodes from clean ci build The embedded bytecodes were compiled with --extra-output storageLayout which subtly altered the output. Regenerated from a clean ci profile build to match what forge produces without extra flags. --- bin/ev-deployer/src/contracts/mailbox.rs | 2 +- bin/ev-deployer/src/contracts/protocol_fee.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs index 64cdf3d4..4f774587 100644 --- a/bin/ev-deployer/src/contracts/mailbox.rs +++ b/bin/ev-deployer/src/contracts/mailbox.rs @@ -47,7 +47,7 @@ use std::collections::BTreeMap; /// forge soldeer install && \ /// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode /// ``` -const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); +const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016109cd565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); // ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs index 9dea5d59..d7b2135c 100644 --- a/bin/ev-deployer/src/contracts/protocol_fee.rs +++ b/bin/ev-deployer/src/contracts/protocol_fee.rs @@ -39,7 +39,7 @@ use std::collections::BTreeMap; /// forge soldeer install && \ /// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode /// ``` -const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401516103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); +const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); // ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── From aeffc0da683867cf74dd9b387d78077593206910 Mon Sep 17 00:00:00 2001 From: Jonathan Gimeno Date: Thu, 19 Mar 2026 15:22:17 +0100 Subject: [PATCH 18/35] 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 19/35] 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 20/35] 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 21/35] 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 22/35] 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 23/35] 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 24/35] 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 25/35] 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 26/35] 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 27/35] 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 28/35] 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 29/35] style(ev-deployer): fix fmt and clippy lint in genesis.rs --- bin/ev-deployer/src/genesis.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index 76033b70..167499bb 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -79,7 +79,7 @@ fn insert_contract(alloc: &mut Map, contract: &GenesisContract) { ); entry.insert("storage".to_string(), Value::Object(storage_map)); - alloc.insert(addr_key.to_string(), Value::Object(entry)); + alloc.insert(addr_key, Value::Object(entry)); } /// Format a storage slot key as a full 32-byte hex string. @@ -183,7 +183,8 @@ mod tests { #[test] fn merge_detects_collision_with_0x_prefix() { - let genesis = r#"{"alloc":{"0x000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; + let genesis = + r#"{"alloc":{"0x000000000000000000000000000000000000ad00":{"balance":"0x0"}}}"#; let tmp = tempfile::NamedTempFile::new().unwrap(); std::fs::write(tmp.path(), genesis).unwrap(); From 7f9e238d89099fb6bce9a310a0b1f52958eb6799 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 05:57:53 +0100 Subject: [PATCH 30/35] docs(ev-deployer): add Permit2 to init template and README --- bin/ev-deployer/README.md | 8 ++++++++ bin/ev-deployer/src/init_template.toml | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index 235e493a..d02d9c11 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -69,6 +69,12 @@ See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with a |-----------|---------|----------------------| | `address` | address | Address to deploy at | +#### `[contracts.permit2]` + +| Field | Type | Description | +|-----------|---------|----------------------------------------------------------| +| `address` | address | Address to deploy at (canonical: `0x000000000022D473...`) | + #### `[contracts.protocol_fee]` | Field | Type | Description | @@ -134,6 +140,7 @@ Output: "mailbox": "0x0000000000000000000000000000000000001200", "merkle_tree_hook": "0x0000000000000000000000000000000000001100", "noop_ism": "0x0000000000000000000000000000000000001300", + "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "protocol_fee": "0x0000000000000000000000000000000000001400" } ``` @@ -153,6 +160,7 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy | `mailbox` | Hyperlane core messaging hub | | `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) | | `noop_ism` | Hyperlane ISM that accepts all messages | +| `permit2` | Uniswap canonical token approval manager | | `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee| Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 2d75b981..97f8eeb9 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -45,6 +45,10 @@ chain_id = 0 # [contracts.noop_ism] # address = "0x0000000000000000000000000000000000001300" +# Permit2: Uniswap canonical token approval manager. +# [contracts.permit2] +# address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" + # ProtocolFee: Hyperlane post-dispatch hook that charges a protocol fee. # [contracts.protocol_fee] # address = "0x0000000000000000000000000000000000001400" From b1e5ae30e336e4b7a329b3ef598efba3679c3881 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Wed, 25 Mar 2026 10:52:03 +0100 Subject: [PATCH 31/35] fix(ev-deployer): use case-insensitive grep in e2e genesis address checks --- bin/ev-deployer/tests/e2e_genesis.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 19424ae8..b1f318b3 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -77,13 +77,13 @@ echo "=== Generating genesis with ev-deployer ===" echo "Genesis written to $GENESIS" # Quick sanity: address should be in the alloc -grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ +grep -qi "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" -grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ +grep -qi "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" -grep -q "0000000000000000000000000000000000001100" "$GENESIS" \ +grep -qi "0000000000000000000000000000000000001100" "$GENESIS" \ || fail "MerkleTreeHook address not found in genesis" -grep -q "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ +grep -qi "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ || fail "Permit2 address not found in genesis" pass "genesis contains all contract addresses" From 5d2be71ebdc2ae25aac1c656caf7514c49db33cc Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 11:48:56 +0100 Subject: [PATCH 32/35] refactor(ev-deployer): remove Hyperlane contracts, keep only AdminProxy and Permit2 Remove FeeVault, Mailbox, MerkleTreeHook, NoopIsm, and ProtocolFee contracts along with their config, validation, tests, docs, and e2e checks. This scopes the permit2 branch to only Part 1 (core) and Part 3 (Permit2) functionality. --- bin/ev-deployer/README.md | 70 +---- bin/ev-deployer/examples/devnet.toml | 33 -- bin/ev-deployer/src/config.rs | 185 +---------- bin/ev-deployer/src/contracts/fee_vault.rs | 184 ----------- bin/ev-deployer/src/contracts/immutables.rs | 53 +--- bin/ev-deployer/src/contracts/mailbox.rs | 295 ------------------ .../src/contracts/merkle_tree_hook.rs | 254 --------------- bin/ev-deployer/src/contracts/mod.rs | 5 - bin/ev-deployer/src/contracts/noop_ism.rs | 109 ------- bin/ev-deployer/src/contracts/protocol_fee.rs | 209 ------------- bin/ev-deployer/src/genesis.rs | 32 -- bin/ev-deployer/src/init_template.toml | 38 --- bin/ev-deployer/src/main.rs | 30 -- bin/ev-deployer/src/output.rs | 35 --- bin/ev-deployer/tests/e2e_genesis.sh | 116 +------ 15 files changed, 8 insertions(+), 1640 deletions(-) delete mode 100644 bin/ev-deployer/src/contracts/fee_vault.rs delete mode 100644 bin/ev-deployer/src/contracts/mailbox.rs delete mode 100644 bin/ev-deployer/src/contracts/merkle_tree_hook.rs delete mode 100644 bin/ev-deployer/src/contracts/noop_ism.rs delete mode 100644 bin/ev-deployer/src/contracts/protocol_fee.rs diff --git a/bin/ev-deployer/README.md b/bin/ev-deployer/README.md index d02d9c11..fe7ae4dc 100644 --- a/bin/ev-deployer/README.md +++ b/bin/ev-deployer/README.md @@ -14,8 +14,6 @@ The binary is output to `target/release/ev-deployer`. EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example. -See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured. - ### Config reference #### `[chain]` @@ -31,60 +29,12 @@ See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with a | `address` | address | Address to deploy at | | `owner` | address | Owner (must not be zero) | -#### `[contracts.fee_vault]` - -| Field | Type | Description | -|----------------------|---------|--------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address | -| `destination_domain` | u32 | Hyperlane destination domain (default: 0) | -| `recipient_address` | bytes32 | Hyperlane recipient address (default: zero) | -| `minimum_amount` | u64 | Minimum amount for bridging (default: 0) | -| `call_fee` | u64 | Call fee for sendToCelestia (default: 0) | -| `bridge_share_bps` | u64 | Basis points for bridge share, 0–10000 (default: 0, treated as 10000) | -| `other_recipient` | address | Other recipient for split accounting (default: zero) | -| `hyp_native_minter` | address | HypNativeMinter address (default: zero) | - -#### `[contracts.mailbox]` - -| Field | Type | Description | -|-----------------|---------|-----------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `default_ism` | address | Default interchain security module (default: zero) | -| `default_hook` | address | Default post-dispatch hook (default: zero) | -| `required_hook` | address | Required post-dispatch hook, e.g. MerkleTreeHook (default: zero) | - -#### `[contracts.merkle_tree_hook]` - -| Field | Type | Description | -|-----------|---------|----------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `mailbox` | address | Mailbox address (patched into bytecode as immutable)| - -#### `[contracts.noop_ism]` - -| Field | Type | Description | -|-----------|---------|----------------------| -| `address` | address | Address to deploy at | - #### `[contracts.permit2]` | Field | Type | Description | |-----------|---------|----------------------------------------------------------| | `address` | address | Address to deploy at (canonical: `0x000000000022D473...`) | -#### `[contracts.protocol_fee]` - -| Field | Type | Description | -|--------------------|---------|---------------------------------------------------| -| `address` | address | Address to deploy at | -| `owner` | address | Owner address (default: zero) | -| `max_protocol_fee` | u64 | Maximum protocol fee in wei | -| `protocol_fee` | u64 | Protocol fee charged per dispatch in wei (default: 0) | -| `beneficiary` | address | Beneficiary address that receives collected fees (default: zero) | - ## Usage ### Generate a starter config @@ -136,12 +86,7 @@ Output: ```json { "admin_proxy": "0x000000000000000000000000000000000000Ad00", - "fee_vault": "0x000000000000000000000000000000000000FE00", - "mailbox": "0x0000000000000000000000000000000000001200", - "merkle_tree_hook": "0x0000000000000000000000000000000000001100", - "noop_ism": "0x0000000000000000000000000000000000001300", - "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3", - "protocol_fee": "0x0000000000000000000000000000000000001400" + "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3" } ``` @@ -153,15 +98,10 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy ## Contracts -| Contract | Description | -|--------------------|---------------------------------------------------------| -| `admin_proxy` | Proxy contract with owner-based access control | -| `fee_vault` | Fee vault with Hyperlane bridging support | -| `mailbox` | Hyperlane core messaging hub | -| `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) | -| `noop_ism` | Hyperlane ISM that accepts all messages | -| `permit2` | Uniswap canonical token approval manager | -| `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee| +| Contract | Description | +|---------------|----------------------------------------------------| +| `admin_proxy` | Proxy contract with owner-based access control | +| `permit2` | Uniswap canonical token approval manager | Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time. diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index a17ad4ca..5e3d9096 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -5,40 +5,7 @@ chain_id = 1234 address = "0x000000000000000000000000000000000000Ad00" owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000FE00" -owner = "0x000000000000000000000000000000000000Ad00" -destination_domain = 0 -recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -minimum_amount = 0 -call_fee = 0 -bridge_share_bps = 10000 -other_recipient = "0x0000000000000000000000000000000000000000" -hyp_native_minter = "0x0000000000000000000000000000000000000000" - -[contracts.mailbox] -address = "0x0000000000000000000000000000000000001200" -owner = "0x000000000000000000000000000000000000Ad00" -default_ism = "0x0000000000000000000000000000000000001300" -default_hook = "0x0000000000000000000000000000000000001400" -required_hook = "0x0000000000000000000000000000000000001100" - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -owner = "0x000000000000000000000000000000000000Ad00" -mailbox = "0x0000000000000000000000000000000000001200" - -[contracts.noop_ism] -address = "0x0000000000000000000000000000000000001300" - [contracts.permit2] # Canonical Uniswap Permit2 address (same on all chains via CREATE2). # Using it here so frontends, SDKs and routers work out of the box. address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" - -[contracts.protocol_fee] -address = "0x0000000000000000000000000000000000001400" -owner = "0x000000000000000000000000000000000000Ad00" -max_protocol_fee = 1000000000000000000 -protocol_fee = 0 -beneficiary = "0x000000000000000000000000000000000000Ad00" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index 4dad06a2..bf1f8671 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -1,6 +1,6 @@ //! TOML config types, parsing, and validation. -use alloy_primitives::{Address, B256}; +use alloy_primitives::Address; use serde::Deserialize; use std::path::Path; @@ -28,18 +28,8 @@ pub(crate) struct ChainConfig { pub(crate) struct ContractsConfig { /// `AdminProxy` contract config (optional). pub admin_proxy: Option, - /// `FeeVault` contract config (optional). - pub fee_vault: Option, - /// `MerkleTreeHook` contract config (optional). - pub merkle_tree_hook: Option, - /// `Mailbox` contract config (optional). - pub mailbox: Option, - /// `NoopIsm` contract config (optional). - pub noop_ism: Option, /// `Permit2` contract config (optional). pub permit2: Option, - /// `ProtocolFee` contract config (optional). - pub protocol_fee: Option, } /// `AdminProxy` configuration. @@ -51,74 +41,6 @@ pub(crate) struct AdminProxyConfig { pub owner: Address, } -/// `FeeVault` configuration. -#[derive(Debug, Deserialize)] -pub(crate) struct FeeVaultConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - pub owner: Address, - /// Hyperlane destination domain. - #[serde(default)] - pub destination_domain: u32, - /// Hyperlane recipient address (bytes32). - #[serde(default)] - pub recipient_address: B256, - /// Minimum amount for bridging. - #[serde(default)] - pub minimum_amount: u64, - /// Call fee for sendToCelestia. - #[serde(default)] - pub call_fee: u64, - /// Basis points for bridge share (0-10000). 0 defaults to 10000. - #[serde(default)] - pub bridge_share_bps: u64, - /// Other recipient for split accounting. - #[serde(default)] - pub other_recipient: Address, - /// `HypNativeMinter` address. - #[serde(default)] - pub hyp_native_minter: Address, -} - -/// `MerkleTreeHook` configuration (Hyperlane required hook). -#[derive(Debug, Deserialize)] -pub(crate) struct MerkleTreeHookConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address (for post-genesis hook/ISM changes). - #[serde(default)] - pub owner: Address, - /// Mailbox address (patched into bytecode as immutable). - pub mailbox: Address, -} - -/// `MailboxConfig` configuration (Hyperlane core messaging hub). -#[derive(Debug, Deserialize)] -pub(crate) struct MailboxConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - #[serde(default)] - pub owner: Address, - /// Default interchain security module. - #[serde(default)] - pub default_ism: Address, - /// Default post-dispatch hook. - #[serde(default)] - pub default_hook: Address, - /// Required post-dispatch hook (e.g. `MerkleTreeHook`). - #[serde(default)] - pub required_hook: Address, -} - -/// `NoopIsm` configuration (Hyperlane ISM that accepts all messages). -#[derive(Debug, Deserialize)] -pub(crate) struct NoopIsmConfig { - /// Address to deploy at. - pub address: Address, -} - /// `Permit2` configuration (Uniswap token approval manager). #[derive(Debug, Deserialize)] pub(crate) struct Permit2Config { @@ -126,24 +48,6 @@ pub(crate) struct Permit2Config { pub address: Address, } -/// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee). -#[derive(Debug, Deserialize)] -pub(crate) struct ProtocolFeeConfig { - /// Address to deploy at. - pub address: Address, - /// Owner address. - #[serde(default)] - pub owner: Address, - /// Maximum protocol fee in wei. - pub max_protocol_fee: u64, - /// Protocol fee charged per dispatch in wei. - #[serde(default)] - pub protocol_fee: u64, - /// Beneficiary address that receives collected fees. - #[serde(default)] - pub beneficiary: Address, -} - impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { @@ -162,43 +66,6 @@ impl DeployConfig { ); } - if let Some(ref fv) = self.contracts.fee_vault { - eyre::ensure!( - !fv.owner.is_zero(), - "fee_vault.owner must not be the zero address" - ); - eyre::ensure!( - fv.bridge_share_bps <= 10000, - "fee_vault.bridge_share_bps must be 0-10000, got {}", - fv.bridge_share_bps - ); - } - - if let Some(ref mth) = self.contracts.merkle_tree_hook { - eyre::ensure!( - !mth.mailbox.is_zero(), - "merkle_tree_hook.mailbox must not be the zero address" - ); - } - - if let Some(ref pf) = self.contracts.protocol_fee { - eyre::ensure!( - !pf.owner.is_zero(), - "protocol_fee.owner must not be the zero address" - ); - eyre::ensure!( - !pf.beneficiary.is_zero(), - "protocol_fee.beneficiary must not be the zero address" - ); - } - - if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) { - eyre::ensure!( - ap.address != fv.address, - "contracts.admin_proxy.address and contracts.fee_vault.address must be distinct" - ); - } - Ok(()) } } @@ -248,56 +115,6 @@ chain_id = 1 assert!(config.contracts.admin_proxy.is_none()); } - #[test] - fn parse_merkle_tree_hook_config() { - let toml = r#" -[chain] -chain_id = 1234 - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -owner = "0x000000000000000000000000000000000000ad00" -mailbox = "0x0000000000000000000000000000000000001200" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - config.validate().unwrap(); - assert!(config.contracts.merkle_tree_hook.is_some()); - let mth = config.contracts.merkle_tree_hook.unwrap(); - assert!(!mth.mailbox.is_zero()); - } - - #[test] - fn reject_zero_mailbox_merkle_tree_hook() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.merkle_tree_hook] -address = "0x0000000000000000000000000000000000001100" -mailbox = "0x0000000000000000000000000000000000000000" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - - #[test] - fn reject_duplicate_addresses() { - let toml = r#" -[chain] -chain_id = 1 - -[contracts.admin_proxy] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - -[contracts.fee_vault] -address = "0x000000000000000000000000000000000000Ad00" -owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -"#; - let config: DeployConfig = toml::from_str(toml).unwrap(); - assert!(config.validate().is_err()); - } - #[test] fn admin_proxy_only() { let toml = r#" diff --git a/bin/ev-deployer/src/contracts/fee_vault.rs b/bin/ev-deployer/src/contracts/fee_vault.rs deleted file mode 100644 index 445ea8c1..00000000 --- a/bin/ev-deployer/src/contracts/fee_vault.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! `FeeVault` bytecode and storage encoding. - -use crate::{config::FeeVaultConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `FeeVault` runtime bytecode compiled with solc 0.8.33 (`cbor_metadata=false`). -/// Regenerate with: `cd contracts && forge inspect FeeVault deployedBytecode` -const FEE_VAULT_BYTECODE: &[u8] = &hex!("608060405260043610610101575f3560e01c80636cb53e1611610094578063bb0c829811610063578063bb0c8298146102dc578063c3f909d414610306578063eeb4a9c814610337578063f2fde38b1461035f578063f63188b71461038757610108565b80636cb53e16146102565780637d57d97a1461027e5780638da5cb5b1461028857806390321e1a146102b257610108565b806339bb1c5b116100d057806339bb1c5b146101ae5780634cebdc49146101d85780635aff5999146102025780635c4a6d841461022c57610108565b80631636b3681461010c57806326465826146101345780632858c55a1461015c5780632c2d80891461018657610108565b3661010857005b5f5ffd5b348015610117575f5ffd5b50610132600480360381019061012d919061117f565b6103af565b005b34801561013f575f5ffd5b5061015a600480360381019061015591906111dd565b610526565b005b348015610167575f5ffd5b506101706105f6565b60405161017d9190611226565b60405180910390f35b348015610191575f5ffd5b506101ac60048036038101906101a7919061129c565b61060c565b005b3480156101b9575f5ffd5b506101c2610700565b6040516101cf9190611335565b60405180910390f35b3480156101e3575f5ffd5b506101ec610724565b6040516101f9919061135d565b60405180910390f35b34801561020d575f5ffd5b50610216610749565b6040516102239190611385565b60405180910390f35b348015610237575f5ffd5b5061024061074f565b60405161024d91906113ad565b60405180910390f35b348015610261575f5ffd5b5061027c6004803603810190610277919061117f565b610755565b005b6102866108cb565b005b348015610293575f5ffd5b5061029c610caa565b6040516102a9919061135d565b60405180910390f35b3480156102bd575f5ffd5b506102c6610ccf565b6040516102d391906113ad565b60405180910390f35b3480156102e7575f5ffd5b506102f0610cd5565b6040516102fd91906113ad565b60405180910390f35b348015610311575f5ffd5b5061031a610cdb565b60405161032e9897969594939291906113c6565b60405180910390f35b348015610342575f5ffd5b5061035d600480360381019061035891906111dd565b610d81565b005b34801561036a575f5ffd5b506103856004803603810190610380919061117f565b610e51565b005b348015610392575f5ffd5b506103ad60048036038101906103a891906111dd565b61100c565b005b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461043e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610435906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036104ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104a39061152a565b60405180910390fd5b8060055f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fa50c88d04012de3892b47d81943c983dc2690cfb81f0428eaa7d382f95683e4a8160405161051b919061135d565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146105b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ac906114c2565b60405180910390fd5b806004819055507f63a8f7442c91b7117b3f235d24793c034fd752a01266bef3ef1d051efb56ca3d816040516105eb91906113ad565b60405180910390a150565b600160149054906101000a900463ffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461069b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610692906114c2565b60405180910390fd5b81600160146101000a81548163ffffffff021916908363ffffffff160217905550806002819055507fcac2c3add78f132121267d80a684a62d521a9799fd8434bd0da1a27c491b044982826040516106f4929190611548565b60405180910390a15050565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60065481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146107e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107db906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610852576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108499061152a565b60405180910390fd5b805f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f6eedba6e0a60268e3d78633f8822cea5dc75430d531f96fb46a29333834665c6816040516108c0919061135d565b60405180910390a150565b5f73ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610959576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610950906115b9565b60405180910390fd5b60045434101561099e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161099590611621565b60405180910390fd5b5f4790505f612710600654836109b4919061166c565b6109be91906116da565b90505f81836109cd919061170a565b9050600354821015610a14576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a0b90611787565b60405180910390fd5b7f50ecfcc47f2c5b2a26f91422abf650476ec7f701c48b1cf6d1d6d4d51a872ed6838383604051610a47939291906117a5565b60405180910390a15f811115610bb1575f73ffffffffffffffffffffffffffffffffffffffff1660055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610ae6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610add9061184a565b60405180910390fd5b5f60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1682604051610b2c90611895565b5f6040518083038185875af1925050503d805f8114610b66576040519150601f19603f3d011682016040523d82523d5f602084013e610b6b565b606091505b5050905080610baf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba6906118f3565b60405180910390fd5b505b5f5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381b4e8b484600160149054906101000a900463ffffffff16600254876040518563ffffffff1660e01b8152600401610c2493929190611911565b60206040518083038185885af1158015610c40573d5f5f3e3d5ffd5b50505050506040513d601f19601f82011682018060405250810190610c65919061195a565b90507f301fb78c068680a9fb5daa4ebadf5914ddc3a317f1fdc2c97f32740374d61e748360025483604051610c9c93929190611985565b60405180910390a150505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60045481565b60035481565b5f5f5f5f5f5f5f5f60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160149054906101000a900463ffffffff1660025460035460045460065460055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16975097509750975097509750975097509091929394959697565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e07906114c2565b60405180910390fd5b806003819055507f6ea576632a91ef2f8d4ee43600561b386f3c0254692977f0d33e17742bc5355881604051610e4691906113ad565b60405180910390a150565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ee0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ed7906114c2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610f4e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f4590611a2a565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a38060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461109b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611092906114c2565b60405180910390fd5b6127108111156110e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d790611a92565b60405180910390fd5b806006819055507fa8da92ecf88f6d9f058e5f86d614520d5f20a3ecf87914deb605f649bd63de878160405161111691906113ad565b60405180910390a150565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61114e82611125565b9050919050565b61115e81611144565b8114611168575f5ffd5b50565b5f8135905061117981611155565b92915050565b5f6020828403121561119457611193611121565b5b5f6111a18482850161116b565b91505092915050565b5f819050919050565b6111bc816111aa565b81146111c6575f5ffd5b50565b5f813590506111d7816111b3565b92915050565b5f602082840312156111f2576111f1611121565b5b5f6111ff848285016111c9565b91505092915050565b5f63ffffffff82169050919050565b61122081611208565b82525050565b5f6020820190506112395f830184611217565b92915050565b61124881611208565b8114611252575f5ffd5b50565b5f813590506112638161123f565b92915050565b5f819050919050565b61127b81611269565b8114611285575f5ffd5b50565b5f8135905061129681611272565b92915050565b5f5f604083850312156112b2576112b1611121565b5b5f6112bf85828601611255565b92505060206112d085828601611288565b9150509250929050565b5f819050919050565b5f6112fd6112f86112f384611125565b6112da565b611125565b9050919050565b5f61130e826112e3565b9050919050565b5f61131f82611304565b9050919050565b61132f81611315565b82525050565b5f6020820190506113485f830184611326565b92915050565b61135781611144565b82525050565b5f6020820190506113705f83018461134e565b92915050565b61137f81611269565b82525050565b5f6020820190506113985f830184611376565b92915050565b6113a7816111aa565b82525050565b5f6020820190506113c05f83018461139e565b92915050565b5f610100820190506113da5f83018b61134e565b6113e7602083018a611217565b6113f46040830189611376565b611401606083018861139e565b61140e608083018761139e565b61141b60a083018661139e565b61142860c083018561134e565b61143560e083018461134e565b9998505050505050505050565b5f82825260208201905092915050565b7f4665655661756c743a2063616c6c6572206973206e6f7420746865206f776e655f8201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b5f6114ac602183611442565b91506114b782611452565b604082019050919050565b5f6020820190508181035f8301526114d9816114a0565b9050919050565b7f4665655661756c743a207a65726f2061646472657373000000000000000000005f82015250565b5f611514601683611442565b915061151f826114e0565b602082019050919050565b5f6020820190508181035f83015261154181611508565b9050919050565b5f60408201905061155b5f830185611217565b6115686020830184611376565b9392505050565b7f4665655661756c743a206d696e746572206e6f742073657400000000000000005f82015250565b5f6115a3601883611442565b91506115ae8261156f565b602082019050919050565b5f6020820190508181035f8301526115d081611597565b9050919050565b7f4665655661756c743a20696e73756666696369656e74206665650000000000005f82015250565b5f61160b601a83611442565b9150611616826115d7565b602082019050919050565b5f6020820190508181035f830152611638816115ff565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611676826111aa565b9150611681836111aa565b925082820261168f816111aa565b915082820484148315176116a6576116a561163f565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6116e4826111aa565b91506116ef836111aa565b9250826116ff576116fe6116ad565b5b828204905092915050565b5f611714826111aa565b915061171f836111aa565b92508282039050818111156117375761173661163f565b5b92915050565b7f4665655661756c743a206d696e696d756d20616d6f756e74206e6f74206d65745f82015250565b5f611771602083611442565b915061177c8261173d565b602082019050919050565b5f6020820190508181035f83015261179e81611765565b9050919050565b5f6060820190506117b85f83018661139e565b6117c5602083018561139e565b6117d2604083018461139e565b949350505050565b7f4665655661756c743a206f7468657220726563697069656e74206e6f742073655f8201527f7400000000000000000000000000000000000000000000000000000000000000602082015250565b5f611834602183611442565b915061183f826117da565b604082019050919050565b5f6020820190508181035f83015261186181611828565b9050919050565b5f81905092915050565b50565b5f6118805f83611868565b915061188b82611872565b5f82019050919050565b5f61189f82611875565b9150819050919050565b7f4665655661756c743a207472616e73666572206661696c6564000000000000005f82015250565b5f6118dd601983611442565b91506118e8826118a9565b602082019050919050565b5f6020820190508181035f83015261190a816118d1565b9050919050565b5f6060820190506119245f830186611217565b6119316020830185611376565b61193e604083018461139e565b949350505050565b5f8151905061195481611272565b92915050565b5f6020828403121561196f5761196e611121565b5b5f61197c84828501611946565b91505092915050565b5f6060820190506119985f83018661139e565b6119a56020830185611376565b6119b26040830184611376565b949350505050565b7f4665655661756c743a206e6577206f776e657220697320746865207a65726f205f8201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b5f611a14602783611442565b9150611a1f826119ba565b604082019050919050565b5f6020820190508181035f830152611a4181611a08565b9050919050565b7f4665655661756c743a20696e76616c69642062707300000000000000000000005f82015250565b5f611a7c601583611442565b9150611a8782611a48565b602082019050919050565b5f6020820190508181035f830152611aa981611a70565b905091905056"); - -/// Build a genesis alloc entry for `FeeVault`. -pub(crate) fn build(config: &FeeVaultConfig) -> GenesisContract { - let mut storage = BTreeMap::new(); - - // Apply constructor default: bps 0 -> 10000 - let effective_bps = if config.bridge_share_bps == 0 { - 10000 - } else { - config.bridge_share_bps - }; - - // Slot 0: hypNativeMinter (address) - storage.insert( - B256::ZERO, - B256::from(U256::from_be_bytes(config.hyp_native_minter.into_word().0)), - ); - - // Slot 1: owner (lower 160 bits) + destinationDomain (shifted left 160 bits) - let owner_u256 = U256::from_be_bytes(config.owner.into_word().0); - let domain_u256 = U256::from(config.destination_domain) << 160; - storage.insert( - B256::with_last_byte(1), - B256::from(owner_u256 | domain_u256), - ); - - // Slot 2: recipientAddress (bytes32) - storage.insert(B256::with_last_byte(2), config.recipient_address); - - // Slot 3: minimumAmount - storage.insert( - B256::with_last_byte(3), - B256::from(U256::from(config.minimum_amount)), - ); - - // Slot 4: callFee - storage.insert( - B256::with_last_byte(4), - B256::from(U256::from(config.call_fee)), - ); - - // Slot 5: otherRecipient (address) - storage.insert( - B256::with_last_byte(5), - B256::from(U256::from_be_bytes(config.other_recipient.into_word().0)), - ); - - // Slot 6: bridgeShareBps - storage.insert( - B256::with_last_byte(6), - B256::from(U256::from(effective_bps)), - ); - - GenesisContract { - address: config.address, - code: Bytes::from_static(FEE_VAULT_BYTECODE), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, Address}; - use std::{path::PathBuf, process::Command}; - - #[test] - fn fee_vault_storage_encoding() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // Slot 0: hypNativeMinter = zero - assert_eq!(contract.storage[&B256::ZERO], B256::ZERO); - - // Slot 1: owner packed with domain - let expected_slot1: B256 = - "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected_slot1); - - // Slot 6: bridgeShareBps = 10000 - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn bps_zero_defaults_to_10000() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("000000000000000000000000000000000000Ad00"), - destination_domain: 0, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 0, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - let expected_slot6 = B256::from(U256::from(10000u64)); - assert_eq!(contract.storage[&B256::with_last_byte(6)], expected_slot6); - } - - #[test] - fn slot1_packing_with_nonzero_domain() { - let config = FeeVaultConfig { - address: address!("000000000000000000000000000000000000FE00"), - owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - destination_domain: 42, - recipient_address: B256::ZERO, - minimum_amount: 0, - call_fee: 0, - bridge_share_bps: 10000, - other_recipient: Address::ZERO, - hyp_native_minter: Address::ZERO, - }; - let contract = build(&config); - - // slot1 = (42 << 160) | owner - let owner_u256 = U256::from_be_bytes( - address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266") - .into_word() - .0, - ); - let expected = B256::from((U256::from(42u32) << 160) | owner_u256); - assert_eq!(contract.storage[&B256::with_last_byte(1)], expected); - } - - #[test] - #[ignore = "requires forge CLI"] - fn fee_vault_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts"); - - let output = Command::new("forge") - .args(["inspect", "FeeVault", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .trim_start_matches("0x") - .to_lowercase(); - - let hardcoded_hex = hex::encode(FEE_VAULT_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "FeeVault bytecode mismatch! Update the constant with: cd contracts && forge inspect FeeVault deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/immutables.rs b/bin/ev-deployer/src/contracts/immutables.rs index 40c75f48..09341047 100644 --- a/bin/ev-deployer/src/contracts/immutables.rs +++ b/bin/ev-deployer/src/contracts/immutables.rs @@ -6,7 +6,7 @@ //! byte offsets. This module replaces those regions with the actual values from //! the deploy config at genesis-generation time. -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{B256, U256}; /// A single immutable reference inside a bytecode blob. #[derive(Debug, Clone, Copy)] @@ -35,18 +35,6 @@ pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u } } -/// Convenience: patch with an ABI-encoded `address` (left-padded to 32 bytes). -pub(crate) fn patch_address(bytecode: &mut [u8], refs: &[ImmutableRef], addr: Address) { - let word: B256 = B256::from(U256::from_be_bytes(addr.into_word().0)); - patch_bytes(bytecode, refs, &word.0); -} - -/// Convenience: patch with an ABI-encoded `uint32` (left-padded to 32 bytes). -pub(crate) fn patch_u32(bytecode: &mut [u8], refs: &[ImmutableRef], val: u32) { - let word = B256::from(U256::from(val)); - patch_bytes(bytecode, refs, &word.0); -} - /// Convenience: patch with an ABI-encoded `uint256`. pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) { let word = B256::from(val); @@ -74,45 +62,6 @@ mod tests { assert_eq!(bytecode[42], 0); } - #[test] - fn patch_multiple_refs() { - let mut bytecode = vec![0u8; 128]; - let refs = [ - ImmutableRef { - start: 0, - length: 32, - }, - ImmutableRef { - start: 64, - length: 32, - }, - ]; - let addr = Address::repeat_byte(0xAB); - patch_address(&mut bytecode, &refs, addr); - - // Both locations should have the address (last 20 bytes of the 32-byte word) - assert_eq!(bytecode[12..32], [0xAB; 20]); - assert_eq!(bytecode[76..96], [0xAB; 20]); - // Padding bytes should be zero - assert_eq!(bytecode[0..12], [0u8; 12]); - assert_eq!(bytecode[64..76], [0u8; 12]); - } - - #[test] - fn patch_u32_value() { - let mut bytecode = vec![0u8; 64]; - let refs = [ImmutableRef { - start: 0, - length: 32, - }]; - patch_u32(&mut bytecode, &refs, 1234); - - // uint32 1234 = 0x04D2, left-padded to 32 bytes - assert_eq!(bytecode[30], 0x04); - assert_eq!(bytecode[31], 0xD2); - assert_eq!(bytecode[0..30], [0u8; 30]); - } - #[test] #[should_panic(expected = "immutable ref out of bounds")] fn patch_out_of_bounds_panics() { diff --git a/bin/ev-deployer/src/contracts/mailbox.rs b/bin/ev-deployer/src/contracts/mailbox.rs deleted file mode 100644 index 4f774587..00000000 --- a/bin/ev-deployer/src/contracts/mailbox.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! `Mailbox` bytecode and storage encoding. -//! -//! `Mailbox` is the core Hyperlane messaging hub. It dispatches and processes -//! cross-chain messages. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |-----------------|---------|----------------------| -//! | `deployedBlock` | uint256 | \[930\] | -//! | `localDomain` | uint32 | \[982, 2831, 5985\] | -//! -//! ## Storage layout (from `forge inspect Mailbox storageLayout`) -//! -//! | Slot | Variable | Type | -//! |-------|-----------------------------|-----------| -//! | 0 | `_initialized` + `_initializing` | uint8 + bool | -//! | 1-50 | `__gap` (Initializable) | — | -//! | 51 | `_owner` | address | -//! | 52-100| `__gap` (Ownable) | — | -//! | 101 | `nonce` | uint32 | -//! | 102 | `latestDispatchedId` | bytes32 | -//! | 103 | `defaultIsm` | address | -//! | 104 | `defaultHook` | address | -//! | 105 | `requiredHook` | address | -//! | 106 | `deliveries` (mapping) | — | - -use crate::{ - config::MailboxConfig, - contracts::{ - immutables::{patch_u256, patch_u32, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `Mailbox` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode -/// ``` -const MAILBOX_BYTECODE: &[u8] = &hex!("6080604052600436106101ac5760003560e01c80638da5cb5b116100ec578063e70f48ac1161008a578063f7ccd32111610064578063f7ccd321146105d7578063f8c8765e146105f7578063fa31de0114610617578063ffa1ad741461062a57600080fd5b8063e70f48ac14610577578063f2fde38b14610597578063f794687a146105b757600080fd5b80639c42bd18116100c65780639c42bd18146104ae578063affed0e0146104ce578063d6d08a09146104eb578063e495f1d41461051857600080fd5b80638da5cb5b1461040d57806393c448471461043857806399b048091461048e57600080fd5b80635d1fe5a9116101595780637c39d130116101335780637c39d1301461035d57806381d2ea951461037057806382ea7bfe146103905780638d3638f4146103c457600080fd5b80635d1fe5a9146102d85780636e5f516e1461031b578063715018a61461034857600080fd5b80631426b7f41161018a5780631426b7f4146102515780633d1250b71461027357806348aee8d4146102c557600080fd5b806307a2fda1146101b157806310b83dc01461021a578063134fbb4f1461023b575b600080fd5b3480156101bd57600080fd5b506101fe6101cc366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff1690565b60405165ffffffffffff90911681526020015b60405180910390f35b61022d610228366004611c00565b610651565b604051908152602001610211565b34801561024757600080fd5b5061022d60665481565b34801561025d57600080fd5b5061027161026c366004611c9e565b610925565b005b34801561027f57600080fd5b506068546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b61022d6102d3366004611cbb565b610a45565b3480156102e457600080fd5b506102a06102f3366004611b6a565b6000908152606a602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032757600080fd5b506067546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561035457600080fd5b50610271610a83565b61027161036b366004611d45565b610a97565b34801561037c57600080fd5b5061022d61038b366004611c00565b610f39565b34801561039c57600080fd5b5061022d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103d057600080fd5b506103f87f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610211565b34801561041957600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff166102a0565b34801561044457600080fd5b506104816040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516102119190611e1f565b34801561049a57600080fd5b506102716104a9366004611c9e565b6110cc565b3480156104ba57600080fd5b5061022d6104c9366004611e32565b6111e7565b3480156104da57600080fd5b506065546103f89063ffffffff1681565b3480156104f757600080fd5b506069546102a09073ffffffffffffffffffffffffffffffffffffffff1681565b34801561052457600080fd5b50610567610533366004611b6a565b6000908152606a602052604090205474010000000000000000000000000000000000000000900465ffffffffffff16151590565b6040519015158152602001610211565b34801561058357600080fd5b506102a0610592366004611c9e565b611223565b3480156105a357600080fd5b506102716105b2366004611c9e565b61135a565b3480156105c357600080fd5b506102716105d2366004611c9e565b611411565b3480156105e357600080fd5b5061022d6105f2366004611cbb565b61152c565b34801561060357600080fd5b50610271610612366004611e80565b61155f565b61022d610625366004611e32565b611719565b34801561063657600080fd5b5061063f600381565b60405160ff9091168152602001610211565b600073ffffffffffffffffffffffffffffffffffffffff821661068a5760685473ffffffffffffffffffffffffffffffffffffffff1691505b60006106988989898961174c565b805160208201206066819055606580549293509091600191906000906106c590849063ffffffff16611f0b565b92506101000a81548163ffffffff021916908363ffffffff160217905550888a63ffffffff163373ffffffffffffffffffffffffffffffffffffffff167f769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814856040516107319190611e1f565b60405180910390a460405181907f788dbc1b7152732178210e7f4d9d010ef016f9eafbe66786bd7169f56e0c353a90600090a26069546040517faaccd23000000000000000000000000000000000000000000000000000000000815260009173ffffffffffffffffffffffffffffffffffffffff169063aaccd230906107bf908a908a908890600401611f78565b602060405180830381865afa1580156107dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108009190611fa8565b90508034101561080d5750345b6069546040517f086011b900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063086011b9908390610869908b908b908990600401611f78565b6000604051808303818588803b15801561088257600080fd5b505af1158015610896573d6000803e3d6000fd5b50505050508473ffffffffffffffffffffffffffffffffffffffff1663086011b982346108c39190611fc1565b8989876040518563ffffffff1660e01b81526004016108e493929190611f78565b6000604051808303818588803b1580156108fd57600080fd5b505af1158015610911573d6000803e3d6000fd5b50949e9d5050505050505050505050505050565b61092d611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6109d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f4d61696c626f783a20726571756972656420686f6f6b206e6f7420636f6e747260448201527f616374000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b606980547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f329ec8e2438a73828ecf31a6568d7a91d7b1d79e342b0692914fd053d1a002b190600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610651565b979650505050505050565b610a8b611795565b610a956000611816565b565b6003610aa3838361188d565b60ff1614610b0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d61696c626f783a206261642076657273696f6e00000000000000000000000060448201526064016109cd565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16610b3e83836118b1565b63ffffffff1614610bab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4d61696c626f783a20756e65787065637465642064657374696e6174696f6e0060448201526064016109cd565b6000610bec83838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061178a92505050565b6000818152606a602052604090205490915074010000000000000000000000000000000000000000900465ffffffffffff1615610c85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d61696c626f783a20616c72656164792064656c69766572656400000000000060448201526064016109cd565b6000610c9184846118d4565b90506000610c9e82611223565b60408051808201825233815265ffffffffffff43811660208084019182526000898152606a9091529390932091518254935190911674010000000000000000000000000000000000000000027fffffffffffff000000000000000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff918216179290921790559091508216610d3c86866118ef565b610d468787611908565b63ffffffff167f0d381c2a574ae8f04e213db7cfb4df8df712cdbd427d9868ffef380660ca657460405160405180910390a460405183907f1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a990600090a26040517ff7e83aee00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063f7e83aee90610dfb908a908a908a908a90600401611fd4565b6020604051808303816000875af1158015610e1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e3e9190611ffb565b610ea4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d61696c626f783a2049534d20766572696669636174696f6e206661696c656460448201526064016109cd565b8173ffffffffffffffffffffffffffffffffffffffff166356d5d47534610ecb8888611908565b610ed589896118ef565b610edf8a8a611918565b6040518663ffffffff1660e01b8152600401610efe949392919061201d565b6000604051808303818588803b158015610f1757600080fd5b505af1158015610f2b573d6000803e3d6000fd5b505050505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216610f725760685473ffffffffffffffffffffffffffffffffffffffff1691505b6000610f808989898961174c565b6040517faaccd23000000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff84169063aaccd23090610fd990889088908690600401611f78565b602060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061101a9190611fa8565b6069546040517faaccd23000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063aaccd2309061107490899089908790600401611f78565b602060405180830381865afa158015611091573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b59190611fa8565b6110bf9190612043565b9998505050505050505050565b6110d4611795565b73ffffffffffffffffffffffffffffffffffffffff81163b611178576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f4d61696c626f783a2064656661756c7420686f6f6b206e6f7420636f6e74726160448201527f637400000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517f65a63e5066ee2fcdf9d32a7f1bf7ce71c76066f19d0609dddccd334ab87237d790600090a250565b600061121a858585856111fc86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610f39565b95945050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fde523cf30000000000000000000000000000000000000000000000000000000017905290516000918291829173ffffffffffffffffffffffffffffffffffffffff8616916112a49190612080565b600060405180830381855afa9150503d80600081146112df576040519150601f19603f3d011682016040523d82523d6000602084013e6112e4565b606091505b50915091508180156112f65750805115155b1561133957600081806020019051810190611311919061209c565b905073ffffffffffffffffffffffffffffffffffffffff81161561133757949350505050565b505b505060675473ffffffffffffffffffffffffffffffffffffffff1692915050565b611362611795565b73ffffffffffffffffffffffffffffffffffffffff8116611405576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016109cd565b61140e81611816565b50565b611419611795565b73ffffffffffffffffffffffffffffffffffffffff81163b6114bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d61696c626f783a2064656661756c742049534d206e6f7420636f6e7472616360448201527f740000000000000000000000000000000000000000000000000000000000000060648201526084016109cd565b606780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fa76ad0adbf45318f8633aa0210f711273d50fbb6fef76ed95bbae97082c75daa90600090a250565b6000610a78878787878787606860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16610f39565b600054610100900460ff161580801561157f5750600054600160ff909116105b806115995750303b158015611599575060005460ff166001145b611625576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016109cd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561168357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61168b611934565b61169484611411565b61169d836110cc565b6116a682610925565b6116af8561135a565b801561171257600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b600061121a8585858561172e86808385612056565b60685473ffffffffffffffffffffffffffffffffffffffff16610651565b60655460609061121a9060039063ffffffff167f000000000000000000000000000000000000000000000000000000000000000033898989896119d3565b805160209091012090565b60335473ffffffffffffffffffffffffffffffffffffffff163314610a95576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109cd565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061189c6001828486612056565b6118a5916120b9565b60f81c90505b92915050565b60006118c1602d60298486612056565b6118ca91612101565b60e01c9392505050565b60006118e86118e38484611a11565b611a21565b9392505050565b60006118ff602960098486612056565b6118e891612147565b60006118c1600960058486612056565b36600061192883604d8187612056565b915091505b9250929050565b600054610100900460ff166119cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a95611aca565b606088888888888888886040516020016119f4989796959493929190612183565b604051602081830303815290604052905098975050505050505050565b60006118ff604d602d8486612056565b600073ffffffffffffffffffffffffffffffffffffffff821115611ac6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016109cd565b5090565b600054610100900460ff16611b61576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016109cd565b610a9533611816565b600060208284031215611b7c57600080fd5b5035919050565b803563ffffffff81168114611b9757600080fd5b919050565b60008083601f840112611bae57600080fd5b50813567ffffffffffffffff811115611bc657600080fd5b60208301915083602082850101111561192d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461140e57600080fd5b600080600080600080600060a0888a031215611c1b57600080fd5b611c2488611b83565b965060208801359550604088013567ffffffffffffffff80821115611c4857600080fd5b611c548b838c01611b9c565b909750955060608a0135915080821115611c6d57600080fd5b50611c7a8a828b01611b9c565b9094509250506080880135611c8e81611bde565b8091505092959891949750929550565b600060208284031215611cb057600080fd5b81356118e881611bde565b60008060008060008060808789031215611cd457600080fd5b611cdd87611b83565b955060208701359450604087013567ffffffffffffffff80821115611d0157600080fd5b611d0d8a838b01611b9c565b90965094506060890135915080821115611d2657600080fd5b50611d3389828a01611b9c565b979a9699509497509295939492505050565b60008060008060408587031215611d5b57600080fd5b843567ffffffffffffffff80821115611d7357600080fd5b611d7f88838901611b9c565b90965094506020870135915080821115611d9857600080fd5b50611da587828801611b9c565b95989497509550505050565b60005b83811015611dcc578181015183820152602001611db4565b50506000910152565b60008151808452611ded816020860160208601611db1565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006118e86020830184611dd5565b60008060008060608587031215611e4857600080fd5b611e5185611b83565b935060208501359250604085013567ffffffffffffffff811115611e7457600080fd5b611da587828801611b9c565b60008060008060808587031215611e9657600080fd5b8435611ea181611bde565b93506020850135611eb181611bde565b92506040850135611ec181611bde565b91506060850135611ed181611bde565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff818116838216019080821115611f2857611f28611edc565b5092915050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b604081526000611f8c604083018587611f2f565b8281036020840152611f9e8185611dd5565b9695505050505050565b600060208284031215611fba57600080fd5b5051919050565b818103818111156118ab576118ab611edc565b604081526000611fe8604083018688611f2f565b8281036020840152610a78818587611f2f565b60006020828403121561200d57600080fd5b815180151581146118e857600080fd5b63ffffffff85168152836020820152606060408201526000611f9e606083018486611f2f565b808201808211156118ab576118ab611edc565b6000808585111561206657600080fd5b8386111561207357600080fd5b5050820193919092039150565b60008251612092818460208701611db1565b9190910192915050565b6000602082840312156120ae57600080fd5b81516118e881611bde565b7fff0000000000000000000000000000000000000000000000000000000000000081358181169160018510156120f95780818660010360031b1b83161692505b505092915050565b7fffffffff0000000000000000000000000000000000000000000000000000000081358181169160048510156120f95760049490940360031b84901b1690921692915050565b803560208310156118ab577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7fff000000000000000000000000000000000000000000000000000000000000008960f81b16815260007fffffffff00000000000000000000000000000000000000000000000000000000808a60e01b166001840152808960e01b166005840152876009840152808760e01b1660298401525084602d8301528284604d8401375060009101604d0190815297965050505050505056"); - -// ── Immutable reference offsets (from `forge inspect Mailbox immutableReferences`) ── - -/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. -const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 930, - length: 32, -}]; - -/// `localDomain` (uint32) — from `Mailbox.sol`. -const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 982, - length: 32, - }, - ImmutableRef { - start: 2831, - length: 32, - }, - ImmutableRef { - start: 5985, - length: 32, - }, -]; - -/// Build a genesis alloc entry for `Mailbox`. -pub(crate) fn build(config: &MailboxConfig, local_domain: u32) -> GenesisContract { - let mut bytecode = MAILBOX_BYTECODE.to_vec(); - - // Patch immutables - patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); - patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); - - let mut storage = BTreeMap::new(); - - // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false - storage.insert(B256::ZERO, B256::from(U256::from(1u8))); - - // Slot 51: _owner - if !config.owner.is_zero() { - storage.insert( - B256::from(U256::from(51u64)), - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // Slot 103: defaultIsm - if !config.default_ism.is_zero() { - storage.insert( - B256::from(U256::from(103u64)), - B256::from(U256::from_be_bytes(config.default_ism.into_word().0)), - ); - } - - // Slot 104: defaultHook - if !config.default_hook.is_zero() { - storage.insert( - B256::from(U256::from(104u64)), - B256::from(U256::from_be_bytes(config.default_hook.into_word().0)), - ); - } - - // Slot 105: requiredHook - if !config.required_hook.is_zero() { - storage.insert( - B256::from(U256::from(105u64)), - B256::from(U256::from_be_bytes(config.required_hook.into_word().0)), - ); - } - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex, Address}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> MailboxConfig { - MailboxConfig { - address: address!("0000000000000000000000000000000000001200"), - owner: address!("000000000000000000000000000000000000ad00"), - default_ism: address!("0000000000000000000000000000000000002000"), - default_hook: address!("0000000000000000000000000000000000003000"), - required_hook: address!("0000000000000000000000000000000000004000"), - } - } - - #[test] - fn storage_has_initialized_flag() { - let contract = build(&test_config(), 1234); - assert_eq!( - contract.storage[&B256::ZERO], - B256::from(U256::from(1u8)), - "_initialized should be 1" - ); - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config(), 1234); - let owner_slot = B256::from(U256::from(51u64)); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&owner_slot], expected); - } - - #[test] - fn storage_has_default_ism() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(103u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000002000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn storage_has_default_hook() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(104u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000003000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn storage_has_required_hook() { - let contract = build(&test_config(), 1234); - let slot = B256::from(U256::from(105u64)); - let expected: B256 = "0x0000000000000000000000000000000000000000000000000000000000004000" - .parse() - .unwrap(); - assert_eq!(contract.storage[&slot], expected); - } - - #[test] - fn bytecode_is_patched_with_local_domain() { - let config = test_config(); - let contract = build(&config, 42); - let code = contract.code.to_vec(); - - for &offset in &[982, 2831, 5985] { - let word = &code[offset..offset + 32]; - assert_eq!(word[31], 42, "localDomain not patched at offset {offset}"); - assert_eq!( - word[0..31], - [0u8; 31], - "localDomain padding wrong at offset {offset}" - ); - } - } - - #[test] - fn bytecode_has_zero_deployed_block() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - let word = &code[930..930 + 32]; - assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); - } - - #[test] - fn storage_count_for_standard_config() { - let contract = build(&test_config(), 1234); - // _initialized (0) + _owner (51) + defaultIsm (103) + defaultHook (104) + requiredHook (105) - assert_eq!(contract.storage.len(), 5); - } - - #[test] - fn zero_addresses_omit_slots() { - let config = MailboxConfig { - address: address!("0000000000000000000000000000000000001200"), - owner: Address::ZERO, - default_ism: Address::ZERO, - default_hook: Address::ZERO, - required_hook: Address::ZERO, - }; - let contract = build(&config, 1234); - // Only _initialized (slot 0) should be present - assert_eq!(contract.storage.len(), 1); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(51u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(103u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(104u64)))); - assert!(!contract - .storage - .contains_key(&B256::from(U256::from(105u64)))); - } - - #[test] - #[ignore = "requires forge CLI"] - fn mailbox_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "Mailbox", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(MAILBOX_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "Mailbox bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect Mailbox deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs b/bin/ev-deployer/src/contracts/merkle_tree_hook.rs deleted file mode 100644 index 0723adab..00000000 --- a/bin/ev-deployer/src/contracts/merkle_tree_hook.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! `MerkleTreeHook` bytecode and storage encoding. -//! -//! `MerkleTreeHook` is a Hyperlane post-dispatch hook that maintains an -//! incremental Merkle tree of dispatched message IDs. Validators sign -//! checkpoints against this tree to attest to the messages. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |---------------|---------|---------------------| -//! | `mailbox` | address | \[904, 3300\] | -//! | `localDomain` | uint32 | \[644\] | -//! | `deployedBlock`| uint256| \[578\] | -//! -//! ## Storage layout (from `forge inspect MerkleTreeHook storageLayout`) -//! -//! | Slot | Variable | Type | -//! |------|-----------------------------|---------| -//! | 0 | `_initialized` + `_initializing` | uint8 + bool | -//! | 1-50 | `__gap` (Initializable) | — | -//! | 51 | `_owner` | address | -//! | 52-100| `__gap` (Ownable) | — | -//! | 101 | `hook` | address | -//! | 102 | `_interchainSecurityModule` | address | -//! | 103-150| `__GAP` (MailboxClient) | — | -//! | 151-182| `_tree.branch\[0..31\]` | bytes32\[32\] | -//! | 183 | `_tree.count` | uint256 | - -use crate::{ - config::MerkleTreeHookConfig, - contracts::{ - immutables::{patch_address, patch_u256, patch_u32, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `MerkleTreeHook` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode -/// ``` -const MERKLE_TREE_HOOK_BYTECODE: &[u8] = &hex!("6080604052600436106101445760003560e01c8063907c0f92116100c0578063e445e7dd11610074578063ebf0c71711610059578063ebf0c7171461042c578063f2fde38b14610441578063fd54b2281461046157600080fd5b8063e445e7dd146103d5578063e5320bb9146103fc57600080fd5b8063aaccd230116100a5578063aaccd23014610356578063d5438eae14610376578063de523cf3146103aa57600080fd5b8063907c0f92146102d157806393c448471461030057600080fd5b8063715018a61161011757806382ea7bfe116100fc57806382ea7bfe146102305780638d3638f4146102725780638da5cb5b146102a657600080fd5b8063715018a6146101c95780637f5a7c7b146101de57600080fd5b806306661abd14610149578063086011b9146101745780630e72cc06146101895780633dfd3873146101a9575b600080fd5b34801561015557600080fd5b5060b7545b60405163ffffffff90911681526020015b60405180910390f35b6101876101823660046114c2565b610483565b005b34801561019557600080fd5b506101876101a436600461152e565b610530565b3480156101b557600080fd5b506101876101c436600461152e565b610679565b3480156101d557600080fd5b506101876107ba565b3480156101ea57600080fd5b5060655461020b9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161016b565b34801561023c57600080fd5b506102647f000000000000000000000000000000000000000000000000000000000000000081565b60405190815260200161016b565b34801561027e57600080fd5b5061015a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102b257600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156102dd57600080fd5b506102e66107ce565b6040805192835263ffffffff90911660208301520161016b565b34801561030c57600080fd5b506103496040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161016b919061156b565b34801561036257600080fd5b506102646103713660046114c2565b6107f6565b34801561038257600080fd5b5061020b7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103b657600080fd5b5060665473ffffffffffffffffffffffffffffffffffffffff1661020b565b3480156103e157600080fd5b506103ea610899565b60405160ff909116815260200161016b565b34801561040857600080fd5b5061041c6104173660046115d8565b6108a3565b604051901515815260200161016b565b34801561043857600080fd5b506102646108c8565b34801561044d57600080fd5b5061018761045c36600461152e565b6108d4565b34801561046d57600080fd5b5061047661098b565b60405161016b919061161a565b61048d84846108a3565b61051e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b61052a848484846109da565b50505050565b8073ffffffffffffffffffffffffffffffffffffffff81163b15158061056a575073ffffffffffffffffffffffffffffffffffffffff8116155b6105f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b6105fe610b78565b606680547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527fc47cbcc588c67679e52261c45cc315e56562f8d0ccaba16facb9093ff9498799906020015b60405180910390a15050565b8073ffffffffffffffffffffffffffffffffffffffff81163b1515806106b3575073ffffffffffffffffffffffffffffffffffffffff8116155b61073f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f4d61696c626f78436c69656e743a20696e76616c696420636f6e74726163742060448201527f73657474696e67000000000000000000000000000000000000000000000000006064820152608401610515565b610747610b78565b606580547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091556040519081527f4eab7b127c764308788622363ad3e9532de3dfba7845bd4f84c125a22544255a9060200161066d565b6107c2610b78565b6107cc6000610bf9565b565b6000806107d96108c8565b60016107e460b75490565b6107ee919061168c565b915091509091565b600061080285856108a3565b61088e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e7400000000000000000000000000006064820152608401610515565b600095945050505050565b600060035b905090565b60008115806108bf575060016108b98484610c70565b61ffff16145b90505b92915050565b600061089e6097610cc1565b6108dc610b78565b73ffffffffffffffffffffffffffffffffffffffff811661097f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610515565b61098881610bf9565b50565b61099361143a565b60408051610440810180835290916097918391820190839060209082845b8154815260200190600101908083116109b1575050509183525050602091820154910152919050565b3415610a68576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4d65726b6c6554726565486f6f6b3a206e6f2076616c7565206578706563746560448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610515565b6000610aa983838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610cd492505050565b9050610ab481610cdf565b610b1a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6d657373616765206e6f74206469737061746368696e670000000000000000006044820152606401610515565b6000610b2560b75490565b9050610b32609783610d78565b6040805183815263ffffffff831660208201527f253a3a04cab70d47c1504809242d9350cd81627b4f1d50753e159cf8cd76ed33910160405180910390a1505050505050565b60335473ffffffffffffffffffffffffffffffffffffffff1633146107cc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610515565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000610c7d8160026116b0565b60ff16821015610c8f575060006108c2565b82600083610c9e8260026116b0565b60ff1692610cae939291906116c9565b610cb7916116f3565b60f01c9392505050565b60006108c282610ccf610eb2565b611373565b805160209091012090565b6000817f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663134fbb4f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d4d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d719190611739565b1492915050565b6001610d8660206002611872565b610d90919061187e565b826020015410610dfc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6d65726b6c6520747265652066756c6c000000000000000000000000000000006044820152606401610515565b6001826020016000828254610e119190611891565b9091555050602082015460005b6020811015610ea45781600116600103610e4d5782848260208110610e4557610e456118a4565b015550505050565b838160208110610e5f57610e5f6118a4565b01546040805160208101929092528101849052606001604051602081830303815290604052805190602001209250600282610e9a91906118d3565b9150600101610e1e565b50610ead61190e565b505050565b610eba61145a565b600081527fad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb560208201527fb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3060408201527f21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba8560608201527fe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a1934460808201527f0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d60a08201527f887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a196860c08201527fffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f8360e08201527f9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af6101008201527fcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e06101208201527ff9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a56101408201527ff8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8926101608201527f3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c6101808201527fc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb6101a08201527f5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc6101c08201527fda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d26101e08201527f2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f6102008201527fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a6102208201527f5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a06102408201527fb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa06102608201527fc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e26102808201527ff4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd96102a08201527f5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3776102c08201527f4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee6526102e08201527fcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef6103008201527f0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6103208201527fb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d06103408201527f838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e6103608201527f662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e6103808201527f388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea3226103a08201527f93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7356103c08201527f8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a96103e082015290565b6020820154600090815b602081101561143257600182821c1660008683602081106113a0576113a06118a4565b01549050816001036113dd576040805160208101839052908101869052606001604051602081830303815290604052805190602001209450611428565b848684602081106113f0576113f06118a4565b602002015160405160200161140f929190918252602082015260400190565b6040516020818303038152906040528051906020012094505b505060010161137d565b505092915050565b604051806040016040528061144d61145a565b8152602001600081525090565b6040518061040001604052806020906020820280368337509192915050565b60008083601f84011261148b57600080fd5b50813567ffffffffffffffff8111156114a357600080fd5b6020830191508360208285010111156114bb57600080fd5b9250929050565b600080600080604085870312156114d857600080fd5b843567ffffffffffffffff808211156114f057600080fd5b6114fc88838901611479565b9096509450602087013591508082111561151557600080fd5b5061152287828801611479565b95989497509550505050565b60006020828403121561154057600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461156457600080fd5b9392505050565b60006020808352835180602085015260005b818110156115995785810183015185820160400152820161157d565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b600080602083850312156115eb57600080fd5b823567ffffffffffffffff81111561160257600080fd5b61160e85828601611479565b90969095509350505050565b81516104208201908260005b60208082106116355750611649565b835183529283019290910190600101611626565b505050602083015161040083015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b63ffffffff8281168282160390808211156116a9576116a961165d565b5092915050565b60ff81811683821601908111156108c2576108c261165d565b600080858511156116d957600080fd5b838611156116e657600080fd5b5050820193919092039150565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156114325760029490940360031b84901b1690921692915050565b60006020828403121561174b57600080fd5b5051919050565b600181815b808511156117ab57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156117915761179161165d565b8085161561179e57918102915b93841c9390800290611757565b509250929050565b6000826117c2575060016108c2565b816117cf575060006108c2565b81600181146117e557600281146117ef5761180b565b60019150506108c2565b60ff8411156118005761180061165d565b50506001821b6108c2565b5060208310610133831016604e8410600b841016171561182e575081810a6108c2565b6118388383611752565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561186a5761186a61165d565b029392505050565b60006108bf83836117b3565b818103818111156108c2576108c261165d565b808201808211156108c2576108c261165d565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082611909577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd"); - -// ── Immutable reference offsets (from `forge inspect MerkleTreeHook immutableReferences`) ── - -/// `deployedBlock` (uint256) — from `Indexed.sol`. Set to 0 for genesis contracts. -const DEPLOYED_BLOCK_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 578, - length: 32, -}]; - -/// `mailbox` (address) — from `MailboxClient.sol`. -const MAILBOX_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 904, - length: 32, - }, - ImmutableRef { - start: 3300, - length: 32, - }, -]; - -/// `localDomain` (uint32) — from `MailboxClient.sol`. -const LOCAL_DOMAIN_REFS: &[ImmutableRef] = &[ImmutableRef { - start: 644, - length: 32, -}]; - -/// Build a genesis alloc entry for `MerkleTreeHook`. -pub(crate) fn build(config: &MerkleTreeHookConfig, local_domain: u32) -> GenesisContract { - let mut bytecode = MERKLE_TREE_HOOK_BYTECODE.to_vec(); - - // Patch immutables - patch_address(&mut bytecode, MAILBOX_REFS, config.mailbox); - patch_u32(&mut bytecode, LOCAL_DOMAIN_REFS, local_domain); - patch_u256(&mut bytecode, DEPLOYED_BLOCK_REFS, U256::ZERO); - - let mut storage = BTreeMap::new(); - - // Slot 0: _initialized = 1 (OZ v4 Initializable), _initializing = false - // byte layout: [_initialized (1 byte)] [_initializing (1 byte)] [... 30 zero bytes] - // Packed at slot 0: value = 0x01 (just _initialized = 1) - storage.insert(B256::ZERO, B256::from(U256::from(1u8))); - - // Slot 51: _owner — set the owner so the contract can be administered post-genesis - if !config.owner.is_zero() { - storage.insert( - B256::from(U256::from(51u64)), - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // All other storage starts at zero: - // - hook (slot 101): zero = use mailbox default - // - _interchainSecurityModule (slot 102): zero = use mailbox default - // - _tree.branch[0..31] (slots 151-182): all zero (empty tree) - // - _tree.count (slot 183): 0 - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex, Address}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> MerkleTreeHookConfig { - MerkleTreeHookConfig { - address: address!("0000000000000000000000000000000000001100"), - owner: address!("000000000000000000000000000000000000ad00"), - mailbox: address!("0000000000000000000000000000000000001200"), - } - } - - #[test] - fn storage_has_initialized_flag() { - let contract = build(&test_config(), 1234); - assert_eq!( - contract.storage[&B256::ZERO], - B256::from(U256::from(1u8)), - "_initialized should be 1" - ); - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config(), 1234); - let owner_slot = B256::from(U256::from(51u64)); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&owner_slot], expected); - } - - #[test] - fn zero_owner_omits_slot() { - let config = MerkleTreeHookConfig { - address: address!("0000000000000000000000000000000000001100"), - owner: Address::ZERO, - mailbox: address!("0000000000000000000000000000000000001200"), - }; - let contract = build(&config, 1234); - let owner_slot = B256::from(U256::from(51u64)); - assert!( - !contract.storage.contains_key(&owner_slot), - "zero owner should not produce a storage entry" - ); - } - - #[test] - fn bytecode_is_patched_with_mailbox() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - // Check that mailbox address is patched at both offsets - for &offset in &[904, 3300] { - let word = &code[offset..offset + 32]; - // Address is in the last 20 bytes of the 32-byte word - let addr_bytes = &word[12..32]; - assert_eq!( - addr_bytes, - config.mailbox.as_slice(), - "mailbox not patched at offset {offset}" - ); - } - } - - #[test] - fn bytecode_is_patched_with_local_domain() { - let config = test_config(); - let contract = build(&config, 42); - let code = contract.code.to_vec(); - - let word = &code[644..644 + 32]; - // uint32(42) in big-endian, left-padded: ...00 00 00 2a - assert_eq!(word[31], 42); - assert_eq!(word[0..31], [0u8; 31]); - } - - #[test] - fn bytecode_has_zero_deployed_block() { - let config = test_config(); - let contract = build(&config, 1234); - let code = contract.code.to_vec(); - - let word = &code[578..578 + 32]; - assert_eq!(word, &[0u8; 32], "deployedBlock should be 0 at genesis"); - } - - #[test] - fn only_two_storage_slots_for_standard_config() { - let contract = build(&test_config(), 1234); - // Should have exactly 2 storage entries: _initialized (slot 0) and _owner (slot 51) - assert_eq!(contract.storage.len(), 2); - } - - #[test] - #[ignore = "requires forge CLI"] - fn merkle_tree_hook_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "MerkleTreeHook", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(MERKLE_TREE_HOOK_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "MerkleTreeHook bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect MerkleTreeHook deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index 72b5fcae..9e61b58f 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -1,13 +1,8 @@ //! Contract bytecode and storage encoding. pub(crate) mod admin_proxy; -pub(crate) mod fee_vault; pub(crate) mod immutables; -pub(crate) mod mailbox; -pub(crate) mod merkle_tree_hook; -pub(crate) mod noop_ism; pub(crate) mod permit2; -pub(crate) mod protocol_fee; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/noop_ism.rs b/bin/ev-deployer/src/contracts/noop_ism.rs deleted file mode 100644 index 9f17efe0..00000000 --- a/bin/ev-deployer/src/contracts/noop_ism.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! `NoopIsm` bytecode encoding. -//! -//! `NoopIsm` is a Hyperlane Interchain Security Module (ISM) that accepts all -//! messages without verification — `verify` always returns `true`. -//! -//! ## Immutables -//! -//! None. -//! -//! ## Storage layout -//! -//! None. - -use crate::{config::NoopIsmConfig, contracts::GenesisContract}; -use alloy_primitives::{hex, Bytes}; -use std::collections::BTreeMap; - -/// `NoopIsm` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode -/// ``` -const NOOP_ISM_BYTECODE: &[u8] = &hex!("608060405234801561001057600080fd5b50600436106100415760003560e01c80636465e69f1461004657806393c4484714610065578063f7e83aee146100ae575b600080fd5b61004e600681565b60405160ff90911681526020015b60405180910390f35b6100a16040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b60405161005c91906100d6565b6100c66100bc36600461018c565b6001949350505050565b604051901515815260200161005c565b60006020808352835180602085015260005b81811015610104578581018301518582016040015282016100e8565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008083601f84011261015557600080fd5b50813567ffffffffffffffff81111561016d57600080fd5b60208301915083602082850101111561018557600080fd5b9250929050565b600080600080604085870312156101a257600080fd5b843567ffffffffffffffff808211156101ba57600080fd5b6101c688838901610143565b909650945060208701359150808211156101df57600080fd5b506101ec87828801610143565b9598949750955050505056"); - -/// Build a genesis alloc entry for `NoopIsm`. -pub(crate) fn build(config: &NoopIsmConfig) -> GenesisContract { - GenesisContract { - address: config.address, - code: Bytes::from(NOOP_ISM_BYTECODE.to_vec()), - storage: BTreeMap::new(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> NoopIsmConfig { - NoopIsmConfig { - address: address!("0000000000000000000000000000000000001300"), - } - } - - #[test] - fn storage_is_empty() { - let contract = build(&test_config()); - assert!( - contract.storage.is_empty(), - "NoopIsm should have no storage" - ); - } - - #[test] - fn bytecode_is_present() { - let contract = build(&test_config()); - assert!( - !contract.code.is_empty(), - "NoopIsm should have non-empty bytecode" - ); - } - - #[test] - #[ignore = "requires forge CLI"] - fn noop_ism_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "NoopIsm", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(NOOP_ISM_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "NoopIsm bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect NoopIsm deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/contracts/protocol_fee.rs b/bin/ev-deployer/src/contracts/protocol_fee.rs deleted file mode 100644 index d7b2135c..00000000 --- a/bin/ev-deployer/src/contracts/protocol_fee.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! `ProtocolFee` bytecode and storage encoding. -//! -//! `ProtocolFee` is a Hyperlane post-dispatch hook that charges a protocol fee -//! on message dispatches. -//! -//! ## Immutables (in bytecode, not storage) -//! -//! | Variable | Type | Offsets | -//! |-------------------|---------|-----------------| -//! | `MAX_PROTOCOL_FEE`| uint256 | \[655, 2248\] | -//! -//! ## Storage layout (from `forge inspect ProtocolFee storageLayout`) -//! -//! | Slot | Variable | Type | -//! |------|---------------|---------| -//! | 0 | `_owner` | address | -//! | 1 | `protocolFee` | uint256 | -//! | 2 | `beneficiary` | address | - -use crate::{ - config::ProtocolFeeConfig, - contracts::{ - immutables::{patch_u256, ImmutableRef}, - GenesisContract, - }, -}; -use alloy_primitives::{hex, Bytes, B256, U256}; -use std::collections::BTreeMap; - -/// `ProtocolFee` runtime bytecode compiled with Hyperlane v11.0.3, -/// solc 0.8.22 (Foundry `ci` profile: `cbor_metadata=false`, `bytecode_hash="none"`). -/// -/// Compiled with placeholder immutables (all zeros). Actual values are patched -/// at genesis time via [`build`]. -/// -/// Regenerate with: -/// ```sh -/// cd contracts/lib/hyperlane-monorepo/solidity && \ -/// forge soldeer install && \ -/// FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode -/// ``` -const PROTOCOL_FEE_BYTECODE: &[u8] = &hex!("6080604052600436106100dd5760003560e01c8063a1af5b9a1161007f578063b8ca3b8311610059578063b8ca3b831461027d578063e445e7dd146102b1578063e5320bb9146102cd578063f2fde38b146102fd57600080fd5b8063a1af5b9a14610224578063aaccd23014610239578063b0e21e8a1461026757600080fd5b8063715018a6116100bb578063715018a61461016e578063787dce3d146101835780638da5cb5b146101a357806393c44847146101ce57600080fd5b8063086011b9146100e25780631c31f710146100f757806338af3eed14610117575b600080fd5b6100f56100f0366004610e0a565b61031d565b005b34801561010357600080fd5b506100f5610112366004610e76565b6103ca565b34801561012357600080fd5b506002546101449073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561017a57600080fd5b506100f56103de565b34801561018f57600080fd5b506100f561019e366004610eac565b6103f2565b3480156101af57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610144565b3480156101da57600080fd5b506102176040518060400160405280600681526020017f31312e302e33000000000000000000000000000000000000000000000000000081525081565b6040516101659190610ec5565b34801561023057600080fd5b506100f5610403565b34801561024557600080fd5b50610259610254366004610e0a565b610426565b604051908152602001610165565b34801561027357600080fd5b5061025960015481565b34801561028957600080fd5b506102597f000000000000000000000000000000000000000000000000000000000000000081565b3480156102bd57600080fd5b5060405160088152602001610165565b3480156102d957600080fd5b506102ed6102e8366004610f32565b6104ca565b6040519015158152602001610165565b34801561030957600080fd5b506100f5610318366004610e76565b61051e565b61032784846104ca565b6103b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084015b60405180910390fd5b6103c4848484846105d2565b50505050565b6103d26106d9565b6103db8161075a565b50565b6103e66106d9565b6103f06000610851565b565b6103fa6106d9565b6103db816108c6565b6002546103f09073ffffffffffffffffffffffffffffffffffffffff16476109ab565b600061043285856104ca565b6104be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4162737472616374506f73744469737061746368486f6f6b3a20696e76616c6960448201527f64206d657461646174612076617269616e74000000000000000000000000000060648201526084016103af565b60015495945050505050565b6000806001541180156104fe575060006104e5848483610b0a565b73ffffffffffffffffffffffffffffffffffffffff1614155b1561050b57506000610518565b6105158383610b5e565b90505b92915050565b6105266106d9565b73ffffffffffffffffffffffffffffffffffffffff81166105c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016103af565b6103db81610851565b600154341015610664576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f50726f746f636f6c4665653a20696e73756666696369656e742070726f746f6360448201527f6f6c20666565000000000000000000000000000000000000000000000000000060648201526084016103af565b61066e8282610b80565b73ffffffffffffffffffffffffffffffffffffffff167fb87e607f6030a23ed9b7dac1a717610f3a3b07325269f18808ba763bdcefe7ae6001546040516106b791815260200190565b60405180910390a26103c484848484600154346106d49190610fa3565b610b94565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103f0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016103af565b73ffffffffffffffffffffffffffffffffffffffff81166107d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f50726f746f636f6c4665653a20696e76616c69642062656e656669636961727960448201526064016103af565b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040519081527f04d55a8be181fb8d75b76f2d48aa0b2ee40f47e53d6e61763eeeec46feea8a24906020015b60405180910390a150565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b7f0000000000000000000000000000000000000000000000000000000000000000811115610976576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f746f636f6c4665653a2065786365656473206d61782070726f746f636f60448201527f6c2066656500000000000000000000000000000000000000000000000000000060648201526084016103af565b60018190556040518181527fdb5aafdb29539329e37d4e3ee869bc4031941fd55a5dfc92824fbe34b204e30d90602001610846565b80471015610a15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016103af565b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114610a6f576040519150601f19603f3d011682016040523d82523d6000602084013e610a74565b606091505b5050905080610b05576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016103af565b505050565b6000610b1860566014610fb6565b60ff16831015610b29575080610b57565b83605684610b38826014610fb6565b60ff1692610b4893929190610fcf565b610b5191610ff9565b60601c90505b9392505050565b600081158061051557506001610b748484610c80565b61ffff16149392505050565b6000610515610b8f8484610cd1565b610cea565b8015610c79576000610bb2610ba98585610b80565b87908790610d93565b905073ffffffffffffffffffffffffffffffffffffffff8116610c57576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f4162737472616374506f73744469737061746368486f6f6b3a206e6f2072656660448201527f756e64206164647265737300000000000000000000000000000000000000000060648201526084016103af565b610c7773ffffffffffffffffffffffffffffffffffffffff8216836109ab565b505b5050505050565b6000610c8d816002610fb6565b60ff16821015610c9f57506000610518565b82600083610cae826002610fb6565b60ff1692610cbe93929190610fcf565b610cc791611041565b60f01c9392505050565b6000610ce1602960098486610fcf565b61051591611087565b600073ffffffffffffffffffffffffffffffffffffffff821115610d8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f5479706543617374733a2062797465733332546f41646472657373206f76657260448201527f666c6f770000000000000000000000000000000000000000000000000000000060648201526084016103af565b5090565b6000610da160426014610fb6565b60ff16831015610db2575080610b57565b83604284610b38826014610fb6565b60008083601f840112610dd357600080fd5b50813567ffffffffffffffff811115610deb57600080fd5b602083019150836020828501011115610e0357600080fd5b9250929050565b60008060008060408587031215610e2057600080fd5b843567ffffffffffffffff80821115610e3857600080fd5b610e4488838901610dc1565b90965094506020870135915080821115610e5d57600080fd5b50610e6a87828801610dc1565b95989497509550505050565b600060208284031215610e8857600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610b5757600080fd5b600060208284031215610ebe57600080fd5b5035919050565b60006020808352835180602085015260005b81811015610ef357858101830151858201604001528201610ed7565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b60008060208385031215610f4557600080fd5b823567ffffffffffffffff811115610f5c57600080fd5b610f6885828601610dc1565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561051857610518610f74565b60ff818116838216019081111561051857610518610f74565b60008085851115610fdf57600080fd5b83861115610fec57600080fd5b5050820193919092039150565b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000081358181169160148510156110395780818660140360031b1b83161692505b505092915050565b7fffff00000000000000000000000000000000000000000000000000000000000081358181169160028510156110395760029490940360031b84901b1690921692915050565b80356020831015610518577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b169291505056"); - -// ── Immutable reference offsets (from `forge inspect ProtocolFee immutableReferences`) ── - -/// `MAX_PROTOCOL_FEE` (uint256) — maximum fee that can be set. -const MAX_PROTOCOL_FEE_REFS: &[ImmutableRef] = &[ - ImmutableRef { - start: 655, - length: 32, - }, - ImmutableRef { - start: 2248, - length: 32, - }, -]; - -/// Build a genesis alloc entry for `ProtocolFee`. -pub(crate) fn build(config: &ProtocolFeeConfig) -> GenesisContract { - let mut bytecode = PROTOCOL_FEE_BYTECODE.to_vec(); - - // Patch immutables - patch_u256( - &mut bytecode, - MAX_PROTOCOL_FEE_REFS, - U256::from(config.max_protocol_fee), - ); - - let mut storage = BTreeMap::new(); - - // Slot 0: _owner - if !config.owner.is_zero() { - storage.insert( - B256::ZERO, - B256::from(U256::from_be_bytes(config.owner.into_word().0)), - ); - } - - // Slot 1: protocolFee - if config.protocol_fee > 0 { - storage.insert( - B256::from(U256::from(1u64)), - B256::from(U256::from(config.protocol_fee)), - ); - } - - // Slot 2: beneficiary - if !config.beneficiary.is_zero() { - storage.insert( - B256::from(U256::from(2u64)), - B256::from(U256::from_be_bytes(config.beneficiary.into_word().0)), - ); - } - - GenesisContract { - address: config.address, - code: Bytes::from(bytecode), - storage, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, hex}; - use std::{path::PathBuf, process::Command}; - - fn test_config() -> ProtocolFeeConfig { - ProtocolFeeConfig { - address: address!("0000000000000000000000000000000000001300"), - owner: address!("000000000000000000000000000000000000ad00"), - max_protocol_fee: 1_000_000_000_000_000_000, - protocol_fee: 100_000, - beneficiary: address!("000000000000000000000000000000000000be00"), - } - } - - #[test] - fn storage_has_owner() { - let contract = build(&test_config()); - let expected: B256 = "0x000000000000000000000000000000000000000000000000000000000000Ad00" - .parse() - .unwrap(); - assert_eq!(contract.storage[&B256::ZERO], expected); - } - - #[test] - fn bytecode_is_patched_with_max_protocol_fee() { - let config = test_config(); - let contract = build(&config); - let code = contract.code.to_vec(); - - let expected = B256::from(U256::from(config.max_protocol_fee)); - for &offset in &[655, 2248] { - let word = &code[offset..offset + 32]; - assert_eq!( - word, - expected.as_slice(), - "max_protocol_fee not patched at offset {offset}" - ); - } - } - - #[test] - fn zero_protocol_fee_omits_slot_1() { - let config = ProtocolFeeConfig { - address: address!("0000000000000000000000000000000000001300"), - owner: address!("000000000000000000000000000000000000ad00"), - max_protocol_fee: 1_000_000_000_000_000_000, - protocol_fee: 0, - beneficiary: address!("000000000000000000000000000000000000be00"), - }; - let contract = build(&config); - let fee_slot = B256::from(U256::from(1u64)); - assert!( - !contract.storage.contains_key(&fee_slot), - "zero protocol_fee should not produce a storage entry" - ); - } - - #[test] - fn storage_count_for_standard_config() { - let contract = build(&test_config()); - // Should have exactly 3 storage entries: _owner (slot 0), protocolFee (slot 1), beneficiary (slot 2) - assert_eq!(contract.storage.len(), 3); - } - - #[test] - #[ignore = "requires forge CLI"] - fn protocol_fee_bytecode_matches_solidity_source() { - let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .join("contracts") - .join("lib") - .join("hyperlane-monorepo") - .join("solidity"); - - let output = Command::new("forge") - .args(["inspect", "ProtocolFee", "deployedBytecode", "--root"]) - .arg(&contracts_root) - .env("FOUNDRY_PROFILE", "ci") - .output() - .expect("forge not found"); - - assert!( - output.status.success(), - "forge inspect failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - - let forge_hex = String::from_utf8(output.stdout) - .unwrap() - .trim() - .strip_prefix("0x") - .unwrap() - .to_lowercase(); - - let hardcoded_hex = hex::encode(PROTOCOL_FEE_BYTECODE); - - assert_eq!( - forge_hex, hardcoded_hex, - "ProtocolFee bytecode mismatch! Regenerate with: \ - cd contracts/lib/hyperlane-monorepo/solidity && \ - FOUNDRY_PROFILE=ci forge inspect ProtocolFee deployedBytecode" - ); - } -} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index c3d7e926..e270b697 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -17,38 +17,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } - if let Some(ref fv_config) = config.contracts.fee_vault { - let contract = contracts::fee_vault::build(fv_config); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref mth_config) = config.contracts.merkle_tree_hook { - let local_domain = config.chain.chain_id as u32; - let contract = contracts::merkle_tree_hook::build(mth_config, local_domain); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref mb_config) = config.contracts.mailbox { - let local_domain = config.chain.chain_id as u32; - let contract = contracts::mailbox::build(mb_config, local_domain); - insert_contract(&mut alloc, &contract); - } - - if let Some(ref ni_config) = config.contracts.noop_ism { - let contract = contracts::noop_ism::build(ni_config); - insert_contract(&mut alloc, &contract); - } - if let Some(ref p2_config) = config.contracts.permit2 { let contract = contracts::permit2::build(p2_config, config.chain.chain_id); insert_contract(&mut alloc, &contract); } - if let Some(ref pf_config) = config.contracts.protocol_fee { - let contract = contracts::protocol_fee::build(pf_config); - insert_contract(&mut alloc, &contract); - } - Value::Object(alloc) } @@ -134,12 +107,7 @@ mod tests { address: address!("000000000000000000000000000000000000ad00"), owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), }), - fee_vault: None, - merkle_tree_hook: None, - mailbox: None, - noop_ism: None, permit2: None, - protocol_fee: None, }, } } diff --git a/bin/ev-deployer/src/init_template.toml b/bin/ev-deployer/src/init_template.toml index 97f8eeb9..e44eb6fb 100644 --- a/bin/ev-deployer/src/init_template.toml +++ b/bin/ev-deployer/src/init_template.toml @@ -15,44 +15,6 @@ chain_id = 0 # address = "0x000000000000000000000000000000000000Ad00" # owner = "0x..." -# FeeVault: fee vault with Hyperlane bridging support. -# [contracts.fee_vault] -# address = "0x000000000000000000000000000000000000FE00" -# owner = "0x..." -# destination_domain = 0 -# recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000" -# minimum_amount = 0 -# call_fee = 0 -# bridge_share_bps = 10000 -# other_recipient = "0x0000000000000000000000000000000000000000" -# hyp_native_minter = "0x0000000000000000000000000000000000000000" - -# Mailbox: Hyperlane core messaging hub. -# [contracts.mailbox] -# address = "0x0000000000000000000000000000000000001200" -# owner = "0x..." -# default_ism = "0x0000000000000000000000000000000000000000" -# default_hook = "0x0000000000000000000000000000000000000000" -# required_hook = "0x0000000000000000000000000000000000000000" - -# MerkleTreeHook: Hyperlane required hook (Merkle tree for messages). -# [contracts.merkle_tree_hook] -# address = "0x0000000000000000000000000000000000001100" -# owner = "0x..." -# mailbox = "0x0000000000000000000000000000000000001200" - -# NoopIsm: Hyperlane ISM that accepts all messages. -# [contracts.noop_ism] -# address = "0x0000000000000000000000000000000000001300" - # Permit2: Uniswap canonical token approval manager. # [contracts.permit2] # address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" - -# ProtocolFee: Hyperlane post-dispatch hook that charges a protocol fee. -# [contracts.protocol_fee] -# address = "0x0000000000000000000000000000000000001400" -# owner = "0x..." -# max_protocol_fee = 1000000000000000000 -# protocol_fee = 0 -# beneficiary = "0x..." diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index 8efe0d0c..5d87ae9b 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -119,42 +119,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("admin_proxy not configured"))?, - "fee_vault" => cfg - .contracts - .fee_vault - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("fee_vault not configured"))?, - "merkle_tree_hook" => cfg - .contracts - .merkle_tree_hook - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, - "mailbox" => cfg - .contracts - .mailbox - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("mailbox not configured"))?, - "noop_ism" => cfg - .contracts - .noop_ism - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("noop_ism not configured"))?, "permit2" => cfg .contracts .permit2 .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, - "protocol_fee" => cfg - .contracts - .protocol_fee - .as_ref() - .map(|c| c.address) - .ok_or_else(|| eyre::eyre!("protocol_fee not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 5e97dd4d..f683377a 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -14,34 +14,6 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - if let Some(ref fv) = config.contracts.fee_vault { - manifest.insert( - "fee_vault".to_string(), - Value::String(format!("{}", fv.address)), - ); - } - - if let Some(ref mth) = config.contracts.merkle_tree_hook { - manifest.insert( - "merkle_tree_hook".to_string(), - Value::String(format!("{}", mth.address)), - ); - } - - if let Some(ref mb) = config.contracts.mailbox { - manifest.insert( - "mailbox".to_string(), - Value::String(format!("{}", mb.address)), - ); - } - - if let Some(ref ni) = config.contracts.noop_ism { - manifest.insert( - "noop_ism".to_string(), - Value::String(format!("{}", ni.address)), - ); - } - if let Some(ref p2) = config.contracts.permit2 { manifest.insert( "permit2".to_string(), @@ -49,12 +21,5 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } - if let Some(ref pf) = config.contracts.protocol_fee { - manifest.insert( - "protocol_fee".to_string(), - Value::String(format!("{}", pf.address)), - ); - } - Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index b1f318b3..b7c085ae 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -79,10 +79,6 @@ echo "Genesis written to $GENESIS" # Quick sanity: address should be in the alloc grep -qi "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" -grep -qi "000000000000000000000000000000000000FE00" "$GENESIS" \ - || fail "FeeVault address not found in genesis" -grep -qi "0000000000000000000000000000000000001100" "$GENESIS" \ - || fail "MerkleTreeHook address not found in genesis" grep -qi "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ || fail "Permit2 address not found in genesis" @@ -131,117 +127,7 @@ expected_owner_slot="0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cff || fail "AdminProxy slot 0 (owner) mismatch: got $admin_slot0, expected $expected_owner_slot" pass "AdminProxy owner slot 0 = $ADMIN_OWNER" -# ── Step 5: Verify FeeVault ────────────────────────────── - -FEE_VAULT="0x000000000000000000000000000000000000FE00" -FEE_VAULT_OWNER="0x000000000000000000000000000000000000Ad00" - -echo "=== Verifying FeeVault at $FEE_VAULT ===" - -# Check code is present -fv_code=$(rpc_call "eth_getCode" "[\"$FEE_VAULT\", \"latest\"]") -[[ "$fv_code" != "0x" && "$fv_code" != "0x0" && ${#fv_code} -gt 10 ]] \ - || fail "FeeVault has no bytecode (got: $fv_code)" -pass "FeeVault has bytecode (${#fv_code} hex chars)" - -# Slot 0: hypNativeMinter (should be zero) -fv_slot0=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x0\", \"latest\"]") -expected_zero="0x0000000000000000000000000000000000000000000000000000000000000000" -[[ "$(echo "$fv_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_zero" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 0 (hypNativeMinter) should be zero, got $fv_slot0" -pass "FeeVault slot 0 (hypNativeMinter) = zero" - -# Slot 1: owner (lower 160 bits) + destinationDomain (upper bits) -# With domain=0 and owner=0x...Ad00, it's just the owner padded -fv_slot1=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x1\", \"latest\"]") -expected_slot1="0x000000000000000000000000000000000000000000000000000000000000ad00" -[[ "$(echo "$fv_slot1" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot1" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 1 (owner|domain) mismatch: got $fv_slot1, expected $expected_slot1" -pass "FeeVault slot 1 (owner|domain) correct" - -# Slot 6: bridgeShareBps = 10000 = 0x2710 -fv_slot6=$(rpc_call "eth_getStorageAt" "[\"$FEE_VAULT\", \"0x6\", \"latest\"]") -expected_slot6="0x0000000000000000000000000000000000000000000000000000000000002710" -[[ "$(echo "$fv_slot6" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_slot6" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" -pass "FeeVault slot 6 (bridgeShareBps) = 10000" - -# ── Step 6: Verify MerkleTreeHook ──────────────────────── - -MERKLE_TREE_HOOK="0x0000000000000000000000000000000000001100" -MERKLE_TREE_HOOK_OWNER="0x000000000000000000000000000000000000Ad00" -MERKLE_TREE_HOOK_MAILBOX="0x0000000000000000000000000000000000001200" - -echo "=== Verifying MerkleTreeHook at $MERKLE_TREE_HOOK ===" - -# Check code is present -mth_code=$(rpc_call "eth_getCode" "[\"$MERKLE_TREE_HOOK\", \"latest\"]") -[[ "$mth_code" != "0x" && "$mth_code" != "0x0" && ${#mth_code} -gt 10 ]] \ - || fail "MerkleTreeHook has no bytecode (got: $mth_code)" -pass "MerkleTreeHook has bytecode (${#mth_code} hex chars)" - -# Compare full bytecode against genesis JSON -# Extract expected code from genesis for the MerkleTreeHook address -expected_mth_code=$(python3 -c " -import json, sys -with open('$GENESIS') as f: - genesis = json.load(f) -alloc = genesis['alloc'] -# Address key is checksummed without 0x prefix -entry = alloc.get('0000000000000000000000000000000000001100') -print(entry['code']) -") -[[ "$(echo "$mth_code" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_mth_code" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook bytecode from node does not match genesis JSON" -pass "MerkleTreeHook bytecode matches genesis JSON" - -# Slot 0: _initialized = 1 (OZ v4 Initializable) -mth_slot0=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x0\", \"latest\"]") -expected_init="0x0000000000000000000000000000000000000000000000000000000000000001" -[[ "$(echo "$mth_slot0" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_init" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook slot 0 (_initialized) mismatch: got $mth_slot0, expected $expected_init" -pass "MerkleTreeHook slot 0 (_initialized) = 1" - -# Slot 51 (0x33): _owner -mth_slot51=$(rpc_call "eth_getStorageAt" "[\"$MERKLE_TREE_HOOK\", \"0x33\", \"latest\"]") -expected_owner="0x000000000000000000000000000000000000000000000000000000000000ad00" -[[ "$(echo "$mth_slot51" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_owner" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "MerkleTreeHook slot 51 (_owner) mismatch: got $mth_slot51, expected $expected_owner" -pass "MerkleTreeHook slot 51 (_owner) = $MERKLE_TREE_HOOK_OWNER" - -# Verify immutables are patched in bytecode: -# mailbox address at byte offsets 904 and 3300 (each is a 32-byte word, address in last 20 bytes) -# localDomain (chain_id=1234=0x04d2) at byte offset 644 -# The hex string has "0x" prefix, so byte N in the bytecode = hex chars at positions 2+2N..2+2N+2 -mth_hex="${mth_code#0x}" - -check_immutable() { - local name="$1" - local byte_offset="$2" - local expected_hex="$3" - local hex_offset=$((byte_offset * 2)) - local hex_len=${#expected_hex} - local actual="${mth_hex:$hex_offset:$hex_len}" - [[ "$(echo "$actual" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_hex" | tr '[:upper:]' '[:lower:]')" ]] \ - || fail "$name at byte offset $byte_offset mismatch: got $actual, expected $expected_hex" - pass "$name patched correctly at byte offset $byte_offset" -} - -# mailbox = 0x...1200 → 32-byte word with address in last 20 bytes -# Full 32-byte word: 000000000000000000000000 + 0000000000000000000000000000000000001200 -mailbox_word="0000000000000000000000000000000000000000000000000000000000001200" -check_immutable "mailbox" 904 "$mailbox_word" -check_immutable "mailbox (second ref)" 3300 "$mailbox_word" - -# localDomain = chain_id 1234 = 0x04d2 → 32-byte word -domain_word="00000000000000000000000000000000000000000000000000000000000004d2" -check_immutable "localDomain" 644 "$domain_word" - -# deployedBlock = 0 → 32 zero bytes -deployed_block_word="0000000000000000000000000000000000000000000000000000000000000000" -check_immutable "deployedBlock" 578 "$deployed_block_word" - -# ── Step 7: Verify Permit2 ───────────────────────────── +# ── Step 5: Verify Permit2 ────────────────────────────── PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3" From 04beb6bebf6f3938f3e839203677fe0d8cd22bf8 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 13:55:52 +0100 Subject: [PATCH 33/35] refactor(contracts): remove Hyperlane dependency from FeeVault Remove IHypNativeMinter interface and all Hyperlane-specific fields (destinationDomain, recipientAddress) from FeeVault. The contract now uses direct ETH transfers to a configurable bridgeRecipient instead of Hyperlane's transferRemote(). sendToCelestia() renamed to distribute(). Also removes the hyperlane-monorepo git submodule and updates all deployment scripts, tests, and documentation. --- .claude/skills/contracts.md | 8 +- .gitmodules | 3 - contracts/README.md | 53 +++----- contracts/foundry.lock | 3 + contracts/lib/hyperlane-monorepo | 1 - contracts/script/DeployFeeVault.s.sol | 18 +-- contracts/script/GenerateFeeVaultAlloc.s.sol | 54 +++----- contracts/src/FeeVault.sol | 78 +++-------- contracts/test/AdminProxy.t.sol | 2 - contracts/test/FeeVault.t.sol | 128 ++++++------------- docs/contracts/fee_vault.md | 60 ++++----- docs/guide/fee-systems.md | 8 +- 12 files changed, 132 insertions(+), 284 deletions(-) delete mode 160000 contracts/lib/hyperlane-monorepo diff --git a/.claude/skills/contracts.md b/.claude/skills/contracts.md index 292b7c8a..d93142f2 100644 --- a/.claude/skills/contracts.md +++ b/.claude/skills/contracts.md @@ -1,5 +1,5 @@ --- -description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee bridging to Celestia", "Hyperlane integration", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and bridged. +description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee distribution", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and distributed. --- # Contracts Onboarding @@ -9,13 +9,13 @@ description: This skill should be used when the user asks about "ev-reth contrac The contracts live in `contracts/` and use Foundry for development. There are two main contracts: 1. **AdminProxy** (`src/AdminProxy.sol`) - Bootstrap contract for admin addresses at genesis -2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees, bridges to Celestia via Hyperlane (cross-chain messaging protocol) +2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees and distributes them between configured recipients ## Key Files ### Contract Sources - `contracts/src/AdminProxy.sol` - Transparent proxy pattern for admin control -- `contracts/src/FeeVault.sol` - Fee collection and bridging logic +- `contracts/src/FeeVault.sol` - Fee collection and distribution logic ### Deployment Scripts - `contracts/script/DeployFeeVault.s.sol` - FeeVault deployment with CREATE2 @@ -34,7 +34,7 @@ The AdminProxy contract provides a bootstrap mechanism for setting admin address ### FeeVault The FeeVault serves as the destination for redirected base fees (instead of burning them). Key responsibilities: - Receive base fees from block production -- Bridge accumulated fees to Celestia via Hyperlane +- Distribute accumulated fees between configured recipients - Manage withdrawal permissions ## Connection to Rust Code diff --git a/.gitmodules b/.gitmodules index 0cebd2a9..9df9f949 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "contracts/lib/forge-std"] path = contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "contracts/lib/hyperlane-monorepo"] - path = contracts/lib/hyperlane-monorepo - url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git [submodule "contracts/lib/permit2"] path = contracts/lib/permit2 url = https://github.com/Uniswap/permit2 diff --git a/contracts/README.md b/contracts/README.md index 4c6e0a37..a04e3260 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,6 +1,6 @@ # EV-Reth Contracts -Smart contracts for EV-Reth, including the FeeVault for bridging collected fees to Celestia. +Smart contracts for EV-Reth, including the FeeVault for collecting and distributing fees. ## AdminProxy @@ -10,11 +10,11 @@ See [AdminProxy documentation](../docs/contracts/admin_proxy.md) for detailed se ## FeeVault -The FeeVault contract collects base fees and bridges them to Celestia via Hyperlane. It supports: +The FeeVault contract collects base fees and distributes them between a bridge recipient and an optional secondary recipient. It supports: - Configurable fee splitting between bridge and another recipient -- Minimum amount thresholds before bridging -- Call fee for incentivizing bridge calls +- Minimum amount thresholds before distributing +- Call fee for incentivizing distribution calls - Owner-controlled configuration ## Prerequisites @@ -45,17 +45,14 @@ All configuration is set via constructor arguments at deploy time: |----------|----------|-------------| | `OWNER` | Yes | Owner address (can configure the vault post-deployment) | | `SALT` | No | CREATE2 salt (default: `0x0`). Use any bytes32 value | -| `DESTINATION_DOMAIN` | Yes* | Hyperlane destination chain ID | -| `RECIPIENT_ADDRESS` | Yes* | Recipient on destination chain (bytes32, left-padded) | -| `MINIMUM_AMOUNT` | No | Minimum wei to bridge (default: 0) | -| `CALL_FEE` | No | Fee in wei for calling `sendToCelestia()` (default: 0) | +| `MINIMUM_AMOUNT` | No | Minimum wei to distribute (default: 0) | +| `CALL_FEE` | No | Fee in wei for calling `distribute()` (default: 0) | | `BRIDGE_SHARE_BPS` | No | Basis points to bridge (default: 10000 = 100%) | -| `OTHER_RECIPIENT` | No** | Address to receive non-bridged portion | +| `OTHER_RECIPIENT` | No* | Address to receive non-bridged portion | -*Required for the vault to be operational (can be set to 0 at deploy and configured later via setters) -**Required if `BRIDGE_SHARE_BPS` < 10000 +*Required if `BRIDGE_SHARE_BPS` < 10000 -**Note:** `HYP_NATIVE_MINTER` must be set via `setHypNativeMinter()` after deployment for the vault to be operational. +**Note:** `BRIDGE_RECIPIENT` must be set via `setBridgeRecipient()` after deployment for the vault to be operational. ### Choosing a Salt @@ -89,8 +86,6 @@ export OWNER=0xYourOwnerAddress export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 # Optional - configure at deploy time (can also be set later) -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x000000000000000000000000... # bytes32, left-padded cosmos address export MINIMUM_AMOUNT=1000000000000000000 # 1 ETH in wei export CALL_FEE=100000000000000 # 0.0001 ETH export BRIDGE_SHARE_BPS=8000 # 80% to bridge @@ -107,43 +102,25 @@ forge script script/DeployFeeVault.s.sol:DeployFeeVault \ --broadcast ``` -### Post-Deployment: Set HypNativeMinter +### Post-Deployment: Set Bridge Recipient -After deploying the HypNativeMinter contract, link it to the FeeVault: +After deployment, set the bridge recipient address: ```shell -cast send "setHypNativeMinter(address)" \ +cast send "setBridgeRecipient(address)" \ --rpc-url \ --private-key ``` -### Converting Cosmos Addresses to bytes32 - -The `recipientAddress` must be a bytes32. To convert a bech32 Cosmos address: - -1. Decode the bech32 to get the 20-byte address -2. Left-pad with zeros to 32 bytes - -Example using cast: - -```shell -# Left-pad a 20-byte address to 32 bytes -cast pad --left --len 32 1234567890abcdef1234567890abcdef12345678 -# Output: 0x0000000000000000000000001234567890abcdef1234567890abcdef12345678 -``` - -Note: When calling `transferRemote()` via cast, you may need to omit the `0x` prefix depending on your invocation method. - ## Admin Functions All functions are owner-only: | Function | Description | |----------|-------------| -| `setHypNativeMinter(address)` | Set the Hyperlane minter contract | -| `setRecipient(uint32, bytes32)` | Set destination domain and recipient | -| `setMinimumAmount(uint256)` | Set minimum amount to bridge | -| `setCallFee(uint256)` | Set fee for calling sendToCelestia | +| `setBridgeRecipient(address)` | Set the bridge recipient address | +| `setMinimumAmount(uint256)` | Set minimum amount to distribute | +| `setCallFee(uint256)` | Set fee for calling distribute | | `setBridgeShare(uint256)` | Set bridge percentage (basis points) | | `setOtherRecipient(address)` | Set recipient for non-bridged funds | | `transferOwnership(address)` | Transfer contract ownership | diff --git a/contracts/foundry.lock b/contracts/foundry.lock index aee2c9a8..19f75092 100644 --- a/contracts/foundry.lock +++ b/contracts/foundry.lock @@ -1,5 +1,8 @@ { "lib/forge-std": { "rev": "887e87251562513a7b5ab1ea517c039fe6ee0984" + }, + "lib/permit2": { + "rev": "cc56ad0f3439c502c246fc5cfcc3db92bb8b7219" } } \ No newline at end of file diff --git a/contracts/lib/hyperlane-monorepo b/contracts/lib/hyperlane-monorepo deleted file mode 160000 index bc401f7a..00000000 --- a/contracts/lib/hyperlane-monorepo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bc401f7a64f9e43aa25265dba12d80a33a19de21 diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index 54b7e0f8..4c6f7624 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -10,8 +10,6 @@ contract DeployFeeVault is Script { address owner = vm.envAddress("OWNER"); bytes32 salt = vm.envOr("SALT", bytes32(0)); - uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); // 0 defaults to 10000 in constructor @@ -21,34 +19,28 @@ contract DeployFeeVault is Script { vm.startBroadcast(); // Deploy FeeVault with CREATE2 - FeeVault feeVault = new FeeVault{salt: salt}( - owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient - ); + FeeVault feeVault = + new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); vm.stopBroadcast(); console.log("FeeVault deployed at:", address(feeVault)); console.log("Owner:", owner); - console.log("Destination domain:", destinationDomain); console.log("Minimum amount:", minimumAmount); console.log("Call fee:", callFee); console.log("Bridge share bps:", feeVault.bridgeShareBps()); console.log(""); - console.log("NOTE: Call setHypNativeMinter() after deploying HypNativeMinter"); + console.log("NOTE: Call setBridgeRecipient() to set the bridge destination"); } } /// @notice Compute FeeVault CREATE2 address off-chain -/// @dev Use this to predict the address before deploying -/// Requires env vars: DEPLOYER (EOA), OWNER, SALT (optional), and all constructor args contract ComputeFeeVaultAddress is Script { function run() external view { address deployer = vm.envAddress("DEPLOYER"); bytes32 salt = vm.envOr("SALT", bytes32(0)); address owner = vm.envAddress("OWNER"); - uint32 destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - bytes32 recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); uint256 minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); uint256 callFee = vm.envOr("CALL_FEE", uint256(0)); uint256 bridgeShareBps = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); @@ -57,9 +49,7 @@ contract ComputeFeeVaultAddress is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( type(FeeVault).creationCode, - abi.encode( - owner, destinationDomain, recipientAddress, minimumAmount, callFee, bridgeShareBps, otherRecipient - ) + abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) ) ); diff --git a/contracts/script/GenerateFeeVaultAlloc.s.sol b/contracts/script/GenerateFeeVaultAlloc.s.sol index 0215452f..bea158ec 100644 --- a/contracts/script/GenerateFeeVaultAlloc.s.sol +++ b/contracts/script/GenerateFeeVaultAlloc.s.sol @@ -8,27 +8,23 @@ abstract contract FeeVaultAllocBase is Script { struct Config { address feeVaultAddress; address owner; - uint32 destinationDomain; - bytes32 recipientAddress; + address bridgeRecipient; + address otherRecipient; uint256 minimumAmount; uint256 callFee; uint256 bridgeShareBpsRaw; uint256 bridgeShareBps; - address otherRecipient; - address hypNativeMinter; bytes32 salt; address deployer; } function loadConfig() internal view returns (Config memory cfg) { cfg.owner = vm.envAddress("OWNER"); - cfg.destinationDomain = uint32(vm.envOr("DESTINATION_DOMAIN", uint256(0))); - cfg.recipientAddress = vm.envOr("RECIPIENT_ADDRESS", bytes32(0)); + cfg.bridgeRecipient = vm.envOr("BRIDGE_RECIPIENT", address(0)); + cfg.otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); cfg.minimumAmount = vm.envOr("MINIMUM_AMOUNT", uint256(0)); cfg.callFee = vm.envOr("CALL_FEE", uint256(0)); cfg.bridgeShareBpsRaw = vm.envOr("BRIDGE_SHARE_BPS", uint256(0)); - cfg.otherRecipient = vm.envOr("OTHER_RECIPIENT", address(0)); - cfg.hypNativeMinter = vm.envOr("HYP_NATIVE_MINTER", address(0)); cfg.feeVaultAddress = vm.envOr("FEE_VAULT_ADDRESS", address(0)); cfg.deployer = vm.envOr("DEPLOYER", address(0)); cfg.salt = vm.envOr("SALT", bytes32(0)); @@ -43,13 +39,7 @@ abstract contract FeeVaultAllocBase is Script { abi.encodePacked( type(FeeVault).creationCode, abi.encode( - cfg.owner, - cfg.destinationDomain, - cfg.recipientAddress, - cfg.minimumAmount, - cfg.callFee, - cfg.bridgeShareBpsRaw, - cfg.otherRecipient + cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient ) ) ); @@ -64,29 +54,19 @@ abstract contract FeeVaultAllocBase is Script { function computeSlots(Config memory cfg) internal pure - returns ( - bytes32 slot0, - bytes32 slot1, - bytes32 slot2, - bytes32 slot3, - bytes32 slot4, - bytes32 slot5, - bytes32 slot6 - ) + returns (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) { - slot0 = bytes32(uint256(uint160(cfg.hypNativeMinter))); - slot1 = bytes32((uint256(cfg.destinationDomain) << 160) | uint256(uint160(cfg.owner))); - slot2 = cfg.recipientAddress; + slot0 = bytes32(uint256(uint160(cfg.owner))); + slot1 = bytes32(uint256(uint160(cfg.bridgeRecipient))); + slot2 = bytes32(uint256(uint160(cfg.otherRecipient))); slot3 = bytes32(cfg.minimumAmount); slot4 = bytes32(cfg.callFee); - slot5 = bytes32(uint256(uint160(cfg.otherRecipient))); - slot6 = bytes32(cfg.bridgeShareBps); + slot5 = bytes32(cfg.bridgeShareBps); } function addressKey(address addr) internal pure returns (string memory) { bytes memory full = bytes(vm.toString(addr)); bytes memory key = new bytes(40); - // Fixed-length copy for address key without 0x prefix. for (uint256 i = 0; i < 40; i++) { key[i] = full[i + 2]; } @@ -102,13 +82,12 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) = + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); console.log("========== FeeVault Genesis Alloc =========="); console.log("FeeVault address:", cfg.feeVaultAddress); console.log("Owner:", cfg.owner); - console.log("Destination domain:", cfg.destinationDomain); console.log("Bridge share bps (raw):", cfg.bridgeShareBpsRaw); console.log("Bridge share bps (effective):", cfg.bridgeShareBps); console.log(""); @@ -119,8 +98,8 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { if (cfg.bridgeShareBps < 10000 && cfg.otherRecipient == address(0)) { console.log("WARNING: OTHER_RECIPIENT is zero but bridge share < 10000."); } - if (cfg.hypNativeMinter == address(0)) { - console.log("NOTE: HYP_NATIVE_MINTER is zero; set it before calling sendToCelestia()."); + if (cfg.bridgeRecipient == address(0)) { + console.log("NOTE: BRIDGE_RECIPIENT is zero; set it before calling distribute()."); } console.log(""); @@ -137,8 +116,7 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { console.log(' "0x2": "%s",', vm.toString(slot2)); console.log(' "0x3": "%s",', vm.toString(slot3)); console.log(' "0x4": "%s",', vm.toString(slot4)); - console.log(' "0x5": "%s",', vm.toString(slot5)); - console.log(' "0x6": "%s"', vm.toString(slot6)); + console.log(' "0x5": "%s"', vm.toString(slot5)); console.log(" }"); console.log(" }"); console.log(" }"); @@ -157,7 +135,7 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5, bytes32 slot6) = + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); string memory json = string( @@ -178,8 +156,6 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { vm.toString(slot4), '","0x5":"', vm.toString(slot5), - '","0x6":"', - vm.toString(slot6), '"}}}' ) ); diff --git a/contracts/src/FeeVault.sol b/contracts/src/FeeVault.sol index 7069258c..b0ec6c10 100644 --- a/contracts/src/FeeVault.sol +++ b/contracts/src/FeeVault.sol @@ -1,35 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -interface IHypNativeMinter { - function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) - external - payable - returns (bytes32 messageId); -} - contract FeeVault { - IHypNativeMinter public hypNativeMinter; - address public owner; - uint32 public destinationDomain; - bytes32 public recipientAddress; + address public bridgeRecipient; + address public otherRecipient; uint256 public minimumAmount; uint256 public callFee; - - // Split accounting - address public otherRecipient; uint256 public bridgeShareBps; // Basis points (0-10000) for bridge share - event SentToCelestia(uint256 amount, bytes32 recipient, bytes32 messageId); + event FundsDistributed(uint256 total, uint256 bridgeAmount, uint256 otherAmount); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event HypNativeMinterUpdated(address hypNativeMinter); - event RecipientUpdated(uint32 destinationDomain, bytes32 recipientAddress); + event BridgeRecipientUpdated(address bridgeRecipient); event MinimumAmountUpdated(uint256 minimumAmount); event CallFeeUpdated(uint256 callFee); event BridgeShareUpdated(uint256 bridgeShareBps); event OtherRecipientUpdated(address otherRecipient); - event FundsSplit(uint256 totalNew, uint256 bridgeAmount, uint256 otherAmount); modifier onlyOwner() { require(msg.sender == owner, "FeeVault: caller is not the owner"); @@ -38,8 +24,6 @@ contract FeeVault { constructor( address _owner, - uint32 _destinationDomain, - bytes32 _recipientAddress, uint256 _minimumAmount, uint256 _callFee, uint256 _bridgeShareBps, @@ -49,8 +33,6 @@ contract FeeVault { require(_bridgeShareBps <= 10000, "FeeVault: invalid bps"); owner = _owner; - destinationDomain = _destinationDomain; - recipientAddress = _recipientAddress; minimumAmount = _minimumAmount; callFee = _callFee; bridgeShareBps = _bridgeShareBps == 0 ? 10000 : _bridgeShareBps; @@ -61,8 +43,8 @@ contract FeeVault { receive() external payable {} - function sendToCelestia() external payable { - require(address(hypNativeMinter) != address(0), "FeeVault: minter not set"); + function distribute() external payable { + require(bridgeRecipient != address(0), "FeeVault: bridge recipient not set"); require(msg.value >= callFee, "FeeVault: insufficient fee"); uint256 currentBalance = address(this).balance; @@ -73,20 +55,18 @@ contract FeeVault { require(bridgeAmount >= minimumAmount, "FeeVault: minimum amount not met"); - emit FundsSplit(currentBalance, bridgeAmount, otherAmount); + emit FundsDistributed(currentBalance, bridgeAmount, otherAmount); // Send other amount if any if (otherAmount > 0) { require(otherRecipient != address(0), "FeeVault: other recipient not set"); - (bool success,) = otherRecipient.call{value: otherAmount}(""); - require(success, "FeeVault: transfer failed"); + (bool sent,) = otherRecipient.call{value: otherAmount}(""); + require(sent, "FeeVault: transfer failed"); } - // Bridge the bridge amount - bytes32 messageId = - hypNativeMinter.transferRemote{value: bridgeAmount}(destinationDomain, recipientAddress, bridgeAmount); - - emit SentToCelestia(bridgeAmount, recipientAddress, messageId); + // Send bridge amount + (bool success,) = bridgeRecipient.call{value: bridgeAmount}(""); + require(success, "FeeVault: bridge transfer failed"); } // Admin functions @@ -97,10 +77,10 @@ contract FeeVault { owner = newOwner; } - function setRecipient(uint32 _destinationDomain, bytes32 _recipientAddress) external onlyOwner { - destinationDomain = _destinationDomain; - recipientAddress = _recipientAddress; - emit RecipientUpdated(_destinationDomain, _recipientAddress); + function setBridgeRecipient(address _bridgeRecipient) external onlyOwner { + require(_bridgeRecipient != address(0), "FeeVault: zero address"); + bridgeRecipient = _bridgeRecipient; + emit BridgeRecipientUpdated(_bridgeRecipient); } function setMinimumAmount(uint256 _minimumAmount) external onlyOwner { @@ -125,36 +105,18 @@ contract FeeVault { emit OtherRecipientUpdated(_otherRecipient); } - function setHypNativeMinter(address _hypNativeMinter) external onlyOwner { - require(_hypNativeMinter != address(0), "FeeVault: zero address"); - hypNativeMinter = IHypNativeMinter(_hypNativeMinter); - emit HypNativeMinterUpdated(_hypNativeMinter); - } - - /// @notice Return the full configuration currently stored in the contract. function getConfig() external view returns ( address _owner, - uint32 _destinationDomain, - bytes32 _recipientAddress, + address _bridgeRecipient, + address _otherRecipient, uint256 _minimumAmount, uint256 _callFee, - uint256 _bridgeShareBps, - address _otherRecipient, - address _hypNativeMinter + uint256 _bridgeShareBps ) { - return ( - owner, - destinationDomain, - recipientAddress, - minimumAmount, - callFee, - bridgeShareBps, - otherRecipient, - address(hypNativeMinter) - ); + return (owner, bridgeRecipient, otherRecipient, minimumAmount, callFee, bridgeShareBps); } } diff --git a/contracts/test/AdminProxy.t.sol b/contracts/test/AdminProxy.t.sol index 4404e4e7..d998ebcd 100644 --- a/contracts/test/AdminProxy.t.sol +++ b/contracts/test/AdminProxy.t.sol @@ -362,8 +362,6 @@ contract AdminProxyTest is Test { // Deploy FeeVault with proxy as owner FeeVault vault = new FeeVault( address(proxy), // proxy is owner - 1234, - bytes32(uint256(0xbeef)), 1 ether, 0.1 ether, 10000, diff --git a/contracts/test/FeeVault.t.sol b/contracts/test/FeeVault.t.sol index fdc379db..7814b339 100644 --- a/contracts/test/FeeVault.t.sol +++ b/contracts/test/FeeVault.t.sol @@ -4,71 +4,49 @@ pragma solidity ^0.8.24; import {Test, console} from "forge-std/Test.sol"; import {FeeVault} from "../src/FeeVault.sol"; -contract MockHypNativeMinter { - event TransferRemoteCalled(uint32 destination, bytes32 recipient, uint256 amount); - - function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) - external - payable - returns (bytes32 messageId) - { - require(msg.value == _amount, "MockHypNativeMinter: value mismatch"); - emit TransferRemoteCalled(_destination, _recipient, _amount); - return bytes32(uint256(1)); // Return a dummy messageId - } -} - contract FeeVaultTest is Test { FeeVault public feeVault; - MockHypNativeMinter public mockMinter; address public owner; address public user; + address public bridgeRecipient; address public otherRecipient; - uint32 public destination = 1234; - bytes32 public recipient = bytes32(uint256(0xdeadbeef)); uint256 public minAmount = 1 ether; uint256 public fee = 0.1 ether; function setUp() public { owner = address(this); user = address(0x1); + bridgeRecipient = address(0x42); otherRecipient = address(0x99); - mockMinter = new MockHypNativeMinter(); feeVault = new FeeVault( owner, - destination, - recipient, minAmount, fee, 10000, // 100% bridge share otherRecipient ); - feeVault.setHypNativeMinter(address(mockMinter)); + feeVault.setBridgeRecipient(bridgeRecipient); } - function test_GetConfig() public { + function test_GetConfig() public view { ( address cfgOwner, - uint32 cfgDestination, - bytes32 cfgRecipient, + address cfgBridgeRecipient, + address cfgOtherRecipient, uint256 cfgMinAmount, uint256 cfgCallFee, - uint256 cfgBridgeShare, - address cfgOtherRecipient, - address cfgHypNativeMinter + uint256 cfgBridgeShare ) = feeVault.getConfig(); assertEq(cfgOwner, owner); - assertEq(cfgDestination, destination); - assertEq(cfgRecipient, recipient); + assertEq(cfgBridgeRecipient, bridgeRecipient); + assertEq(cfgOtherRecipient, otherRecipient); assertEq(cfgMinAmount, minAmount); assertEq(cfgCallFee, fee); assertEq(cfgBridgeShare, 10000); - assertEq(cfgOtherRecipient, otherRecipient); - assertEq(cfgHypNativeMinter, address(mockMinter)); } function test_Receive() public { @@ -78,36 +56,28 @@ contract FeeVaultTest is Test { assertEq(address(feeVault).balance, amount, "Balance mismatch"); } - function test_SendToCelestia_100PercentBridge() public { + function test_Distribute_100PercentBridge() public { // Fund with minAmount (bool success,) = address(feeVault).call{value: minAmount}(""); require(success); uint256 totalAmount = minAmount + fee; - vm.expectEmit(true, true, true, true, address(mockMinter)); - emit MockHypNativeMinter.TransferRemoteCalled(destination, recipient, totalAmount); - - // Expect the event from FeeVault vm.expectEmit(true, true, true, true, address(feeVault)); - emit FeeVault.SentToCelestia(totalAmount, recipient, bytes32(uint256(1))); + emit FeeVault.FundsDistributed(totalAmount, totalAmount, 0); vm.prank(user); vm.deal(user, fee); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); - assertEq(address(feeVault).balance, 0, "Collector should be empty"); + assertEq(address(feeVault).balance, 0, "Vault should be empty"); + assertEq(bridgeRecipient.balance, totalAmount, "Bridge recipient should receive funds"); } - function test_SendToCelestia_Split5050() public { + function test_Distribute_Split5050() public { // Set split to 50% feeVault.setBridgeShare(5000); - // Fund with 2 ether. - // Fee is 0.1 ether. - // Total new funds = 2.1 ether. - // Bridge = 1.05 ether. Other = 1.05 ether. - // Min amount is 1 ether, so 1.05 >= 1.0 is OK. uint256 fundAmount = 2 ether; (bool success,) = address(feeVault).call{value: fundAmount}(""); require(success); @@ -116,46 +86,47 @@ contract FeeVaultTest is Test { uint256 expectedBridge = totalNew / 2; uint256 expectedOther = totalNew - expectedBridge; - vm.expectEmit(true, true, true, true, address(mockMinter)); - emit MockHypNativeMinter.TransferRemoteCalled(destination, recipient, expectedBridge); - vm.prank(user); vm.deal(user, fee); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); - assertEq(address(feeVault).balance, 0, "Collector should be empty"); + assertEq(address(feeVault).balance, 0, "Vault should be empty"); + assertEq(bridgeRecipient.balance, expectedBridge, "Bridge recipient should receive funds"); assertEq(otherRecipient.balance, expectedOther, "Other recipient should receive funds"); } - function test_SendToCelestia_InsufficientFee() public { + function test_Distribute_InsufficientFee() public { vm.prank(user); vm.deal(user, fee); - // Send less than fee vm.expectRevert("FeeVault: insufficient fee"); - feeVault.sendToCelestia{value: fee - 1}(); + feeVault.distribute{value: fee - 1}(); } - function test_SendToCelestia_BelowMinAmount_AfterSplit() public { + function test_Distribute_BelowMinAmount_AfterSplit() public { feeVault.setBridgeShare(1000); // 10% bridge - // Fund with 2 ether. Total 2.1. - // Bridge = 0.21. Other = 1.89. - // Min amount is 1.0. 0.21 < 1.0. Should revert. (bool success,) = address(feeVault).call{value: 2 ether}(""); require(success); vm.prank(user); vm.deal(user, fee); vm.expectRevert("FeeVault: minimum amount not met"); - feeVault.sendToCelestia{value: fee}(); + feeVault.distribute{value: fee}(); } - function test_AdminFunctions() public { - // Test setRecipient - feeVault.setRecipient(5678, bytes32(uint256(0xbeef))); - assertEq(feeVault.destinationDomain(), 5678); - assertEq(feeVault.recipientAddress(), bytes32(uint256(0xbeef))); + function test_Distribute_BridgeRecipientNotSet() public { + FeeVault freshVault = new FeeVault(owner, minAmount, fee, 10000, otherRecipient); + (bool success,) = address(freshVault).call{value: minAmount}(""); + require(success); + + vm.prank(user); + vm.deal(user, fee); + vm.expectRevert("FeeVault: bridge recipient not set"); + freshVault.distribute{value: fee}(); + } + + function test_AdminFunctions() public { // Test setMinimumAmount feeVault.setMinimumAmount(5 ether); assertEq(feeVault.minimumAmount(), 5 ether); @@ -190,10 +161,6 @@ contract FeeVaultTest is Test { } function test_AdminAccessControl() public { - vm.prank(user); - vm.expectRevert("FeeVault: caller is not the owner"); - feeVault.setRecipient(1, bytes32(0)); - vm.prank(user); vm.expectRevert("FeeVault: caller is not the owner"); feeVault.setMinimumAmount(1); @@ -216,30 +183,17 @@ contract FeeVaultTest is Test { vm.prank(user); vm.expectRevert("FeeVault: caller is not the owner"); - feeVault.setHypNativeMinter(address(0x123)); + feeVault.setBridgeRecipient(address(0x123)); } - function test_SetHypNativeMinter() public { - MockHypNativeMinter newMinter = new MockHypNativeMinter(); - feeVault.setHypNativeMinter(address(newMinter)); - assertEq(address(feeVault.hypNativeMinter()), address(newMinter)); + function test_SetBridgeRecipient() public { + address newRecipient = address(0x55); + feeVault.setBridgeRecipient(newRecipient); + assertEq(feeVault.bridgeRecipient(), newRecipient); } - function test_SetHypNativeMinter_ZeroAddress() public { + function test_SetBridgeRecipient_ZeroAddress() public { vm.expectRevert("FeeVault: zero address"); - feeVault.setHypNativeMinter(address(0)); - } - - function test_SendToCelestia_MinterNotSet() public { - // Deploy fresh vault without minter - FeeVault freshVault = new FeeVault(owner, destination, recipient, minAmount, fee, 10000, otherRecipient); - - (bool success,) = address(freshVault).call{value: minAmount}(""); - require(success); - - vm.prank(user); - vm.deal(user, fee); - vm.expectRevert("FeeVault: minter not set"); - freshVault.sendToCelestia{value: fee}(); + feeVault.setBridgeRecipient(address(0)); } } diff --git a/docs/contracts/fee_vault.md b/docs/contracts/fee_vault.md index ed4ffe54..a0569e15 100644 --- a/docs/contracts/fee_vault.md +++ b/docs/contracts/fee_vault.md @@ -2,33 +2,32 @@ ## Overview -The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between bridging to a specific destination chain (e.g., Celestia) and sending to a secondary recipient. +The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between a bridge recipient and a secondary recipient. ## Use Case -This contract serves as a **fee sink** and **bridging mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to another ecosystem while retaining a portion for other purposes (e.g., developer rewards, treasury). +This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to configured recipients while retaining a portion for other purposes (e.g., developer rewards, treasury). 1. **Fee Accumulation**: The contract receives funds from: - **Base Fee Redirect**: The chain's execution layer (e.g., `ev-revm`) can be configured to direct burned base fees directly to this contract's address. - **Direct Transfers**: Anyone can send native tokens to the contract via the `receive()` function. -2. **Splitting & Bridging**: Once sufficient funds have accumulated, any user can trigger the `sendToCelestia()` function. This splits the funds based on a configured percentage: - - **Bridge Share**: Sent to the destination chain (Celestia) via the `HypNativeMinter`. +2. **Splitting & Distribution**: Once sufficient funds have accumulated, any user can trigger the `distribute()` function. This splits the funds based on a configured percentage: + - **Bridge Share**: Sent to the configured `bridgeRecipient`. - **Other Share**: Immediately transferred to a configured `otherRecipient` address. ## Architecture ### Core Components -- **HypNativeMinter Integration**: The contract interacts with a Hyperlane `HypNativeMinter` to handle the cross-chain transfer logic. +- **Split Logic**: Configurable basis-point split between bridge and secondary recipient. - **Admin Controls**: An `owner` manages critical parameters to ensure security and flexibility. ### Key Features -- **Automatic Splitting**: Funds are split automatically upon calling `sendToCelestia`. No manual withdrawal is required for the secondary recipient. -- **Stored Recipient**: The destination domain (Chain ID) and recipient address are stored in the contract state. -- **Minimum Threshold**: A `minimumAmount` ensures that bridging only occurs when it is economically viable. -- **Caller Incentive/Fee**: A `callFee` is required to trigger the bridge function. +- **Automatic Splitting**: Funds are split automatically upon calling `distribute`. No manual withdrawal is required for the secondary recipient. +- **Minimum Threshold**: A `minimumAmount` ensures that distribution only occurs when it is economically viable. +- **Caller Incentive/Fee**: A `callFee` is required to trigger the distribution function. ## Workflow @@ -38,7 +37,7 @@ This contract serves as a **fee sink** and **bridging mechanism** for a rollup o 2. **Trigger Phase**: - A keeper or user notices the bridge portion exceeds `minimumAmount`. - - They call `sendToCelestia{value: callFee}()`. + - They call `distribute{value: callFee}()`. - The contract checks: - `msg.value >= callFee` - `bridgeAmount >= minimumAmount` @@ -46,19 +45,18 @@ This contract serves as a **fee sink** and **bridging mechanism** for a rollup o 3. **Execution Phase**: - The contract calculates the split based on `bridgeShareBps`. - **Other Share**: Transferred immediately to `otherRecipient`. - - **Bridge Share**: Bridged to Celestia via `hypNativeMinter.transferRemote`. - - `SentToCelestia` and `FundsSplit` events are emitted. + - **Bridge Share**: Sent to `bridgeRecipient`. + - `FundsDistributed` event is emitted. ## Configuration Parameters | Parameter | Description | Managed By | |-----------|-------------|------------| -| `destinationDomain` | Hyperlane domain ID of the target chain (e.g., Celestia). | Owner | -| `recipientAddress` | Address on the target chain to receive funds. | Owner | -| `minimumAmount` | Minimum bridge amount required to trigger a bridge tx. | Owner | +| `bridgeRecipient` | Address to receive the bridge share of funds. | Owner | +| `otherRecipient` | Address to receive the non-bridged portion of funds. | Owner | +| `minimumAmount` | Minimum bridge amount required to trigger distribution. | Owner | | `callFee` | Fee required from the caller to execute the function. | Owner | | `bridgeShareBps` | Basis points (0-10000) determining the % of funds to bridge. | Owner | -| `otherRecipient` | Address to receive the non-bridged portion of funds. | Owner | ## Embedding FeeVault in Genesis @@ -72,8 +70,6 @@ If you want a deterministic address across chains, compute the CREATE2 address a export OWNER=0xYourOwnerOrAdminProxy export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 export DEPLOYER=0xYourDeployerAddress -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x0000000000000000000000000000000000000000000000000000000000000000 export MINIMUM_AMOUNT=0 export CALL_FEE=0 export BRIDGE_SHARE_BPS=10000 @@ -106,13 +102,11 @@ export SALT=0x0000000000000000000000000000000000000000000000000000000000000001 export FEE_VAULT_ADDRESS=0xYourFeeVaultAddress # Optional configuration (defaults to zero) -export DESTINATION_DOMAIN=1234 -export RECIPIENT_ADDRESS=0x0000000000000000000000000000000000000000000000000000000000000000 +export BRIDGE_RECIPIENT=0x0000000000000000000000000000000000000000 +export OTHER_RECIPIENT=0x0000000000000000000000000000000000000000 export MINIMUM_AMOUNT=0 export CALL_FEE=0 export BRIDGE_SHARE_BPS=10000 -export OTHER_RECIPIENT=0x0000000000000000000000000000000000000000 -export HYP_NATIVE_MINTER=0x0000000000000000000000000000000000000000 forge script script/GenerateFeeVaultAlloc.s.sol -vvv ``` @@ -123,19 +117,18 @@ Storage layout is derived from declaration order in `FeeVault.sol`: | Slot | Variable | Encoding | |------|----------|----------| -| `0x0` | `hypNativeMinter` | Address (20 bytes, left-padded) | -| `0x1` | `owner` + `destinationDomain` | `0x0000000000000000` | -| `0x2` | `recipientAddress` | bytes32 | +| `0x0` | `owner` | Address (20 bytes, left-padded) | +| `0x1` | `bridgeRecipient` | Address (20 bytes, left-padded) | +| `0x2` | `otherRecipient` | Address (20 bytes, left-padded) | | `0x3` | `minimumAmount` | uint256 | | `0x4` | `callFee` | uint256 | -| `0x5` | `otherRecipient` | Address (20 bytes, left-padded) | -| `0x6` | `bridgeShareBps` | uint256 | +| `0x5` | `bridgeShareBps` | uint256 | Notes: - `owner` must be non-zero, otherwise no one can administer the vault. - The constructor default (`bridgeShareBps = 10000 when 0`) does **not** apply at genesis. Set `0x2710` (10000) explicitly if you want 100% bridging. The helper script applies this default for you when `BRIDGE_SHARE_BPS=0`. -- `hypNativeMinter` can be zero at genesis, but it must be set before calling `sendToCelestia()`. +- `bridgeRecipient` can be zero at genesis, but it must be set before calling `distribute()`. Example alloc entry (address key without `0x`): @@ -146,13 +139,12 @@ Example alloc entry (address key without `0x`): "balance": "0x0", "code": "0x", "storage": { - "0x0": "0x0000000000000000000000001111111111111111111111111111111111111111", - "0x1": "0x0000000000000000000004d22222222222222222222222222222222222222222", - "0x2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0": "0x0000000000000000000000002222222222222222222222222222222222222222", + "0x1": "0x0000000000000000000000001111111111111111111111111111111111111111", + "0x2": "0x0000000000000000000000000000000000000000", "0x3": "0x0", "0x4": "0x0", - "0x5": "0x0000000000000000000000000000000000000000", - "0x6": "0x2710" + "0x5": "0x2710" } } } @@ -169,7 +161,7 @@ cast code --rpc-url # Inspect full config in one call cast call \ - "getConfig()(address,uint32,bytes32,uint256,uint256,uint256,address,address)" \ + "getConfig()(address,address,address,uint256,uint256,uint256)" \ --rpc-url # Or read individual storage slots (optional) diff --git a/docs/guide/fee-systems.md b/docs/guide/fee-systems.md index d3740575..6e375b10 100644 --- a/docs/guide/fee-systems.md +++ b/docs/guide/fee-systems.md @@ -5,7 +5,7 @@ This guide connects three related components that move or manage native token value: - Base fee redirect: redirects EIP-1559 base fees to a configured sink instead of burning them. -- FeeVault: a contract that accumulates native tokens and can split and bridge them. +- FeeVault: a contract that accumulates native tokens and can split and distribute them. - Native minting precompile: a privileged mint/burn interface for controlled supply changes. These components are independent but commonly deployed together. The base fee redirect is a value transfer, not minting. Native minting is explicit supply change and should remain tightly controlled. @@ -33,13 +33,13 @@ See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. ## FeeVault (contract level) -**Purpose**: Accumulate native tokens and split them between a bridge destination and a secondary recipient. +**Purpose**: Accumulate native tokens and split them between a bridge recipient and a secondary recipient. **Mechanics**: - Receives base fees when `baseFeeSink` is set to the FeeVault address. -- Anyone can trigger `sendToCelestia` (or equivalent) once the minimum threshold is met. -- Splits balance by `bridgeShareBps`, sends the bridge share to `HypNativeMinter`, and transfers the remainder to `otherRecipient`. +- Anyone can trigger `distribute()` once the minimum threshold is met. +- Splits balance by `bridgeShareBps`, sends the bridge share to `bridgeRecipient`, and transfers the remainder to `otherRecipient`. **Why it pairs with base fee redirect**: the redirect funnels base fees into the FeeVault automatically, turning burned fees into recoverable value for treasury or bridging. From c6e679b2e81fd643c0bb53046509dbd93f943688 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 13:58:16 +0100 Subject: [PATCH 34/35] style(contracts): fix forge fmt formatting --- contracts/script/DeployFeeVault.s.sol | 6 ++---- contracts/script/GenerateFeeVaultAlloc.s.sol | 10 +++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/contracts/script/DeployFeeVault.s.sol b/contracts/script/DeployFeeVault.s.sol index 4c6f7624..675010d4 100644 --- a/contracts/script/DeployFeeVault.s.sol +++ b/contracts/script/DeployFeeVault.s.sol @@ -19,8 +19,7 @@ contract DeployFeeVault is Script { vm.startBroadcast(); // Deploy FeeVault with CREATE2 - FeeVault feeVault = - new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); + FeeVault feeVault = new FeeVault{salt: salt}(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient); vm.stopBroadcast(); @@ -48,8 +47,7 @@ contract ComputeFeeVaultAddress is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( - type(FeeVault).creationCode, - abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) + type(FeeVault).creationCode, abi.encode(owner, minimumAmount, callFee, bridgeShareBps, otherRecipient) ) ); diff --git a/contracts/script/GenerateFeeVaultAlloc.s.sol b/contracts/script/GenerateFeeVaultAlloc.s.sol index bea158ec..e332c1e5 100644 --- a/contracts/script/GenerateFeeVaultAlloc.s.sol +++ b/contracts/script/GenerateFeeVaultAlloc.s.sol @@ -38,9 +38,7 @@ abstract contract FeeVaultAllocBase is Script { bytes32 initCodeHash = keccak256( abi.encodePacked( type(FeeVault).creationCode, - abi.encode( - cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient - ) + abi.encode(cfg.owner, cfg.minimumAmount, cfg.callFee, cfg.bridgeShareBpsRaw, cfg.otherRecipient) ) ); cfg.feeVaultAddress = address( @@ -82,8 +80,7 @@ contract GenerateFeeVaultAlloc is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = - computeSlots(cfg); + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); console.log("========== FeeVault Genesis Alloc =========="); console.log("FeeVault address:", cfg.feeVaultAddress); @@ -135,8 +132,7 @@ contract GenerateFeeVaultAllocJSON is FeeVaultAllocBase { Config memory cfg = loadConfig(); bytes memory runtimeCode = type(FeeVault).runtimeCode; - (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = - computeSlots(cfg); + (bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4, bytes32 slot5) = computeSlots(cfg); string memory json = string( abi.encodePacked( From cae3723387ae2e2a2eeae991bdec6bdf829e1cff Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Mar 2026 14:35:42 +0100 Subject: [PATCH 35/35] docs: clarify FeeVault is optional, document when to use it vs plain baseFeeSink --- contracts/README.md | 15 +++++++++------ docs/contracts/fee_vault.md | 14 ++++++++++++-- docs/guide/fee-systems.md | 22 ++++++++++++---------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/contracts/README.md b/contracts/README.md index a04e3260..1de36cea 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -8,14 +8,17 @@ The AdminProxy contract solves the bootstrap problem for admin addresses at gene See [AdminProxy documentation](../docs/contracts/admin_proxy.md) for detailed setup and usage instructions. -## FeeVault +## FeeVault (optional) -The FeeVault contract collects base fees and distributes them between a bridge recipient and an optional secondary recipient. It supports: +The FeeVault is an **optional** contract for chains that need on-chain fee splitting logic. The base fee redirect (`baseFeeSink`) works with any address — an EOA or multisig is sufficient if you just need fees sent to a single destination. -- Configurable fee splitting between bridge and another recipient -- Minimum amount thresholds before distributing -- Call fee for incentivizing distribution calls -- Owner-controlled configuration +FeeVault is useful when you need: + +- **Automatic splitting** of accumulated fees between two recipients (e.g., 80% to a bridge contract, 20% to a treasury) +- **Minimum threshold** to avoid distributing uneconomically small amounts +- **Keeper incentive** (`callFee`) so anyone can trigger distribution and get compensated + +If your chain only needs fees routed to a single address, skip FeeVault and point `baseFeeSink` directly at that address. ## Prerequisites diff --git a/docs/contracts/fee_vault.md b/docs/contracts/fee_vault.md index a0569e15..d701ed3d 100644 --- a/docs/contracts/fee_vault.md +++ b/docs/contracts/fee_vault.md @@ -2,11 +2,21 @@ ## Overview -The `FeeVault` is a specialized smart contract designed to accumulate native tokens (gas tokens) and automatically split them between a bridge recipient and a secondary recipient. +The `FeeVault` is an **optional** smart contract for chains that need on-chain fee splitting logic. It accumulates native tokens (gas tokens) and automatically splits them between two configurable recipients. + +## When to Use FeeVault + +The base fee redirect (`baseFeeSink`) works with any address. You **do not need** FeeVault if fees should go to a single destination — just point `baseFeeSink` at an EOA or multisig. + +FeeVault adds value when you need: + +- **Splitting**: Automatically divide fees between two recipients (e.g., 80% to a bridge, 20% to treasury). +- **Minimum threshold**: Only distribute when enough has accumulated to be economically worthwhile. +- **Keeper incentive**: A `callFee` rewards anyone who triggers the distribution, removing the need for a centralized operator. ## Use Case -This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to configured recipients while retaining a portion for other purposes (e.g., developer rewards, treasury). +This contract serves as a **fee sink** and **distribution mechanism** for a rollup or chain that wants to redirect collected fees (e.g., EIP-1559 base fees) to multiple recipients. 1. **Fee Accumulation**: The contract receives funds from: - **Base Fee Redirect**: The chain's execution layer (e.g., `ev-revm`) can be configured to direct burned base fees directly to this contract's address. diff --git a/docs/guide/fee-systems.md b/docs/guide/fee-systems.md index 6e375b10..aa9dfa76 100644 --- a/docs/guide/fee-systems.md +++ b/docs/guide/fee-systems.md @@ -31,9 +31,11 @@ These components are independent but commonly deployed together. The base fee re See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. -## FeeVault (contract level) +## FeeVault (contract level, optional) -**Purpose**: Accumulate native tokens and split them between a bridge recipient and a secondary recipient. +**Purpose**: Accumulate native tokens and split them between two configurable recipients. + +FeeVault is **optional**. The base fee redirect works with any address — if fees should go to a single destination, point `baseFeeSink` at an EOA or multisig and skip FeeVault entirely. Use FeeVault when you need automatic on-chain splitting, minimum thresholds, or keeper incentives. **Mechanics**: @@ -41,8 +43,6 @@ See `docs/adr/ADR-0001-base-fee-redirect.md` for implementation details. - Anyone can trigger `distribute()` once the minimum threshold is met. - Splits balance by `bridgeShareBps`, sends the bridge share to `bridgeRecipient`, and transfers the remainder to `otherRecipient`. -**Why it pairs with base fee redirect**: the redirect funnels base fees into the FeeVault automatically, turning burned fees into recoverable value for treasury or bridging. - See `docs/contracts/fee_vault.md` for parameters and deployment details. ## Native Token Minting Precompile @@ -69,17 +69,19 @@ See `docs/adr/ADR-0002-native-minting-precompile.md` for the full interface and ## How They Fit Together -1. **Base fee redirect** credits base fees to a sink address instead of burning them. -2. **FeeVault** can be that sink, so base fees accumulate in a contract with deterministic split logic. +1. **Base fee redirect** credits base fees to a sink address instead of burning them. The sink can be any address (EOA, multisig, or contract). +2. **FeeVault** is one option for that sink when you need automatic splitting between two recipients. If fees go to a single destination, skip it. 3. **Native minting** is separate and optional; it is used for controlled supply changes (bootstrapping liquidity, treasury operations), not for redirecting fees. In other words, base fee redirect and FeeVault are about re-routing existing value, while native minting explicitly changes total supply. Keep those responsibilities separate and limit minting access to minimize systemic risk. -## Suggested Deployment Pattern +## Suggested Deployment Patterns + +**Simple (no FeeVault):** Set `baseFeeSink` to an EOA or multisig. Fees accumulate there directly. + +**With splitting (FeeVault):** Set `baseFeeSink` to the FeeVault address. Configure the split between `bridgeRecipient` and `otherRecipient`. Use `AdminProxy` as the FeeVault owner if you need a safe, upgradeable admin. -- Set `baseFeeSink` to the FeeVault address. -- Use `AdminProxy` as the `mintAdmin` and FeeVault owner if you need a safe, upgradeable admin. -- Activate both features at a planned height for existing networks. +Both patterns can be combined with native minting if needed. Activate features at a planned height for existing networks. References: