use alloy_network::{Network, ReceiptResponse, TransactionBuilder};
use alloy_primitives::{Address, B256, address, keccak256};
use alloy_provider::Provider;
use crate::abi::TridentAccount;
const CREATE2_FACTORY: Address = address!("4e59b44847b379578588920cA78FbF26c0B4956C");
pub const DEFAULT_ADDRESS: Address = address!("319A60309bA63b73624729Cc903CD6FeE78af696");
#[derive(Debug, thiserror::Error)]
pub enum DeployError {
#[error(transparent)]
Contract(#[from] alloy_contract::Error),
#[error("rpc: {0}")]
Rpc(Box<dyn std::error::Error + Send + Sync>),
#[error("create2 address mismatch: predicted {predicted}, observed {observed}")]
AddressMismatch {
predicted: Address,
observed: Address,
},
}
pub async fn deploy<P, N>(provider: P) -> Result<Address, DeployError>
where
P: Provider<N>,
N: Network,
{
let instance = TridentAccount::deploy(provider).await?;
Ok(*instance.address())
}
pub async fn deploy_deterministic<P, N>(provider: P, salt: B256) -> Result<Address, DeployError>
where
P: Provider<N>,
N: Network,
{
let predicted = predict_deterministic_address(salt);
let init_code = creation_code();
let mut input = Vec::with_capacity(32 + init_code.len());
input.extend_from_slice(salt.as_slice());
input.extend_from_slice(init_code);
let tx = <N::TransactionRequest as Default>::default()
.with_to(CREATE2_FACTORY)
.with_input(input);
let receipt = provider
.send_transaction(tx)
.await
.map_err(|e| DeployError::Rpc(Box::new(e)))?
.get_receipt()
.await
.map_err(|e| DeployError::Rpc(Box::new(e)))?;
if let Some(observed) = receipt.contract_address()
&& observed != predicted
{
return Err(DeployError::AddressMismatch {
predicted,
observed,
});
}
Ok(predicted)
}
pub fn predict_deterministic_address(salt: B256) -> Address {
CREATE2_FACTORY.create2(salt, keccak256(creation_code()))
}
fn creation_code() -> &'static [u8] {
TridentAccount::BYTECODE.as_ref()
}
#[cfg(test)]
mod tests {
use alloy_primitives::b256;
use alloy_provider::Provider;
use super::*;
use crate::test_util::TestSetup;
#[test]
fn default_address_matches_zero_salt_prediction() {
assert_eq!(DEFAULT_ADDRESS, predict_deterministic_address(B256::ZERO));
}
#[test]
fn predict_deterministic_address_is_pure_and_distinguishes_salts() {
let salt_a = b256!("1111111111111111111111111111111111111111111111111111111111111111");
let salt_b = b256!("2222222222222222222222222222222222222222222222222222222222222222");
assert_eq!(
predict_deterministic_address(salt_a),
predict_deterministic_address(salt_a),
);
assert_ne!(
predict_deterministic_address(salt_a),
predict_deterministic_address(salt_b),
);
}
#[tokio::test]
async fn deploy_places_code_at_returned_address() {
let setup = TestSetup::new().await;
let provider = setup.provider();
let addr = deploy(provider.clone()).await.expect("deploy");
let code = provider.get_code_at(addr).await.expect("get_code_at");
assert!(!code.is_empty(), "no code at deployed address");
}
#[tokio::test]
async fn deploy_deterministic_matches_prediction_and_places_code() {
let setup = TestSetup::new().await;
let provider = setup.provider();
let salt = b256!("abcdef0000000000000000000000000000000000000000000000000000000000");
let predicted = predict_deterministic_address(salt);
let deployed = deploy_deterministic(&provider, salt)
.await
.expect("deploy_deterministic");
assert_eq!(deployed, predicted);
let code = provider.get_code_at(deployed).await.expect("get_code_at");
assert!(!code.is_empty(), "no code at deterministic address");
}
#[tokio::test]
async fn deploy_deterministic_yields_same_address_across_chains() {
let setup_a = TestSetup::new().await;
let setup_b = TestSetup::new().await;
let salt = b256!("cafe000000000000000000000000000000000000000000000000000000000000");
let addr_a = deploy_deterministic(&setup_a.provider(), salt)
.await
.expect("deploy chain a");
let addr_b = deploy_deterministic(&setup_b.provider(), salt)
.await
.expect("deploy chain b");
assert_eq!(
addr_a, addr_b,
"same salt should yield same address across chains"
);
}
}