Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major Changes to Reproducible Builds #53

Merged
merged 13 commits into from
Jul 16, 2024
196 changes: 151 additions & 45 deletions Cargo.lock

Large diffs are not rendered by default.

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

[workspace.package]
authors = ["Offchain Labs"]
version = "0.4.0"
version = "0.4.1"
edition = "2021"
homepage = "https://arbitrum.io"
license = "MIT OR Apache-2.0"
repository = "https://github.com/OffchainLabs/cargo-stylus"

[workspace.dependencies]
alloy-primitives = "0.7.2"
alloy-json-abi = "0.7.2"
alloy-sol-macro = "0.7.2"
alloy-sol-types = "0.7.2"
alloy-primitives = "0.7.6"
alloy-json-abi = "0.7.6"
alloy-sol-macro = "0.7.6"
alloy-sol-types = "0.7.6"
alloy-ethers-typecast = "0.2.0"
clap = { version = "4.5.4", features = [ "derive", "color" ] }
ethers = "2.0.10"
Expand All @@ -34,4 +34,4 @@ parking_lot = "0.12.1"
sneks = "0.1.2"

# members
cargo-stylus-util = { path = "util", version = "0.4.0" }
cargo-stylus-util = { path = "util", version = "0.4.1" }
4 changes: 4 additions & 0 deletions check/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ tokio.workspace = true
wasmer = "3.1.0"
glob = "0.3.1"
tempfile = "3.10.1"
wasmparser = "0.213.0"
wasm-encoder = "0.213.0"
wasm-gen = "0.1.4"
toml = "0.8.14"
49 changes: 19 additions & 30 deletions check/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,22 @@ pub async fn check(cfg: &CheckConfig) -> Result<ProgramCheck> {
greyln!("reading wasm file at {}", wasm.to_string_lossy().lavender());
}

let (wasm, code) = project::compress_wasm(&wasm).wrap_err("failed to compress WASM")?;
// Next, we include the project's hash as a custom section
// in the user's WASM so it can be verified by Cargo stylus'
// reproducible verification. This hash is added as a section that is
// ignored by WASM runtimes, so it will only exist in the file
// for metadata purposes.
// add_project_hash_to_wasm_file(wasm, project_hash)
let (wasm_file_bytes, code) =
project::compress_wasm(&wasm, project_hash).wrap_err("failed to compress WASM")?;

greyln!("contract size: {}", format_file_size(code.len(), 16, 24));

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

Expand All @@ -73,34 +83,23 @@ pub async fn check(cfg: &CheckConfig) -> Result<ProgramCheck> {
let codehash = alloy_primitives::keccak256(&code);

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

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,
project_hash,
})
Ok(ProgramCheck::Ready { code, fee })
}

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

impl ProgramCheck {
Expand All @@ -110,14 +109,6 @@ impl ProgramCheck {
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(),
Expand All @@ -132,11 +123,9 @@ impl CheckConfig {
return Ok((wasm, [0u8; 32]));
}
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)?;
let wasm = project::build_dylib(cfg.clone())?;
let project_hash =
project::hash_files(self.common_cfg.source_files_for_project_hash.clone(), cfg)?;
Ok((wasm, project_hash))
}
}
Expand Down
7 changes: 7 additions & 0 deletions check/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ pub const GITHUB_TEMPLATE_REPO_MINIMAL: &str =

/// One ether in wei.
pub const ONE_ETH: U256 = U256([1000000000000000000, 0, 0, 0]);

/// Name of the custom wasm section that is added to contracts deployed with cargo stylus
/// to include a hash of the Rust project's source files for reproducible verification of builds.
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";
26 changes: 18 additions & 8 deletions check/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ pub async fn deploy(cfg: DeployConfig) -> Result<()> {
}
}

let contract = cfg
.deploy_contract(program.code(), program.project_hash(), sender, &client)
.await?;
let contract = cfg.deploy_contract(program.code(), sender, &client).await?;

