#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![deny(missing_docs)] use std::sync::Arc; use alloy_core::primitives::{hex::FromHex, Address, U256, Bytes, TxKind}; use alloy_consensus::{Signed, TxLegacy}; use alloy_sol_types::SolCall; use alloy_rpc_types_eth::{TransactionInput, TransactionRequest}; use alloy_transport::{TransportErrorKind, RpcError}; use alloy_simple_request_transport::SimpleRequest; use alloy_provider::{Provider, RootProvider}; #[rustfmt::skip] #[expect(warnings)] #[expect(needless_pass_by_value)] #[expect(clippy::all)] #[expect(clippy::ignored_unit_patterns)] #[expect(clippy::redundant_closure_for_method_calls)] mod abi { alloy_sol_macro::sol!("contracts/Deployer.sol"); } /// The Deployer contract for the Serai Router contract. /// /// This Deployer has a deterministic address, letting it be immediately identified on any instance /// of the EVM. It then supports retrieving the deployed contracts addresses (which aren't /// deterministic) using a single call. #[derive(Clone, Debug)] pub struct Deployer(Arc>); impl Deployer { /// Obtain the transaction to deploy this contract, already signed. /// /// The account this transaction is sent from (which is populated in `from`) must be sufficiently /// funded for this transaction to be submitted. This account has no known private key to anyone /// so ETH sent can be neither misappropriated nor returned. pub fn deployment_tx() -> Signed { pub const BYTECODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin")); let bytecode = Bytes::from_hex(BYTECODE).expect("compiled-in Deployer bytecode wasn't valid hex"); // Legacy transactions are used to ensure the widest possible degree of support across EVMs let tx = TxLegacy { chain_id: None, nonce: 0, // This uses a fixed gas price as necessary to achieve a deterministic address // The gas price is fixed to 100 gwei, which should be incredibly generous, in order to make // this getting stuck unlikely. While expensive, this only has to occur once gas_price: 100_000_000_000u128, // TODO: Use a more accurate gas limit gas_limit: 1_000_000u64, to: TxKind::Create, value: U256::ZERO, input: bytecode, }; ethereum_primitives::deterministically_sign(tx) } /// Obtain the deterministic address for this contract. pub fn address() -> Address { let deployer_deployer = Self::deployment_tx().recover_signer().expect("deployment_tx didn't have a valid signature"); Address::create(&deployer_deployer, 0) } /// Obtain the unsigned transaction to deploy a contract. /// /// This will not have its `nonce`, `gas_price`, nor `gas_limit` filled out. pub fn deploy_tx(init_code: Vec) -> TxLegacy { TxLegacy { chain_id: None, nonce: 0, gas_price: 0, gas_limit: 0, to: TxKind::Call(Self::address()), value: U256::ZERO, input: abi::Deployer::deployCall::new((init_code.into(),)).abi_encode().into(), } } /// Construct a new view of the Deployer. /// /// This will return `None` if the Deployer has yet to be deployed on-chain. pub async fn new( provider: Arc>, ) -> Result, RpcError> { let address = Self::address(); let code = provider.get_code_at(address).await?; // Contract has yet to be deployed if code.is_empty() { return Ok(None); } Ok(Some(Self(provider))) } /// Find the deployment of a contract. pub async fn find_deployment( &self, init_code_hash: [u8; 32], ) -> Result, RpcError> { let call = TransactionRequest::default().to(Self::address()).input(TransactionInput::new( abi::Deployer::deploymentsCall::new((init_code_hash.into(),)).abi_encode().into(), )); let bytes = self.0.call(&call).await?; let deployment = abi::Deployer::deploymentsCall::abi_decode_returns(&bytes, true) .map_err(|e| { TransportErrorKind::Custom( format!("node returned a non-address for function returning address: {e:?}").into(), ) })? ._0; if **deployment == [0; 20] { return Ok(None); } Ok(Some(deployment)) } }