Skip to content

Commit

Permalink
Miscellaneous Issue Resolution (#70)
Browse files Browse the repository at this point in the history
* misc fixes

* check cache on unactivated

* commentary

* edits

* add init command

* use cargo stylus base

* base rust

* misc fixes

* fix up docker

* better error

* cargo stylus check no docker

* edit

* use proper image

* comment
  • Loading branch information
rauljordan authored Aug 15, 2024
1 parent 28b7df4 commit 16ace97
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 93 deletions.
19 changes: 1 addition & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 0 additions & 7 deletions check/Dockerfile

This file was deleted.

4 changes: 4 additions & 0 deletions check/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ sol! {
error AlreadyCached(bytes32 codehash);
error BidTooSmall(uint192 bid, uint192 min);
error BidsArePaused();
error ProgramNotActivated();
}
}

Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions check/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ async fn contract_exists(codehash: B256, provider: &Provider<Http>) -> Result<bo

let program_version = match outs {
Ok(outs) => {
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
Expand Down
5 changes: 0 additions & 5 deletions check/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
2 changes: 1 addition & 1 deletion check/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 6 additions & 19 deletions check/src/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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")
Expand All @@ -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(())
Expand Down
58 changes: 29 additions & 29 deletions check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ struct Opts {

#[derive(Parser, Debug, Clone)]
enum Apis {
/// Create a new Rust project.
/// Create a new Stylus project.
New {
/// Project name.
name: PathBuf,
/// Create a minimal contract.
#[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).
Expand Down Expand Up @@ -127,10 +133,6 @@ pub struct CheckConfig {
/// Where to deploy and activate the contract (defaults to a random address).
#[arg(long)]
contract_address: Option<H160>,
/// 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)]
Expand All @@ -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)]
Expand Down Expand Up @@ -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()),
Expand All @@ -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(),
},
)
}
}
Expand All @@ -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(),
},
)
}
}
Expand Down Expand Up @@ -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");
}
Expand All @@ -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<String> =
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::<Vec<String>>();
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<String> =
vec![String::from("deploy"), String::from("--no-verify")];
let config_args = config
Expand All @@ -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<String> =
vec![String::from("verify"), String::from("--no-verify")];
let config_args = config
Expand Down
28 changes: 28 additions & 0 deletions check/src/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
7 changes: 0 additions & 7 deletions check/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ pub fn build_dylib(cfg: BuildConfig) -> Result<PathBuf> {

let mut cmd = sys::new_command("cargo");

if !cfg.stable {
cmd.arg("+nightly");
}

cmd.arg("build");
cmd.arg("--lib");

Expand Down Expand Up @@ -208,9 +204,6 @@ pub fn extract_toolchain_channel(toolchain_file_path: &PathBuf) -> Result<String
pub fn hash_files(source_file_patterns: Vec<String>, 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()
Expand Down
1 change: 0 additions & 1 deletion check/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 2 additions & 5 deletions replay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down

0 comments on commit 16ace97

Please sign in to comment.