Skip to content

Commit

Permalink
Reproducible Verification of Stylus Programs (#43)
Browse files Browse the repository at this point in the history
* port over verify repro

* verify working

* add items

* bump versions

* add arch

* get toolchains out

* empty private key done

* provide paths

* clippy

* clippy

* added info

* Update check/src/verify.rs

Co-authored-by: Joshua Colvin <[email protected]>

* ci

* Revert "ci"

This reverts commit 2e8d785.

---------

Co-authored-by: Joshua Colvin <[email protected]>
  • Loading branch information
rauljordan and joshuacolvin0 authored Jul 1, 2024
1 parent 7d013b9 commit 5fac3da
Show file tree
Hide file tree
Showing 13 changed files with 537 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, beta]
toolchain: [stable]
steps:
- uses: actions/checkout@v3
with:
Expand Down
27 changes: 14 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ resolver = "2"

[workspace.package]
authors = ["Offchain Labs"]
version = "0.3.1"
version = "0.3.2"
edition = "2021"
homepage = "https://arbitrum.io"
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -34,4 +34,4 @@ parking_lot = "0.12.1"
sneks = "0.1.2"

# members
cargo-stylus-util = { path = "util", version = "0.3.1" }
cargo-stylus-util = { path = "util", version = "0.3.2" }
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,27 @@ Usage: cargo stylus deploy [OPTIONS]

See `--help` for all available flags and default values.

## Verifying Stylus Programs

**cargo stylus verify**

Verifies that a deployed smart contract is identical to that produced by the
current project. Since Stylus smart contracts include a hash of all project
files, this additionally verifies that code comments and other files are
identical. To ensure build reproducibility, if a program is to be verified,
it should be both deployed and verified using `cargo stylus reproducible`.

See `--help` for all available flags and default values.

## Reproducibly Deploying and Verifying

**cargo stylus reproducible**

Runs a `cargo stylus` command in a Docker container to ensure build
reproducibility.

See `--help` for all available flags and default values.

## Deploying Non-Rust WASM Projects

The Stylus tool can also be used to deploy non-Rust, WASM projects to Stylus by specifying the WASM file directly with the `--wasm-file` flag to any of the cargo stylus commands.
Expand Down
2 changes: 2 additions & 0 deletions check/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] }
thiserror = "1.0.47"
tokio.workspace = true
wasmer = "3.1.0"
glob = "0.3.1"
tempfile = "3.10.1"
56 changes: 40 additions & 16 deletions check/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ sol! {
/// Checks that a program is valid and can be deployed onchain.
/// Returns whether the WASM is already up-to-date and activated onchain, and the data fee.
pub async fn check(cfg: &CheckConfig) -> Result<ProgramCheck> {
if cfg.endpoint == "https://stylus-testnet.arbitrum.io/rpc" {
if cfg.common_cfg.endpoint == "https://stylus-testnet.arbitrum.io/rpc" {
let version = "cargo stylus version 0.2.1".to_string().red();
bail!("The old Stylus testnet is no longer supported.\nPlease downgrade to {version}",);
}

let verbose = cfg.verbose;
let wasm = cfg.build_wasm().wrap_err("failed to build wasm")?;
let verbose = cfg.common_cfg.verbose;
let (wasm, project_hash) = cfg.build_wasm().wrap_err("failed to build wasm")?;

if verbose {
greyln!("reading wasm file at {}", wasm.to_string_lossy().lavender());
Expand All @@ -65,55 +65,79 @@ pub async fn check(cfg: &CheckConfig) -> Result<ProgramCheck> {

if verbose {
greyln!("wasm size: {}", format_file_size(wasm.len(), 96, 128));
greyln!("connecting to RPC: {}", &cfg.endpoint.lavender());
greyln!("connecting to RPC: {}", &cfg.common_cfg.endpoint.lavender());
}

// check if the program already exists
let provider = sys::new_provider(&cfg.endpoint)?;
let provider = sys::new_provider(&cfg.common_cfg.endpoint)?;
let codehash = alloy_primitives::keccak256(&code);

if program_exists(codehash, &provider).await? {
return Ok(ProgramCheck::Active(code));
return Ok(ProgramCheck::Active { code, project_hash });
}

let address = cfg.program_address.unwrap_or(H160::random());
let fee = check_activate(code.clone().into(), address, &provider).await?;
let visual_fee = format_data_fee(fee).unwrap_or("???".red());
greyln!("wasm data fee: {visual_fee}");
Ok(ProgramCheck::Ready(code, fee))
Ok(ProgramCheck::Ready {
code,
fee,
project_hash,
})
}

/// Whether a program is active, or needs activation.
#[derive(PartialEq)]
pub enum ProgramCheck {
/// Program already exists onchain.
Active(Vec<u8>),
Active {
code: Vec<u8>,
project_hash: [u8; 32],
},
/// Program can be activated with the given data fee.
Ready(Vec<u8>, U256),
Ready {
code: Vec<u8>,
fee: U256,
project_hash: [u8; 32],
},
}

impl ProgramCheck {
pub fn code(&self) -> &[u8] {
match self {
Self::Active(code) => code,
Self::Ready(code, _) => code,
Self::Active { code, .. } => code,
Self::Ready { code, .. } => code,
}
}

pub fn project_hash(&self) -> &[u8; 32] {
match self {
Self::Active { project_hash, .. } => project_hash,
Self::Ready { project_hash, .. } => project_hash,
}
}

pub fn suggest_fee(&self) -> U256 {
match self {
Self::Active(_) => U256::default(),
Self::Ready(_, data_fee) => data_fee * U256::from(120) / U256::from(100),
Self::Active { .. } => U256::default(),
Self::Ready { fee, .. } => fee * U256::from(120) / U256::from(100),
}
}
}

impl CheckConfig {
fn build_wasm(&self) -> Result<PathBuf> {
fn build_wasm(&self) -> Result<(PathBuf, [u8; 32])> {
if let Some(wasm) = self.wasm_file.clone() {
return Ok(wasm);
return Ok((wasm, [0u8; 32]));
}
project::build_dylib(BuildConfig::new(self.rust_stable))
let cfg = BuildConfig::new(self.common_cfg.rust_stable);
let project_hash = project::hash_files(
self.common_cfg.source_files_for_project_hash.clone(),
cfg.clone(),
)?;
let wasm = project::build_dylib(cfg)?;
Ok((wasm, project_hash))
}
}

Expand Down
Loading

0 comments on commit 5fac3da

Please sign in to comment.