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