match program {
ProgramCheck::Ready { .. } => cfg.activate(sender, contract, data_fee, &client).await?,
Expand All @@ -98,11 +96,10 @@ impl DeployConfig {
async fn deploy_contract(
&self,
code: &[u8],
project_hash: &[u8; 32],
sender: H160,
client: &SignerClient,
) -> Result<H160> {
let init_code = program_deployment_calldata(code, project_hash);
let init_code = program_deployment_calldata(code);

let tx = Eip1559TransactionRequest::new()
.from(sender)
Expand Down Expand Up @@ -231,27 +228,40 @@ pub async fn run_tx(
}

/// Prepares an EVM bytecode prelude for contract creation.
pub fn program_deployment_calldata(code: &[u8], hash: &[u8; 32]) -> Vec<u8> {
pub fn program_deployment_calldata(code: &[u8]) -> Vec<u8> {
let mut code_len = [0u8; 32];
U256::from(code.len()).to_big_endian(&mut code_len);
let mut deploy: Vec<u8> = vec![];
deploy.push(0x7f); // PUSH32
deploy.extend(code_len);
deploy.push(0x80); // DUP1
deploy.push(0x60); // PUSH1
deploy.push(42 + 1 + 32); // prelude + version + hash
deploy.push(42 + 1); // prelude + version
deploy.push(0x60); // PUSH1
deploy.push(0x00);
deploy.push(0x39); // CODECOPY
deploy.push(0x60); // PUSH1
deploy.push(0x00);
deploy.push(0xf3); // RETURN
deploy.push(0x00); // version
deploy.extend(hash);
deploy.extend(code);
deploy
}

pub fn extract_program_evm_deployment_prelude(calldata: &[u8]) -> Vec<u8> {
// The length of the prelude, version part is 42 + 1 as per the code
let metadata_length = 42 + 1;
// Extract and return the metadata part
calldata[0..metadata_length].to_vec()
}

pub fn extract_compressed_wasm(calldata: &[u8]) -> Vec<u8> {
// The length of the prelude, version part is 42 + 1 as per the code
let metadata_length = 42 + 1;
// Extract and return the metadata part
calldata[metadata_length..].to_vec()
}

pub fn format_gas(gas: U256) -> String {
let gas: u64 = gas.try_into().unwrap_or(u64::MAX);
let text = format!("{gas} gas");
Expand Down
20 changes: 17 additions & 3 deletions check/src/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md

use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

use eyre::{bail, eyre, Result};

use crate::constants::TOOLCHAIN_FILE_NAME;
use crate::project::extract_toolchain_channel;

fn version_to_image_name(version: &str) -> String {
format!("cargo-stylus-{}", version)
}
Expand All @@ -24,6 +28,8 @@ fn create_image(version: &str) -> Result<()> {
if image_exists(&name)? {
return Ok(());
}
let toolchain_file_path = PathBuf::from(".").as_path().join(TOOLCHAIN_FILE_NAME);
let toolchain_channel = extract_toolchain_channel(&toolchain_file_path)?;
let mut child = Command::new("docker")
.arg("build")
.arg("-t")
Expand All @@ -37,15 +43,19 @@ fn create_image(version: &str) -> Result<()> {
child.stdin.as_mut().unwrap(),
"\
FROM rust:{} as builder\n\
RUN rustup toolchain install {} && rustup default {}
RUN rustup target add wasm32-unknown-unknown
RUN rustup target add wasm32-wasi
RUN rustup target add aarch64-unknown-linux-gnu
RUN rustup target add x86_64-unknown-linux-gnu
RUN cargo install cargo-stylus
RUN cargo install --force cargo-stylus-check
RUN cargo install --force cargo-stylus-replay
RUN cargo install --force cargo-stylus-cgen
",
version
version,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm that this version variable has also been cleaned to prevent injection attacks? @rauljordan

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Fixed

toolchain_channel,
toolchain_channel,
)?;
child.wait().map_err(|e| eyre!("wait failed: {e}"))?;
Ok(())
Expand Down Expand Up @@ -76,10 +86,14 @@ fn run_in_docker_container(version: &str, command_line: &[&str]) -> Result<()> {
}

pub fn run_reproducible(version: &str, command_line: &[String]) -> Result<()> {
let version: String = version
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added here @rory-ocl

.chars()
.filter(|c| c.is_alphanumeric() || *c == '.')
.collect();
let mut command = vec!["cargo", "stylus"];
for s in command_line.iter() {
command.push(s);
}
create_image(version)?;
run_in_docker_container(version, &command)
create_image(&version)?;
run_in_docker_container(&version, &command)
}
Loading
Loading