stylus_tools/core/
verification.rs

1// Copyright 2025, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4use crate::core::deployment::deployer::StylusDeployer::deployCall;
5use crate::core::deployment::deployer::{stylus_constructorCall, ADDRESS};
6use crate::core::verification::VerificationError::{
7    InvalidDeployerAddress, InvalidInitData, TransactionReceiptError, TxNotSuccessful,
8};
9use crate::{
10    core::{deployment::prelude::DeploymentCalldata, project::contract::Contract, reflection},
11    utils::cargo,
12};
13
14use alloy::sol_types::SolCall;
15use alloy::{
16    consensus::Transaction,
17    primitives::{Address, TxHash},
18    providers::Provider,
19};
20
21pub async fn verify(
22    contract: &Contract,
23    tx_hash: TxHash,
24    skip_clean: bool,
25    provider: &impl Provider,
26) -> Result<VerificationStatus, VerificationError> {
27    let tx = provider
28        .get_transaction_by_hash(tx_hash)
29        .await?
30        .ok_or(VerificationError::NoCodeAtAddress)?;
31    if !skip_clean {
32        cargo::clean()?;
33    }
34    let deployment_success = provider
35        .get_transaction_receipt(tx_hash)
36        .await?
37        .map(|receipt| receipt.status())
38        .ok_or(TransactionReceiptError)?;
39    if !deployment_success {
40        return Err(TxNotSuccessful);
41    }
42    let status = contract.check(None, &Default::default(), provider).await?;
43    let deployment_data = DeploymentCalldata::new(status.code());
44
45    match tx.to() {
46        Some(deployer_address) => {
47            verify_constructor_deployment(tx.input(), &deployment_data, deployer_address)
48        }
49        _ => verify_create_deployment(&DeploymentCalldata(tx.input().to_vec()), &deployment_data),
50    }
51}
52
53fn verify_constructor_deployment(
54    tx_input: &[u8],
55    deployment_data: &DeploymentCalldata,
56    deployer_address: Address,
57) -> Result<VerificationStatus, VerificationError> {
58    let _constructor = reflection::constructor()?.ok_or(VerificationError::NoConstructor)?;
59    let deploy_call = deployCall::abi_decode(tx_input).unwrap();
60    let constructor_called = deploy_call
61        .initData
62        .starts_with(stylus_constructorCall::SELECTOR.as_slice());
63    if !constructor_called {
64        return Err(InvalidInitData);
65    }
66    if deployer_address != ADDRESS {
67        return Err(InvalidDeployerAddress);
68    }
69    verify_create_deployment(
70        &DeploymentCalldata(deploy_call.bytecode.to_vec()),
71        deployment_data,
72    )
73}
74
75fn verify_create_deployment(
76    calldata: &DeploymentCalldata,
77    deployment_data: &DeploymentCalldata,
78) -> Result<VerificationStatus, VerificationError> {
79    if deployment_data == calldata {
80        return Ok(VerificationStatus::Success);
81    }
82
83    let tx_prelude = calldata.prelude();
84    let build_prelude = deployment_data.prelude();
85    let prelude_mismatch = if tx_prelude == build_prelude {
86        None
87    } else {
88        Some(PreludeMismatch {
89            tx: hex::encode(tx_prelude),
90            build: hex::encode(build_prelude),
91        })
92    };
93
94    let tx_wasm_length = calldata.compressed_wasm().len();
95    let build_wasm_length = deployment_data.compressed_wasm().len();
96    Ok(VerificationStatus::Failure(VerificationFailure {
97        prelude_mismatch,
98        tx_wasm_length,
99        build_wasm_length,
100    }))
101}
102
103#[derive(Debug)]
104pub enum VerificationStatus {
105    Success,
106    Failure(VerificationFailure),
107}
108
109#[derive(Debug)]
110pub struct VerificationFailure {
111    pub prelude_mismatch: Option<PreludeMismatch>,
112    pub tx_wasm_length: usize,
113    pub build_wasm_length: usize,
114}
115
116#[derive(Debug)]
117pub struct PreludeMismatch {
118    pub tx: String,
119    pub build: String,
120}
121
122#[derive(Debug, thiserror::Error)]
123pub enum VerificationError {
124    #[error("RPC failed: {0}")]
125    Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
126
127    #[error("{0}")]
128    Check(#[from] crate::core::check::CheckError),
129    #[error("{0}")]
130    Reflection(#[from] crate::core::reflection::ReflectionError),
131    #[error("{0}")]
132    Command(#[from] crate::error::CommandError),
133
134    #[error("No code at address")]
135    NoCodeAtAddress,
136    #[error("Deployment transaction uses constructor but the local project doesn't have one")]
137    NoConstructor,
138    #[error("Invalid init data: Constructor not called")]
139    InvalidInitData,
140    #[error("Invalid deployer address")]
141    InvalidDeployerAddress,
142    #[error("Transaction receipt error")]
143    TransactionReceiptError,
144    #[error("Deployment transaction not successful")]
145    TxNotSuccessful,
146}