diff --git a/README.md b/README.md index bdd0228..fcca0c6 100644 --- a/README.md +++ b/README.md @@ -156,24 +156,7 @@ See `--help` for all available flags and default values. ## Verifying Stylus Contracts -**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 contract 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. +See [here](https://hackmd.io/bpeMnrzbSvO4mohhvkrKqw) ## Deploying Non-Rust WASM Projects diff --git a/check/Dockerfile b/check/Dockerfile deleted file mode 100644 index cea0606..0000000 --- a/check/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM rust:1.71 as builder -COPY . . -RUN cargo build --release - -FROM debian:buster-slim -COPY --from=builder ./target/release/cargo-stylus ./target/release/cargo-stylus -CMD ["/target/release/cargo-stylus"] \ No newline at end of file diff --git a/check/src/cache.rs b/check/src/cache.rs index 7d907e1..f850e50 100644 --- a/check/src/cache.rs +++ b/check/src/cache.rs @@ -30,6 +30,7 @@ sol! { error AlreadyCached(bytes32 codehash); error BidTooSmall(uint192 bid, uint192 min); error BidsArePaused(); + error ProgramNotActivated(); } } @@ -88,6 +89,9 @@ pub async fn cache_contract(cfg: &CacheConfig) -> Result<()> { C::BidTooSmall(_) => { bail!("Bid amount {} (wei) too small", cfg.bid.unwrap_or_default()) } + C::ProgramNotActivated(_) => { + bail!("Your Stylus contract {} is not yet activated. To activate it, use the `cargo stylus activate` subcommand", hex::encode(contract)) + } } } let verbose = cfg.common_cfg.verbose; diff --git a/check/src/check.rs b/check/src/check.rs index 0716e62..e315d03 100644 --- a/check/src/check.rs +++ b/check/src/check.rs @@ -210,6 +210,12 @@ async fn contract_exists(codehash: B256, provider: &Provider) -> Result { + if outs.is_empty() { + bail!( + r#"No data returned from the ArbWasm precompile when checking if your Stylus contract exists. +Perhaps the Arbitrum node for the endpoint you are connecting to has not yet upgraded to Stylus"# + ); + } let ArbWasm::codehashVersionReturn { version } = ArbWasm::codehashVersionCall::abi_decode_returns(&outs, true)?; version diff --git a/check/src/constants.rs b/check/src/constants.rs index b923821..935d9b9 100644 --- a/check/src/constants.rs +++ b/check/src/constants.rs @@ -43,8 +43,3 @@ pub const PROJECT_HASH_SECTION_NAME: &str = "project_hash"; /// Name of the toolchain file used to specify the Rust toolchain version for a project. pub const TOOLCHAIN_FILE_NAME: &str = "rust-toolchain.toml"; - -/// Base Rust image version to be used for reproducible builds. This simply installs cargo and the Rust -/// compiler, but the user will specify the exact version of the Rust toolchain to use for building within -/// the docker container. -pub const RUST_BASE_IMAGE_VERSION: &str = "1.79.0"; diff --git a/check/src/deploy.rs b/check/src/deploy.rs index f146e0b..9c5d79e 100644 --- a/check/src/deploy.rs +++ b/check/src/deploy.rs @@ -146,7 +146,7 @@ impl DeployConfig { let tx_hash = receipt.transaction_hash.debug_lavender(); greyln!("deployment tx hash: {tx_hash}"); println!( - r#"we recommend running cargo stylus cache --address={} to cache your activated contract in ArbOS. + r#"INFO: Your program is not yet part of the Stylus contract cache. We recommend running `cargo stylus cache --address={}` to cache your activated contract in ArbOS. Cached contracts benefit from cheaper calls. To read more about the Stylus contract cache, see https://docs.arbitrum.io/stylus/concepts/stylus-cache-manager"#, hex::encode(contract) diff --git a/check/src/docker.rs b/check/src/docker.rs index 89a433d..fa96700 100644 --- a/check/src/docker.rs +++ b/check/src/docker.rs @@ -8,7 +8,7 @@ use std::process::{Command, Stdio}; use cargo_stylus_util::color::Color; use eyre::{bail, eyre, Result}; -use crate::constants::{RUST_BASE_IMAGE_VERSION, TOOLCHAIN_FILE_NAME}; +use crate::constants::TOOLCHAIN_FILE_NAME; use crate::macros::greyln; use crate::project::extract_toolchain_channel; @@ -45,15 +45,7 @@ fn create_image(version: &str) -> Result<()> { if image_exists(&name)? { return Ok(()); } - let cargo_stylus_version = env!("CARGO_PKG_VERSION"); - let cargo_stylus_version: String = cargo_stylus_version - .chars() - .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '.') - .collect(); - println!( - "Building Docker image for cargo-stylus version {} and Rust toolchain {}", - cargo_stylus_version, version, - ); + println!("Building Docker image for Rust toolchain {}", version,); let mut child = Command::new("docker") .arg("build") .arg("-t") @@ -62,24 +54,19 @@ fn create_image(version: &str) -> Result<()> { .arg("-f-") .stdin(Stdio::piped()) .spawn() - .map_err(|e| eyre!("failed to execure Docker command: {e}"))?; + .map_err(|e| eyre!("failed to execute Docker command: {e}"))?; write!( child.stdin.as_mut().unwrap(), "\ - FROM --platform=linux/amd64 rust:{} as builder\n\ + FROM --platform=linux/amd64 offchainlabs/cargo-stylus-base as base RUN rustup toolchain install {}-x86_64-unknown-linux-gnu RUN rustup default {}-x86_64-unknown-linux-gnu RUN rustup target add wasm32-unknown-unknown - RUN rustup target add wasm32-wasi - RUN rustup target add x86_64-unknown-linux-gnu - RUN cargo install cargo-stylus-check --version {} --force - RUN cargo install cargo-stylus --version {} --force + RUN rustup component add rust-src --toolchain {}-x86_64-unknown-linux-gnu ", - RUST_BASE_IMAGE_VERSION, version, version, - cargo_stylus_version, - cargo_stylus_version, + version, )?; child.wait().map_err(|e| eyre!("wait failed: {e}"))?; Ok(()) diff --git a/check/src/main.rs b/check/src/main.rs index 9f8912b..848bf93 100644 --- a/check/src/main.rs +++ b/check/src/main.rs @@ -34,7 +34,7 @@ struct Opts { #[derive(Parser, Debug, Clone)] enum Apis { - /// Create a new Rust project. + /// Create a new Stylus project. New { /// Project name. name: PathBuf, @@ -42,6 +42,12 @@ enum Apis { #[arg(long)] minimal: bool, }, + /// Initializes a Stylus project in the current directory. + Init { + /// Create a minimal contract. + #[arg(long)] + minimal: bool, + }, /// Export a Solidity ABI. ExportAbi { /// The output file (defaults to stdout). @@ -127,10 +133,6 @@ pub struct CheckConfig { /// Where to deploy and activate the contract (defaults to a random address). #[arg(long)] contract_address: Option, - /// If specified, will not run the command in a reproducible docker container. Useful for local - /// builds, but at the risk of not having a reproducible contract for verification purposes. - #[arg(long)] - no_verify: bool, } #[derive(Args, Clone, Debug)] @@ -143,6 +145,10 @@ struct DeployConfig { /// Only perform gas estimation. #[arg(long)] estimate_gas: bool, + /// If specified, will not run the command in a reproducible docker container. Useful for local + /// builds, but at the risk of not having a reproducible contract for verification purposes. + #[arg(long)] + no_verify: bool, } #[derive(Args, Clone, Debug)] @@ -206,7 +212,7 @@ impl fmt::Display for CheckConfig { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "{} {} {} {}", + "{} {} {}", self.common_cfg, match &self.wasm_file { Some(path) => format!("--wasm-file={}", path.display()), @@ -216,10 +222,6 @@ impl fmt::Display for CheckConfig { Some(addr) => format!("--contract-address={:?}", addr), None => "".to_string(), }, - match self.no_verify { - true => "--no-verify".to_string(), - false => "".to_string(), - }, ) } } @@ -228,13 +230,17 @@ impl fmt::Display for DeployConfig { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "{} {} {}", + "{} {} {} {}", self.check_config, self.auth, match self.estimate_gas { true => "--estimate-gas".to_string(), false => "".to_string(), }, + match self.no_verify { + true => "--no-verify".to_string(), + false => "".to_string(), + }, ) } } @@ -296,6 +302,9 @@ async fn main_impl(args: Opts) -> Result<()> { Apis::New { name, minimal } => { run!(new::new(&name, minimal), "failed to open new project"); } + Apis::Init { minimal } => { + run!(new::init(minimal), "failed to initialize project"); + } Apis::ExportAbi { json, output } => { run!(export_abi::export_abi(output, json), "failed to export abi"); } @@ -309,28 +318,16 @@ async fn main_impl(args: Opts) -> Result<()> { run!(cache::cache_contract(&config).await, "stylus cache failed"); } Apis::Check(config) => { - if config.no_verify { - run!(check::check(&config).await, "stylus checks failed"); - } else { - let mut commands: Vec = - vec![String::from("check"), String::from("--no-verify")]; - let config_args = config - .to_string() - .split(' ') - .map(|s| s.to_string()) - .filter(|s| !s.is_empty()) - .collect::>(); - commands.extend(config_args); - run!( - docker::run_reproducible(&commands), - "failed reproducible run" - ); - } + run!(check::check(&config).await, "stylus checks failed"); } Apis::Deploy(config) => { - if config.check_config.no_verify { + if config.no_verify { run!(deploy::deploy(config).await, "stylus deploy failed"); } else { + println!( + "Running in a Docker container for reproducibility, this may take a while", + ); + println!("NOTE: You can opt out by doing --no-verify"); let mut commands: Vec = vec![String::from("deploy"), String::from("--no-verify")]; let config_args = config @@ -350,6 +347,9 @@ async fn main_impl(args: Opts) -> Result<()> { if config.no_verify { run!(verify::verify(config).await, "failed to verify"); } else { + println!( + "Running in a Docker container for reproducibility, this may take a while", + ); let mut commands: Vec = vec![String::from("verify"), String::from("--no-verify")]; let config_args = config diff --git a/check/src/new.rs b/check/src/new.rs index 668df01..7fe0e1c 100644 --- a/check/src/new.rs +++ b/check/src/new.rs @@ -29,3 +29,31 @@ pub fn new(name: &Path, minimal: bool) -> Result<()> { println!("{GREY}new project at: {}", path.to_string_lossy().mint()); Ok(()) } + +pub fn init(minimal: bool) -> Result<()> { + let current_dir = current_dir().wrap_err("no current dir")?; + let repo = if minimal { + GITHUB_TEMPLATE_REPO_MINIMAL + } else { + GITHUB_TEMPLATE_REPO + }; + + let output = sys::new_command("git") + .arg("clone") + .arg("--depth") + .arg("1") + .arg(repo) + .arg(".") + .output() + .wrap_err("git clone failed")?; + + if !output.status.success() { + bail!("git clone command failed"); + } + + println!( + "{GREY}initialized project in: {}", + current_dir.to_string_lossy().mint() + ); + Ok(()) +} diff --git a/check/src/project.rs b/check/src/project.rs index ee327ec..8ea07ab 100644 --- a/check/src/project.rs +++ b/check/src/project.rs @@ -57,10 +57,6 @@ pub fn build_dylib(cfg: BuildConfig) -> Result { let mut cmd = sys::new_command("cargo"); - if !cfg.stable { - cmd.arg("+nightly"); - } - cmd.arg("build"); cmd.arg("--lib"); @@ -208,9 +204,6 @@ pub fn extract_toolchain_channel(toolchain_file_path: &PathBuf) -> Result, cfg: BuildConfig) -> Result<[u8; 32]> { let mut keccak = Keccak::v256(); let mut cmd = Command::new("cargo"); - if !cfg.stable { - cmd.arg("+nightly"); - } cmd.arg("--version"); let output = cmd .output() diff --git a/check/src/verify.rs b/check/src/verify.rs index 9c55f8b..2cc0084 100644 --- a/check/src/verify.rs +++ b/check/src/verify.rs @@ -54,7 +54,6 @@ pub async fn verify(cfg: VerifyConfig) -> eyre::Result<()> { common_cfg: cfg.common_cfg.clone(), wasm_file: None, contract_address: None, - no_verify: cfg.no_verify, }; let _ = check::check(&check_cfg) .await diff --git a/main/src/main.rs b/main/src/main.rs index 5240e1b..ab9a655 100644 --- a/main/src/main.rs +++ b/main/src/main.rs @@ -28,8 +28,11 @@ struct Opts { #[derive(Parser, Debug, Clone)] enum Subcommands { #[command(alias = "n")] - /// Create a new Rust project. + /// Create a new Stylus project. New, + #[command(alias = "i")] + /// Initializes a Stylus project in the current directory. + Init, #[command(alias = "x")] /// Export a Solidity ABI. ExportAbi, @@ -69,12 +72,15 @@ const COMMANDS: &[Binary] = &[ name: "cargo-stylus-check", apis: &[ "new", + "init", + "activate", "export-abi", "cache", "check", "deploy", "verify", "a", + "i", "n", "x", "c", diff --git a/replay/src/main.rs b/replay/src/main.rs index e74b465..79fd1da 100644 --- a/replay/src/main.rs +++ b/replay/src/main.rs @@ -147,7 +147,7 @@ async fn replay(args: ReplayArgs) -> Result<()> { let provider = sys::new_provider(&args.endpoint)?; let trace = Trace::new(provider, args.tx).await?; - build_so(&args.project, args.stable_rust)?; + build_so(&args.project)?; let so = find_so(&args.project)?; // TODO: don't assume the contract is top-level @@ -169,12 +169,9 @@ async fn replay(args: ReplayArgs) -> Result<()> { Ok(()) } -pub fn build_so(path: &Path, stable: bool) -> Result<()> { +pub fn build_so(path: &Path) -> Result<()> { let mut cargo = sys::new_command("cargo"); - if !stable { - cargo.arg("+nightly"); - } cargo .current_dir(path) .arg("build")