Skip to content

Commit

Permalink
Implement new commands build and check + introduce bundles (.cont…
Browse files Browse the repository at this point in the history
…ract files) (use-ink#97)

* Fix URIs

* Make `generate-metadata` output consistent with `build`

* Add `cargo contract pack`

* Return error instead of panicking

* Use blake2_hash()

* Replace match with if

* Pass reference instead of ownership

* Generate metadata.json and <contract>.pack

* Output .wasm, .json, .pack

* Return result object instead of tuple

* Get it to run with '--features test-ci-only'

* Rename .pack to .contract

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Introduce '--skip-packing' and '--skip-metadata'

* Apply suggestions from code review

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Short help message

* Output deprecated error for 'generate-metadata'

* Rename pack ➜ bundle

* Add 'cargo contract check' command

* Optimize resulting Wasm file, except on 'check'

* Do not make unoptimized file easily mistake for optimized one

* Get it to run with

* Update readme

* Make unoptimized wasm not show up in target folder

* Update comments

* Remove 'generate-metadata' variants

* Move dispatch logic into metadata

* Update src/main.rs

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Move logic into build.rs

* Improve progress output

* Make clippy happy

* Fix progress output

* Make it work with `--features test-ci-only`

* Apply cargo fmt

* Always use optimized Wasm for metadata hash

* Always use optimized Wasm for metadata hash

* Make it work with `--features test-ci-only`

* Switch naming

* Fix metadata/bundle output

* Use enum `BuildArtifacts` instead of boolean flags

* Improve misleading fn name

* Make it work with `--features test-ci-only`

* Make output more concise

* Print optimization result at the end

* Improve output

* Replace 5-tuple return value with struct

* Include hash only for bundle in metadata

* Make it work with `--features test-ci-only`

* Fix doc test

* Remove comments

* Introduce wrapper type CodeHash

* Make it work with `--features test-ci-only`

* Display important results bold

* Include size diff for `code-only` as well

* Remove comment

* Shorten code

* Clone metadata for correct UI output

* Remove unnecessary return

* Fix return type

* Print metadata generation in correct step

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
  • Loading branch information
Michael Müller and athei authored Nov 10, 2020
1 parent 4c25e3a commit 144ea27
Show file tree
Hide file tree
Showing 5 changed files with 495 additions and 112 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ OPTIONS:
SUBCOMMANDS:
new Setup and create a new smart contract project
build Compiles the smart contract
generate-metadata Generate contract metadata artifacts
build Compiles the contract, generates metadata, bundles both together in a '.contract' file
check Check that the code builds as Wasm; does not output any build artifact to the top level `target/` directory
test Test the smart contract off-chain
deploy Upload the smart contract code to the chain
instantiate Instantiate a deployed smart contract
Expand Down
96 changes: 78 additions & 18 deletions metadata/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
//!
//! let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
//! let compiler = SourceCompiler::new(Compiler::RustC, Version::parse("1.46.0-nightly").unwrap());
//! let source = Source::new([0u8; 32], language, compiler);
//! let wasm = SourceWasm::new(vec![0u8]);
//! let source = Source::new(Some(wasm), Some(CodeHash([0u8; 32])), language, compiler);
//! let contract = Contract::builder()
//! .name("incrementer".to_string())
//! .version(Version::new(2, 1, 0))
//! .authors(vec!["Parity Technologies <admin@parity.io>".to_string()])
//! .description("increment a value".to_string())
//! .documentation(Url::parse("http:docs.rs/").unwrap())
//! .repository(Url::parse("http:github.com/paritytech/ink/").unwrap())
//! .homepage(Url::parse("http:example.com/").unwrap())
//! .documentation(Url::parse("http://docs.rs/").unwrap())
//! .repository(Url::parse("http://github.com/paritytech/ink/").unwrap())
//! .homepage(Url::parse("http://example.com/").unwrap())
//! .license("Apache-2.0".to_string())
//! .build()
//! .unwrap();
Expand All @@ -60,7 +61,7 @@ use url::Url;
const METADATA_VERSION: &str = "0.1.0";

/// Smart contract metadata.
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct ContractMetadata {
#[serde(rename = "metadataVersion")]
metadata_version: semver::Version,
Expand Down Expand Up @@ -92,29 +93,86 @@ impl ContractMetadata {
abi,
}
}

pub fn remove_source_wasm_attribute(&mut self) {
self.source.wasm = None;
}
}

