stylus_tools/core/
check.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 std::{env, path::Path};
5
6use alloy::{primitives::Address, providers::Provider};
7use bytesize::ByteSize;
8
9use crate::{
10    core::{
11        activation::{self, ActivationConfig},
12        build::{build_contract, BuildConfig},
13        project::{
14            contract::{Contract, ContractStatus},
15            hash_project, ProjectConfig, ProjectHash,
16        },
17    },
18    utils::format_file_size,
19    wasm::process_wasm_file,
20};
21
22#[derive(Debug, Default)]
23pub struct CheckConfig {
24    pub activation: ActivationConfig,
25    pub build: BuildConfig,
26    pub project: ProjectConfig,
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum CheckError {
31    #[error("io error: {0}")]
32    Io(#[from] std::io::Error),
33    #[error("cargo metadata error: {0}")]
34    CargoMetadata(#[from] cargo_metadata::Error),
35
36    #[error("{0}")]
37    Activation(#[from] crate::core::activation::ActivationError),
38    #[error("{0}")]
39    Build(#[from] crate::core::build::BuildError),
40    #[error("{0}")]
41    Contract(#[from] crate::core::project::contract::ContractError),
42    #[error("{0}")]
43    Project(#[from] crate::core::project::ProjectError),
44    #[error("{0}")]
45    ProcessWasm(#[from] crate::wasm::ProcessWasmFileError),
46}
47
48/// Checks that a contract is valid and can be deployed onchain.
49///
50/// Returns whether the WASM is already up-to-date and activated onchain, and the data fee.
51pub async fn check_contract(
52    contract: &Contract,
53    address: Option<Address>,
54    config: &CheckConfig,
55    provider: &impl Provider,
56) -> Result<ContractStatus, CheckError> {
57    let wasm_file = build_contract(contract, &config.build)?;
58    let dir = env::current_dir()?;
59    let project_hash = hash_project(dir, &config.project, &config.build)?;
60    let status = check_wasm_file(&wasm_file, project_hash, address, config, provider).await?;
61    Ok(status)
62}
63
64pub async fn check_wasm_file(
65    wasm_file: impl AsRef<Path>,
66    project_hash: ProjectHash,
67    contract_address: Option<Address>,
68    config: &CheckConfig,
69    provider: &impl Provider,
70) -> Result<ContractStatus, CheckError> {
71    debug!(@grey, "reading wasm file at {}", wasm_file.as_ref().to_string_lossy().lavender());
72    let processed = process_wasm_file(wasm_file, project_hash)?;
73    info!(@grey, "contract size: {}", format_file_size(ByteSize::b(processed.code.len() as u64), ByteSize::kib(16), ByteSize::kib(24)));
74    debug!(@grey, "wasm size: {}", format_file_size(ByteSize::b(processed.wasm.len() as u64), ByteSize::kib(96), ByteSize::kib(128)));
75
76    // Check if the contract already exists
77    // TODO: check log
78    debug!(@grey, "connecting to RPC: {:?}", provider.root());
79    let codehash = processed.codehash();
80    if Contract::exists(codehash, &provider).await? {
81        return Ok(ContractStatus::Active {
82            code: processed.code,
83        });
84    }
85
86    let contract_address = contract_address.unwrap_or_else(Address::random);
87    let fee = activation::data_fee(
88        processed.code.clone(),
89        contract_address,
90        &config.activation,
91        provider,
92    )
93    .await?;
94    Ok(ContractStatus::Ready {
95        code: processed.code,
96        fee,
97    })
98}