Skip to main content

tempo_contracts/
lib.rs

1//! Tempo predeployed contracts and bindings.
2
3#![no_std]
4#![cfg_attr(not(test), warn(unused_crate_dependencies))]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6// auto-generated sol! builders for events/errors with many fields trigger this
7#![allow(clippy::too_many_arguments)]
8
9#[cfg(test)]
10extern crate alloc;
11
12use alloy_primitives::{Address, B256, address, b256};
13
14/// Default address for the Multicall3 contract on most chains. See: <https://github.com/mds1/multicall>
15pub const MULTICALL3_ADDRESS: Address = address!("0xcA11bde05977b3631167028862bE2a173976CA11");
16pub const CREATEX_ADDRESS: Address = address!("0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed");
17pub const SAFE_DEPLOYER_ADDRESS: Address = address!("0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7");
18pub const PERMIT2_ADDRESS: Address = address!("0x000000000022d473030f116ddee9f6b43ac78ba3");
19pub const PERMIT2_SALT: B256 =
20    b256!("0x0000000000000000000000000000000000000000d3af2663da51c10215000000");
21pub const ARACHNID_CREATE2_FACTORY_ADDRESS: Address =
22    address!("0x4e59b44847b379578588920cA78FbF26c0B4956C");
23
24/// Helper macro to allow feature-gating rpc and serde implementations.
25macro_rules! sol {
26    ($($input:tt)*) => {
27        #[cfg(all(feature = "rpc", feature = "serde"))]
28        alloy_sol_types::sol! {
29            #[sol(rpc)]
30            #[derive(serde::Serialize, serde::Deserialize)]
31            $($input)*
32        }
33        #[cfg(all(feature = "rpc", not(feature = "serde")))]
34        alloy_sol_types::sol! {
35            #[sol(rpc)]
36            $($input)*
37        }
38        #[cfg(all(not(feature = "rpc"), feature = "serde"))]
39        alloy_sol_types::sol! {
40            #[derive(serde::Serialize, serde::Deserialize)]
41            $($input)*
42        }
43        #[cfg(all(not(feature = "rpc"), not(feature = "serde")))]
44        alloy_sol_types::sol! {
45            $($input)*
46        }
47    };
48}
49
50pub(crate) use sol;
51
52pub mod contracts {
53    use alloy_primitives::{B256, Bytes, b256, bytes};
54
55    sol!(
56        #[allow(missing_docs)]
57        CreateX,
58        "abi/CreateX.json",
59    );
60
61    /// Keccak256 hash of CreateX deployed bytecode
62    pub const CREATEX_BYTECODE_HASH: B256 =
63        b256!("0xbd8a7ea8cfca7b4e5f5041d7d4b17bc317c5ce42cfbc42066a00cf26b43eb53f");
64
65    sol!(
66        #[allow(missing_docs)]
67        Permit2,
68        "abi/Permit2.json"
69    );
70
71    sol!(
72        #[allow(missing_docs)]
73        SafeDeployer,
74        "abi/SafeDeployer.json",
75    );
76
77    pub const ARACHNID_CREATE2_FACTORY_BYTECODE: Bytes = bytes!(
78        "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
79    );
80
81    sol!(
82        #[allow(missing_docs)]
83        Multicall3,
84        "abi/Multicall3.json",
85    );
86
87    /// Keccak256 hash of Multicall3 deployed bytecode
88    pub const MULTICALL3_DEPLOYED_BYTECODE_HASH: B256 =
89        b256!("0xd5c15df687b16f2ff992fc8d767b4216323184a2bbc6ee2f9c398c318e770891");
90}
91
92pub use contracts::{CreateX, Multicall3, Permit2, SafeDeployer};
93
94pub mod precompiles;
95
96#[cfg(test)]
97mod tests {
98    //! Tests to verify that our predeployed contract bytecode matches Ethereum mainnet.
99    //!
100    //! These tests use alloy to fetch the code hash directly from Ethereum mainnet
101    //! and compare against our stored bytecode hashes. This ensures we haven't accidentally
102    //! deployed the wrong contract (e.g., Multicall instead of Multicall3).
103    //!
104    //! Run with:
105    //! ```sh
106    //! cargo test -p tempo-contracts
107    //! ```
108    //!
109    //! Optionally set `ETH_RPC_URL` to use a custom RPC endpoint.
110
111    extern crate std;
112
113    use super::*;
114    use alloc::string::{String, ToString};
115    use alloy_primitives::{B256, keccak256};
116    use alloy_provider::{Provider, ProviderBuilder};
117
118    /// Default public RPC URL for Ethereum mainnet.
119    const DEFAULT_ETH_RPC_URL: &str = "https://eth.llamarpc.com";
120
121    /// Returns the Ethereum mainnet RPC URL from the `ETH_RPC_URL` environment variable,
122    /// or falls back to a default public RPC.
123    fn get_rpc_url() -> String {
124        std::env::var("ETH_RPC_URL").unwrap_or_else(|_| DEFAULT_ETH_RPC_URL.to_string())
125    }
126
127    /// Fetches the code hash for an address from Ethereum mainnet using alloy provider.
128    async fn get_mainnet_code_hash(address: Address) -> B256 {
129        let rpc_url = get_rpc_url();
130        let provider = ProviderBuilder::new().connect_http(rpc_url.parse().unwrap());
131
132        let code = provider
133            .get_code_at(address)
134            .await
135            .expect("Failed to fetch code from mainnet");
136        keccak256(&code)
137    }
138
139    #[tokio::test]
140    #[ignore = "requires mainnet RPC access - not needed after mainnet launch"]
141    async fn multicall3_bytecode_matches_mainnet() {
142        // Verify our hash constant matches our bytecode
143        let computed_hash = keccak256(&Multicall3::DEPLOYED_BYTECODE);
144        let stored_hash = contracts::MULTICALL3_DEPLOYED_BYTECODE_HASH;
145        assert_eq!(
146            computed_hash, stored_hash,
147            "MULTICALL3_DEPLOYED_BYTECODE_HASH does not match the actual bytecode!\n\
148             Computed: {computed_hash}\n\
149             Stored:   {stored_hash}"
150        );
151
152        // Verify our bytecode matches mainnet
153        let mainnet_hash = get_mainnet_code_hash(MULTICALL3_ADDRESS).await;
154        assert_eq!(
155            mainnet_hash, stored_hash,
156            "Multicall3 bytecode hash mismatch!\n\
157             Mainnet: {mainnet_hash}\n\
158             Ours:    {stored_hash}\n\
159             This likely means we have the wrong bytecode for Multicall3."
160        );
161    }
162
163    #[tokio::test]
164    #[ignore = "requires mainnet RPC access - not needed after mainnet launch"]
165    async fn createx_bytecode_matches_mainnet() {
166        // Verify our hash constant matches our bytecode
167        let computed_hash = keccak256(&CreateX::DEPLOYED_BYTECODE);
168        let stored_hash = contracts::CREATEX_BYTECODE_HASH;
169        assert_eq!(
170            computed_hash, stored_hash,
171            "CREATEX_BYTECODE_HASH does not match the actual bytecode!\n\
172             Computed: {computed_hash}\n\
173             Stored:   {stored_hash}"
174        );
175
176        // Verify our bytecode matches mainnet
177        let mainnet_hash = get_mainnet_code_hash(CREATEX_ADDRESS).await;
178        assert_eq!(
179            mainnet_hash, stored_hash,
180            "CreateX bytecode hash mismatch!\n\
181             Mainnet: {mainnet_hash}\n\
182             Ours:    {stored_hash}\n\
183             This likely means we have the wrong bytecode for CreateX."
184        );
185    }
186
187    #[tokio::test]
188    #[ignore = "requires mainnet RPC access - not needed after mainnet launch"]
189    async fn arachnid_create2_factory_bytecode_matches_mainnet() {
190        let mainnet_hash = get_mainnet_code_hash(ARACHNID_CREATE2_FACTORY_ADDRESS).await;
191        let our_hash = keccak256(&contracts::ARACHNID_CREATE2_FACTORY_BYTECODE);
192
193        assert_eq!(
194            mainnet_hash, our_hash,
195            "Arachnid CREATE2 factory bytecode hash mismatch!\n\
196             Mainnet: {mainnet_hash}\n\
197             Ours:    {our_hash}\n\
198             This likely means we have the wrong bytecode for Arachnid CREATE2 factory."
199        );
200    }
201
202    #[tokio::test]
203    #[ignore = "requires mainnet RPC access - not needed after mainnet launch"]
204    async fn safe_deployer_bytecode_matches_mainnet() {
205        let mainnet_hash = get_mainnet_code_hash(SAFE_DEPLOYER_ADDRESS).await;
206        let our_hash = keccak256(&SafeDeployer::DEPLOYED_BYTECODE);
207
208        assert_eq!(
209            mainnet_hash, our_hash,
210            "SafeDeployer bytecode hash mismatch!\n\
211             Mainnet: {mainnet_hash}\n\
212             Ours:    {our_hash}\n\
213             This likely means we have the wrong bytecode for SafeDeployer."
214        );
215    }
216}