/// Representation of the Wasm code hash.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeHash(pub [u8; 32]);

impl Serialize for CodeHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_as_byte_str(&self.0[..], serializer)
}
}

#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct Source {
#[serde(serialize_with = "serialize_as_byte_str")]
hash: [u8; 32],
#[serde(skip_serializing_if = "Option::is_none")]
hash: Option<CodeHash>,
language: SourceLanguage,
compiler: SourceCompiler,
#[serde(skip_serializing_if = "Option::is_none")]
wasm: Option<SourceWasm>,
}

impl Source {
/// Constructs a new InkProjectSource.
pub fn new(hash: [u8; 32], language: SourceLanguage, compiler: SourceCompiler) -> Self {
pub fn new(
wasm: Option<SourceWasm>,
hash: Option<CodeHash>,
language: SourceLanguage,
compiler: SourceCompiler,
) -> Self {
Source {
hash,
language,
compiler,
wasm,
}
}
}

/// The bytes of the compiled Wasm smart contract.
#[derive(Clone, Debug)]
pub struct SourceWasm {
wasm: Vec<u8>,
}

impl SourceWasm {
/// Constructs a new `SourceWasm`.
pub fn new(wasm: Vec<u8>) -> Self {
SourceWasm { wasm }
}
}

impl Serialize for SourceWasm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_as_byte_str(&self.wasm[..], serializer)
}
}

impl Display for SourceWasm {
fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
write!(f, "0x").expect("failed writing to string");
for byte in &self.wasm {
write!(f, "{:02x}", byte).expect("failed writing to string");
}
write!(f, "")
}
}

/// The language and version in which a smart contract is written.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SourceLanguage {
language: Language,
version: Version,
Expand Down Expand Up @@ -143,7 +201,7 @@ impl Display for SourceLanguage {
}

/// The language in which the smart contract is written.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub enum Language {
Ink,
Solidity,
Expand All @@ -161,7 +219,7 @@ impl Display for Language {
}

/// A compiler used to compile a smart contract.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SourceCompiler {
compiler: Compiler,
version: Version,
Expand Down Expand Up @@ -189,7 +247,7 @@ impl SourceCompiler {
}

