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

Switch to cdylibs #16

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ Please provide a summary of the changes and any backward incompatibilities.
- [ ] I have read the [DCO][DCO] and ensured that these changes comply.
- [ ] I assign this work under its [open source licensing][terms].

[DCO]: licenses/DCO.txt
[terms]: licenses/COPYRIGHT.md
[DCO]: https://github.com/OffchainLabs/cargo-stylus/licenses/DCO.txt
[terms]: https://github.com/OffchainLabs/cargo-stylus/licenses/COPYRIGHT.md
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-stylus"
version = "0.1.8"
version = "0.2.0"
edition = "2021"
authors = ["Offchain Labs"]
license = "MIT OR Apache-2.0"
Expand Down
11 changes: 5 additions & 6 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl std::fmt::Display for FileByteSize {
pub async fn run_checks(cfg: CheckConfig) -> eyre::Result<bool> {
let wasm_file_path: PathBuf = match &cfg.wasm_file_path {
Some(path) => PathBuf::from_str(path).unwrap(),
None => project::build_project_to_wasm(BuildConfig {
None => project::build_project_dylib(BuildConfig {
opt_level: project::OptLevel::default(),
nightly: cfg.nightly,
rebuild: true,
Expand All @@ -72,14 +72,13 @@ pub async fn run_checks(cfg: CheckConfig) -> eyre::Result<bool> {
};
println!("Reading WASM file at {}", wasm_file_path.display().grey());

let (precompressed_bytes, deploy_ready_code) =
project::get_compressed_wasm_bytes(&wasm_file_path)
.map_err(|e| eyre!("failed to get compressed WASM bytes: {e}"))?;
let (precompressed_bytes, init_code) = project::compress_wasm(&wasm_file_path)
.map_err(|e| eyre!("failed to get compressed WASM bytes: {e}"))?;

let precompressed_size = FileByteSize::new(precompressed_bytes.len() as u64);
println!("Uncompressed WASM size: {precompressed_size}");

let compressed_size = FileByteSize::new(deploy_ready_code.len() as u64);
let compressed_size = FileByteSize::new(init_code.len() as u64);
println!("Compressed WASM size to be deployed onchain: {compressed_size}",);

println!(
Expand Down Expand Up @@ -110,7 +109,7 @@ pub async fn run_checks(cfg: CheckConfig) -> eyre::Result<bool> {

expected_program_addr = get_contract_address(wallet.address(), nonce);
}
check_can_activate(provider, &expected_program_addr, deploy_ready_code).await
check_can_activate(provider, &expected_program_addr, init_code).await
}

/// Checks if a program can be successfully activated onchain before it is deployed
Expand Down
8 changes: 6 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ pub const ARBWASM_ACTIVATE_METHOD_HASH: &str = "58c780c2";
/// Target for compiled WASM folder in a Rust project
pub const RUST_TARGET: &str = "wasm32-unknown-unknown";

/// URL of the template repository to start a boilerplate Stylus project in Rust.
pub const GITHUB_TEMPLATE_REPOSITORY: &str = "https://github.com/OffchainLabs/stylus-hello-world";
/// The default repo to clone when creating new projects
pub const GITHUB_TEMPLATE_REPO: &str = "https://github.com/OffchainLabs/stylus-hello-world";

/// The minimal entrypoint repo
pub const GITHUB_TEMPLATE_REPO_MINIMAL: &str =
"https://github.com/OffchainLabs/stylus-hello-world-minimal";

/// The error returned from a call onchain if a program is up to date and requires no activation.
pub const PROGRAM_UP_TO_DATE_ERR: &str = "ProgramUpToDate";
8 changes: 4 additions & 4 deletions src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ on the --mode flag under cargo stylus deploy --help"#
}
};

// Whether or not to send the transactions to the endpoint.
// Whether to send the transactions to the endpoint.
let dry_run = cfg.tx_sending_opts.dry_run;

// If we are attempting to send a real transaction, we check if the deployer has any funds
Expand Down Expand Up @@ -113,21 +113,21 @@ programs to Stylus chains here https://docs.arbitrum.io/stylus/stylus-quickstart
if deploy {
let wasm_file_path: PathBuf = match &cfg.check_cfg.wasm_file_path {
Some(path) => PathBuf::from_str(path).unwrap(),
None => project::build_project_to_wasm(BuildConfig {
None => project::build_project_dylib(BuildConfig {
opt_level: project::OptLevel::default(),
nightly: cfg.check_cfg.nightly,
rebuild: false, // The check step at the start of this command rebuilt.
})
.map_err(|e| eyre!("could not build project to WASM: {e}"))?,
};
let (_, deploy_ready_code) = project::get_compressed_wasm_bytes(&wasm_file_path)?;
let (_, init_code) = project::compress_wasm(&wasm_file_path)?;
println!("");
println!("{}", "====DEPLOYMENT====".grey());
println!(
"Deploying program to address {}",
to_checksum(&expected_program_addr, None).mint(),
);
let deployment_calldata = program_deployment_calldata(&deploy_ready_code);
let deployment_calldata = program_deployment_calldata(&init_code);

// Output the tx data to a user's specified directory if desired.
if let Some(tx_data_output_dir) = output_dir {
Expand Down
134 changes: 13 additions & 121 deletions src/new.rs
Original file line number Diff line number Diff line change
@@ -1,79 +1,28 @@
// Copyright 2023, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md
use eyre::{bail, eyre};
use std::{
env::current_dir,
io::Write,
path::PathBuf,
process::{Command, Stdio},
};
use std::{env::current_dir, path::PathBuf};

use crate::{color::Color, constants::GITHUB_TEMPLATE_REPOSITORY};
use crate::{
color::Color,
constants::{GITHUB_TEMPLATE_REPO, GITHUB_TEMPLATE_REPO_MINIMAL},
util,
};

/// Initializes a new Stylus project in the current directory by git cloning
/// the stylus-hello-world template repository and renaming
/// it to the user's choosing.
/// Creates a new Stylus project in the current directory
pub fn new_stylus_project(name: &str, minimal: bool) -> eyre::Result<()> {
if name.is_empty() {
bail!("cannot have an empty project name");
}
let cwd: PathBuf = current_dir().map_err(|e| eyre!("could not get current dir: {e}"))?;
if minimal {
let output = Command::new("cargo")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.arg("new")
.arg("--bin")
.arg(name)
.output()
.map_err(|e| eyre!("failed to execute cargo new: {e}"))?;
if !output.status.success() {
bail!("cargo new command failed");
}

let cargo_config_dir_path = cwd.join(name).join(".cargo");
std::fs::create_dir_all(&cargo_config_dir_path)
.map_err(|e| eyre!("could not create .cargo folder in new project: {e}"))?;
let cargo_config_path = cargo_config_dir_path.join("config");
let mut f = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(cargo_config_path)
.map_err(|e| eyre!("could not open config file: {e}"))?;
f.write_all(cargo_config().as_bytes())
.map_err(|e| eyre!("could not write to file: {e}"))?;
f.flush()
.map_err(|e| eyre!("could not write to file: {e}"))?;

let main_path = cwd.join(name).join("src").join("main.rs");

// Overwrite the default main.rs file with the Stylus entrypoint.
let mut f = std::fs::OpenOptions::new()
.write(true)
.open(main_path)
.map_err(|e| eyre!("could not open main.rs file: {e}"))?;
f.write_all(basic_entrypoint().as_bytes())
.map_err(|e| eyre!("could not write to file: {e}"))?;
f.flush()
.map_err(|e| eyre!("could not write to file: {e}"))?;

// Overwrite the default Cargo.toml file.
let cargo_path = cwd.join(name).join("Cargo.toml");
let mut f = std::fs::OpenOptions::new()
.write(true)
.open(cargo_path)
.map_err(|e| eyre!("could not open Cargo.toml file: {e}"))?;
f.write_all(minimal_cargo_toml(name).as_bytes())
.map_err(|e| eyre!("could not write to file: {e}"))?;
f.flush()
.map_err(|e| eyre!("could not write to file: {e}"))?;
return Ok(());
}
let output = Command::new("git")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
let repo = match minimal {
true => GITHUB_TEMPLATE_REPO_MINIMAL,
false => GITHUB_TEMPLATE_REPO,
};
let output = util::new_command("git")
.arg("clone")
.arg(GITHUB_TEMPLATE_REPOSITORY)
.arg(repo)
.arg(name)
.output()
.map_err(|e| eyre!("failed to execute git clone: {e}"))?;
Expand All @@ -88,60 +37,3 @@ pub fn new_stylus_project(name: &str, minimal: bool) -> eyre::Result<()> {
);
Ok(())
}

fn basic_entrypoint() -> &'static str {
r#"#![no_main]
#![no_std]
extern crate alloc;

#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

use alloc::vec::Vec;

use stylus_sdk::stylus_proc::entrypoint;

#[entrypoint]
fn user_main(input: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
Ok(input)
}
"#
}

fn cargo_config() -> &'static str {
r#"[build]
target = "wasm32-unknown-unknown"

[target.wasm32-unknown-unknown]
rustflags = [
"-C", "link-arg=-zstack-size=32768",
]
"#
}

fn minimal_cargo_toml(name: &str) -> String {
format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"

[dependencies]
stylus-sdk = "0.4.1"
wee_alloc = "0.4.5"

[features]
export-abi = ["stylus-sdk/export-abi"]

[profile.release]
codegen-units = 1
strip = true
lto = true
panic = "abort"
opt-level = "s"

[workspace]
"#,
name
)
}
45 changes: 23 additions & 22 deletions src/project.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
// Copyright 2023, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md
use std::env::current_dir;
use std::io::Read;
use std::path::PathBuf;
use std::process::{Command, Stdio};

use brotli2::read::BrotliEncoder;
use bytesize::ByteSize;
use eyre::{bail, eyre};

use crate::constants::{MAX_PRECOMPRESSED_WASM_SIZE, MAX_PROGRAM_SIZE};
use crate::util;
use crate::{
color::Color,
constants::{BROTLI_COMPRESSION_LEVEL, EOF_PREFIX, RUST_TARGET},
};
use brotli2::read::BrotliEncoder;
use bytesize::ByteSize;
use eyre::{bail, eyre, Result};
use std::{env::current_dir, io::Read, path::PathBuf};

#[derive(Default, PartialEq)]
pub enum OptLevel {
Expand Down Expand Up @@ -55,12 +52,11 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#)
}

/// Build a Rust project to WASM and return the path to the compiled WASM file.
pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result<PathBuf> {
pub fn build_project_dylib(cfg: BuildConfig) -> Result<PathBuf> {
let cwd: PathBuf = current_dir().map_err(|e| eyre!("could not get current dir: {e}"))?;

if cfg.rebuild {
let mut cmd = Command::new("cargo");
cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
let mut cmd = util::new_command("cargo");

if cfg.nightly {
cmd.arg("+nightly");
Expand All @@ -69,6 +65,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result<PathBuf> {
}

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

if cfg.nightly {
cmd.arg("-Z");
Expand All @@ -84,7 +81,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result<PathBuf> {

let output = cmd
.arg("--release")
.arg(format!("--target={}", RUST_TARGET))
.arg(format!("--target={RUST_TARGET}"))
.output()
.map_err(|e| eyre!("failed to execute cargo build: {e}"))?;

Expand All @@ -93,13 +90,17 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result<PathBuf> {
}
}

let release_path = cwd.join("target").join(RUST_TARGET).join("release");
let release_path = cwd
.join("target")
.join(RUST_TARGET)
.join("release")
.join("deps");

// Gets the files in the release folder.
let release_files: Vec<PathBuf> = std::fs::read_dir(&release_path)
.map_err(|e| eyre!("could not read release dir: {e}"))?
.filter(|r| r.is_ok())
.map(|r| r.unwrap().path())
.map_err(|e| eyre!("could not read deps dir: {e}"))?
.filter_map(|r| r.ok())
.map(|r| r.path())
.filter(|r| r.is_file())
.collect();

Expand All @@ -113,7 +114,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result<PathBuf> {
})
.ok_or(BuildError::NoWasmFound { path: release_path })?;

if let Err(e) = get_compressed_wasm_bytes(&wasm_file_path) {
if let Err(e) = compress_wasm(&wasm_file_path) {
if let Some(BuildError::MaxCompressedSizeExceeded { got, .. }) = e.downcast_ref() {
match cfg.opt_level {
OptLevel::S => {
Expand All @@ -125,7 +126,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#,
got.red(),
);
// Attempt to build again with a bumped-up optimization level.
return build_project_to_wasm(BuildConfig {
return build_project_dylib(BuildConfig {
opt_level: OptLevel::Z,
nightly: cfg.nightly,
rebuild: true,
Expand All @@ -142,7 +143,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#,
);
// Attempt to build again with the nightly flag enabled and extra optimizations
// only available with nightly compilation.
return build_project_to_wasm(BuildConfig {
return build_project_dylib(BuildConfig {
opt_level: OptLevel::Z,
nightly: true,
rebuild: true,
Expand All @@ -162,7 +163,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#,
}

/// Reads a WASM file at a specified path and returns its brotli compressed bytes.
pub fn get_compressed_wasm_bytes(wasm_path: &PathBuf) -> eyre::Result<(Vec<u8>, Vec<u8>)> {
pub fn compress_wasm(wasm_path: &PathBuf) -> Result<(Vec<u8>, Vec<u8>)> {
let wasm_file_bytes = std::fs::read(wasm_path).map_err(|e| {
eyre!(
"could not read WASM file at target path {}: {e}",
Expand All @@ -172,13 +173,13 @@ pub fn get_compressed_wasm_bytes(wasm_path: &PathBuf) -> eyre::Result<(Vec<u8>,

let wasm_bytes = wasmer::wat2wasm(&wasm_file_bytes)
.map_err(|e| eyre!("could not parse wasm file bytes: {e}"))?;
let wasm_bytes = &*wasm_bytes;

let mut compressor = BrotliEncoder::new(wasm_bytes, BROTLI_COMPRESSION_LEVEL);
let mut compressor = BrotliEncoder::new(&*wasm_bytes, BROTLI_COMPRESSION_LEVEL);
let mut compressed_bytes = vec![];
compressor
.read_to_end(&mut compressed_bytes)
.map_err(|e| eyre!("could not Brotli compress WASM bytes: {e}"))?;

let mut deploy_ready_code = hex::decode(EOF_PREFIX).unwrap();
deploy_ready_code.extend(compressed_bytes);

Expand Down
Loading
Loading