/// Compilers used to compile a smart contract.
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub enum Compiler {
RustC,
Solang,
Expand All @@ -205,7 +263,7 @@ impl Display for Compiler {
}

/// Metadata about a smart contract.
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct Contract {
name: String,
version: Version,
Expand All @@ -229,7 +287,7 @@ impl Contract {
}

/// Additional user defined metadata, can be any valid json.
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub struct User {
#[serde(flatten)]
json: Map<String, Value>,
Expand Down Expand Up @@ -463,7 +521,8 @@ mod tests {
let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
let compiler =
SourceCompiler::new(Compiler::RustC, Version::parse("1.46.0-nightly").unwrap());
let source = Source::new([0u8; 32], language, compiler);
let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]);
let source = Source::new(Some(wasm), Some(CodeHash([0u8; 32])), language, compiler);
let contract = Contract::builder()
.name("incrementer".to_string())
.version(Version::new(2, 1, 0))
Expand Down Expand Up @@ -507,7 +566,8 @@ mod tests {
"source": {
"hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"language": "ink! 2.1.0",
"compiler": "rustc 1.46.0-nightly"
"compiler": "rustc 1.46.0-nightly",
"wasm": "0x000102"
},
"contract": {
"name": "incrementer",
Expand Down Expand Up @@ -544,7 +604,7 @@ mod tests {
let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
let compiler =
SourceCompiler::new(Compiler::RustC, Version::parse("1.46.0-nightly").unwrap());
let source = Source::new([0u8; 32], language, compiler);
let source = Source::new(None, Some(CodeHash([0u8; 32])), language, compiler);
let contract = Contract::builder()
.name("incrementer".to_string())
.version(Version::new(2, 1, 0))
Expand Down
76 changes: 57 additions & 19 deletions src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::{
crate_metadata::CrateMetadata,
util,
workspace::{ManifestPath, Profile, Workspace},
UnstableFlags, Verbosity,
GenerateArtifacts, GenerationResult, OptimizationResult, UnstableFlags, Verbosity,
};
use anyhow::{Context, Result};
use colored::Colorize;
Expand Down Expand Up @@ -179,7 +179,7 @@ fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
///
/// The intention is to reduce the size of bloated wasm binaries as a result of missing
/// optimizations (or bugs?) between Rust and Wasm.
fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<OptimizationResult> {
let mut optimized = crate_metadata.dest_wasm.clone();
optimized.set_file_name(format!("{}-opt.wasm", crate_metadata.package_name));

Expand All @@ -206,14 +206,13 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> {

let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0;
let optimized_size = metadata(&optimized)?.len() as f64 / 1000.0;
println!(
" Original wasm size: {:.1}K, Optimized: {:.1}K",
original_size, optimized_size
);

// overwrite existing destination wasm file with the optimised version
std::fs::rename(&optimized, &crate_metadata.dest_wasm)?;
Ok(())
Ok(OptimizationResult {
original_size,
optimized_size,
})
}

/// Executes build of the smart-contract which produces a wasm binary that is ready for deploying.
Expand All @@ -227,57 +226,96 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
pub(crate) fn execute(
manifest_path: &ManifestPath,
verbosity: Option<Verbosity>,
optimize_contract: bool,
build_artifact: GenerateArtifacts,
unstable_options: UnstableFlags,
) -> Result<PathBuf> {
) -> Result<GenerationResult> {
let crate_metadata = CrateMetadata::collect(manifest_path)?;
execute_with_metadata(&crate_metadata, verbosity, unstable_options)
if build_artifact == GenerateArtifacts::CodeOnly {
let (maybe_dest_wasm, maybe_optimization_result) = execute_with_crate_metadata(
&crate_metadata,
verbosity,
optimize_contract,
build_artifact,
unstable_options,
)?;
let res = GenerationResult {
dest_wasm: maybe_dest_wasm,
dest_metadata: None,
dest_bundle: None,
target_directory: crate_metadata.cargo_meta.target_directory,
optimization_result: maybe_optimization_result,
};
return Ok(res);
}

let res =
super::metadata::execute(&manifest_path, verbosity, build_artifact, unstable_options)?;
Ok(res)
}

/// Executes build of the smart-contract which produces a wasm binary that is ready for deploying.
/// Executes build of the smart-contract which produces a Wasm binary that is ready for deploying.
///
/// It does so by invoking `cargo build` and then post processing the final binary.
///
/// # Note
///
/// Uses the supplied `CrateMetadata`. If an instance is not available use [`execute_build`]
pub(crate) fn execute_with_metadata(
///
/// Returns a tuple of `(maybe_optimized_wasm_path, maybe_optimization_result)`.
pub(crate) fn execute_with_crate_metadata(
crate_metadata: &CrateMetadata,
verbosity: Option<Verbosity>,
optimize_contract: bool,
build_artifact: GenerateArtifacts,
unstable_options: UnstableFlags,
) -> Result<PathBuf> {
) -> Result<(Option<PathBuf>, Option<OptimizationResult>)> {
println!(
" {} {}",
"[1/3]".bold(),
format!("[1/{}]", build_artifact.steps()).bold(),
"Building cargo project".bright_green().bold()
);
build_cargo_project(&crate_metadata, verbosity, unstable_options)?;
println!(
" {} {}",
"[2/3]".bold(),
format!("[2/{}]", build_artifact.steps()).bold(),
"Post processing wasm file".bright_green().bold()
);
post_process_wasm(&crate_metadata)?;
if !optimize_contract {
return Ok((None, None));
}
println!(
" {} {}",
"[3/3]".bold(),
format!("[3/{}]", build_artifact.steps()).bold(),
"Optimizing wasm file".bright_green().bold()
);
optimize_wasm(&crate_metadata)?;
Ok(crate_metadata.dest_wasm.clone())
let optimization_result = optimize_wasm(&crate_metadata)?;
Ok((
Some(crate_metadata.dest_wasm.clone()),
Some(optimization_result),
))
}

#[cfg(feature = "test-ci-only")]
#[cfg(test)]
mod tests {
use crate::{cmd, util::tests::with_tmp_dir, ManifestPath, UnstableFlags};
use crate::{cmd, util::tests::with_tmp_dir, GenerateArtifacts, ManifestPath, UnstableFlags};

#[test]
fn build_template() {
with_tmp_dir(|path| {
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let manifest_path =
ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap();
super::execute(&manifest_path, None, UnstableFlags::default()).expect("build failed");
super::execute(
&manifest_path,
None,
true,
GenerateArtifacts::All,
UnstableFlags::default(),
)
.expect("build failed");
Ok(())
})
}
Expand Down
Loading

0 comments on commit 144ea27

Please sign in to comment.