diff --git a/CHANGELOG.md b/CHANGELOG.md index 3614efb16..40ad19d50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Standardised verifiable builds - [#1148](https://github.com/paritytech/cargo-contract/pull/1148) + ### Changed -- Dry-run result output improvements [1123](https://github.com/paritytech/cargo-contract/pull/1123) +- Dry-run result output improvements - [1123](https://github.com/paritytech/cargo-contract/pull/1123) - Display build progress with --output-json, print to stderr [1211](https://github.com/paritytech/cargo-contract/pull/1211) ### Fixed @@ -17,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.0.1] ### Fixed -- `[contract-build]` flush the remaining buffered bytes [1118](https://github.com/paritytech/cargo-contract/pull/1118) +- `[contract-build]` flush the remaining buffered bytes - [1118](https://github.com/paritytech/cargo-contract/pull/1118) ## [3.0.0] @@ -28,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Contracts are build as `bin` crate now (we used `cdylib` before) - [#1076](https://github.com/paritytech/cargo-contract/pull/1076) - BREAKING CHANGE: Make sure that your contract is `no_main` by having this on top of your contract: - `#![cfg_attr(not(feature = "std"), no_std, no_main)]` - - This will be detected and suggested for `error[E0601]` [#1113](https://github.com/paritytech/cargo-contract/pull/1113) + - This will be detected and suggested for `error[E0601]` - [#1113](https://github.com/paritytech/cargo-contract/pull/1113) - Update contracts node metadata (#1105) - Compatible with `substrate-contracts-node 0.25.0-a2b09462c7c` diff --git a/Cargo.lock b/Cargo.lock index 1ec8583e5..a61d1a9d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,77 +198,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", - "tokio", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix 0.37.22", - "slab", - "socket2", - "waker-fn", -] - [[package]] name = "async-lock" version = "2.7.0" @@ -278,56 +207,17 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "async-trait" -version = "0.1.69" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - [[package]] name = "autocfg" version = "1.1.0" @@ -469,18 +359,42 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.3.1" +name = "bollard" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "af254ed2da4936ef73309e9597180558821cb16ae9bba4cb24ce6b612d8d80ed" dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", + "base64 0.21.2", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http", + "hyper", + "hyperlocal", "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.42.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602bda35f33aeb571cef387dcd4042c643a8bf689d8aaac2cc47ea24cb7bc7e0" +dependencies = [ + "serde", + "serde_with", ] [[package]] @@ -557,13 +471,12 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "once_cell", - "regex-automata 0.1.10", + "regex-automata 0.3.2", "serde", ] @@ -634,7 +547,6 @@ version = "3.0.1" dependencies = [ "anyhow", "assert_cmd", - "async-std", "clap", "colored", "contract-build", @@ -648,6 +560,7 @@ dependencies = [ "substrate-build-script-utils", "subxt", "tempfile", + "tokio", "tracing", "tracing-subscriber 0.3.17", "url", @@ -701,6 +614,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", + "serde", "winapi", ] @@ -736,7 +650,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -782,12 +696,16 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "2.2.0" +name = "console" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ - "crossbeam-utils", + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", ] [[package]] @@ -802,6 +720,7 @@ version = "3.0.1" dependencies = [ "anyhow", "blake2", + "bollard", "cargo_metadata", "clap", "colored", @@ -810,9 +729,11 @@ dependencies = [ "heck", "hex", "impl-serde", + "indicatif", "parity-scale-codec", "parity-wasm", "pretty_assertions", + "regex", "rustc_version", "semver", "serde", @@ -820,9 +741,12 @@ dependencies = [ "strum 0.25.0", "tempfile", "term_size", + "tokio", + "tokio-stream", "toml 0.7.6", "tracing", "url", + "users", "wabt", "walkdir", "wasm-opt", @@ -835,7 +759,6 @@ name = "contract-extrinsics" version = "3.0.1" dependencies = [ "anyhow", - "async-std", "clap", "colored", "contract-build", @@ -853,6 +776,7 @@ dependencies = [ "sp-runtime", "sp-weights", "subxt", + "tokio", "tracing", "url", ] @@ -927,9 +851,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1031,9 +955,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88abab2f5abbe4c56e8f1fb431b784d710b709888f35755a160e62e33fe38e8" +checksum = "e928d50d5858b744d1ea920b790641129c347a770d1530c3a85b77705a5ee031" dependencies = [ "cc", "cxxbridge-flags", @@ -1043,9 +967,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0c11acd0e63bae27dcd2afced407063312771212b7a823b4fd72d633be30fb" +checksum = "8332ba63f8a8040ca479de693150129067304a3496674477fff6d0c372cc34ae" dependencies = [ "cc", "codespan-reporting", @@ -1053,24 +977,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] name = "cxxbridge-flags" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3816ed957c008ccd4728485511e3d9aaf7db419aa321e3d2c5a2f3411e36c8" +checksum = "5966a5a87b6e9bb342f5fab7170a93c77096efe199872afffc4b477cfeb86957" [[package]] name = "cxxbridge-macro" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26acccf6f445af85ea056362561a24ef56cdc15fcc685f03aec50b9c702cb6d" +checksum = "81b2dab6991c7ab1572fea8cb049db819b1aeea1e2dac74c0869f244d9f21a7c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1118,7 +1042,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1140,7 +1064,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core 0.20.1", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1292,6 +1216,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "env_logger" version = "0.10.0" @@ -1477,21 +1407,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.28" @@ -1500,7 +1415,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -1599,18 +1514,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.3.20" @@ -1677,9 +1580,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1807,6 +1710,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyperlocal" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" +dependencies = [ + "futures-util", + "hex", + "hyper", + "pin-project", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1902,6 +1818,19 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "indicatif" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "ink" version = "4.2.1" @@ -1948,7 +1877,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -2005,7 +1934,7 @@ dependencies = [ "itertools 0.10.5", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -2020,7 +1949,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "synstructure", ] @@ -2124,12 +2053,12 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.2", + "rustix 0.38.3", "windows-sys 0.48.0", ] @@ -2357,15 +2286,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2468,9 +2388,6 @@ name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -dependencies = [ - "value-bag", -] [[package]] name = "mach" @@ -2511,7 +2428,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.37.22", + "rustix 0.37.23", ] [[package]] @@ -2644,6 +2561,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.30.4" @@ -2752,12 +2675,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.12.1" @@ -2828,7 +2745,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -2850,20 +2767,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] -name = "polling" -version = "2.8.0" +name = "portable-atomic" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" [[package]] name = "ppv-lite86" @@ -2970,9 +2877,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -3103,22 +3010,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85d07b1a5f16b5548f4255a978c94259971aff73f39e8d67e8250e8b2f6667c3" +checksum = "1641819477c319ef452a075ac34a4be92eb9ba09f6841f62d594d50fdcf0bf6b" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a930b010d9effee5834317bb7ff406b76af7724348fd572b38705b4bd099fa92" +checksum = "68bf53dad9b6086826722cdc99140793afd9f62faa14a1ad07eb4f955e7a7216" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -3270,9 +3177,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.14" +version = "0.36.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62" +checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" dependencies = [ "bitflags 1.3.2", "errno", @@ -3284,9 +3191,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.22" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", @@ -3298,9 +3205,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.2" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabcb0461ebd01d6b79945797c27f8529082226cb630a9865a71870ff63532a4" +checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" dependencies = [ "bitflags 2.3.3", "errno", @@ -3323,9 +3230,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" dependencies = [ "log", "ring", @@ -3356,9 +3263,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" dependencies = [ "ring", "untrusted", @@ -3595,9 +3502,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "764cad9e7e1ca5fe15b552859ff5d96a314e6ed2934f2260168cd5dfa5891409" [[package]] name = "sct" @@ -3694,22 +3601,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.168" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d614f89548720367ded108b3c843be93f3a341e22d5674ca0dd5cd57f34926af" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.168" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fe589678c688e44177da4f27152ee2d190757271dc7f1d5b6b9f68d869d641" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -3723,6 +3630,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -3732,6 +3650,33 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "time", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -3833,9 +3778,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -3959,7 +3904,7 @@ checksum = "c7f531814d2f16995144c74428830ccf7d94ff4a7749632b83ad8199b181140c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4090,7 +4035,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4281,7 +4226,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4361,7 +4306,7 @@ dependencies = [ "quote", "scale-info", "subxt-metadata", - "syn 2.0.23", + "syn 2.0.25", "thiserror", "tokio", ] @@ -4375,7 +4320,7 @@ dependencies = [ "darling 0.20.1", "proc-macro-error", "subxt-codegen", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4404,9 +4349,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -4421,7 +4366,7 @@ checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "unicode-xid", ] @@ -4447,7 +4392,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix 0.37.22", + "rustix 0.37.23", "windows-sys 0.48.0", ] @@ -4493,7 +4438,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4506,6 +4451,33 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + [[package]] name = "tiny-bip39" version = "1.0.0" @@ -4566,7 +4538,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4586,7 +4558,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.2", + "rustls 0.21.3", "tokio", ] @@ -4685,7 +4657,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -4868,6 +4840,16 @@ dependencies = [ "serde", ] +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -4886,12 +4868,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" - [[package]] name = "version_check" version = "0.9.4" @@ -4930,12 +4906,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.3" @@ -4988,22 +4958,10 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.87" @@ -5022,7 +4980,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5196,7 +5154,7 @@ dependencies = [ "memoffset", "paste", "rand 0.8.5", - "rustix 0.36.14", + "rustix 0.36.15", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", @@ -5429,9 +5387,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" dependencies = [ "memchr", ] @@ -5480,7 +5438,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] diff --git a/README.md b/README.md index a793c74d2..98e3e1ad5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ Modern releases of gcc and clang, as well as Visual Studio 2019+ should work. * (MacOS) `brew install openssl` * `cargo install cargo-dylint dylint-link`. +* Step 4: (**Optional**) Install [Docker Engine](https://docs.docker.com/engine/install) +to be able to produce verifiable builds. + You can always update the `cargo-contract` binary to the latest version by running the Step 2. ### Installation using Docker Image @@ -76,6 +79,16 @@ docker run --rm -it -v ${pwd}:/sources paritytech/contracts-ci-linux:production If you want to reproduce other steps of CI process you can use the following [guide](https://github.com/paritytech/scripts#reproduce-ci-locally). +### Verifiable builds + +Some block explorers require the Wasm binary to be compiled in the deterministic environment. +To achieve it, you should build your contract using Docker image we provide: +```bash +cargo contract build --verifiable +``` + +You can find more detailed documentation how to use the image [here](/build-image/README.md) + ## Usage You can always use `cargo contract help` to print information on available diff --git a/build-image/.dockerignore b/build-image/.dockerignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/build-image/.dockerignore @@ -0,0 +1 @@ +target/ diff --git a/build-image/Dockerfile b/build-image/Dockerfile new file mode 100644 index 000000000..94e8b4eba --- /dev/null +++ b/build-image/Dockerfile @@ -0,0 +1,111 @@ +FROM docker.io/bitnami/minideb:bullseye-amd64 as slimmed-rust + +# The rust version to use +# The 1.69 toolchain is temporarily required to build ink! contracts because of +# https://github.com/paritytech/cargo-contract/issues/1139 +ARG RUST_VERSION=1.69 +# The cargo contract version to use +ARG CARGO_CONTRACT_VERSION=3.0.1 +# Url to the cargo-contract repository to install from +ARG CARGO_CONTRACT_GIT +# Branch to use in git repository +ARG CARGO_CONTRACT_BRANCH +# Commit to use in git repository +ARG CARGO_CONTRACT_REV +# Tag to use in git repository +ARG CARGO_CONTRACT_TAG +# gcc package version +ARG GCC_VERSION=4:10.2.1-1 +# wget package version +ARG WGET_VERSION=1.21-1+deb11u1 +# g++ package version +ARG G_VERSION=4:10.2.1-1 +ARG MUSL_V=1.2.2-1 + +# metadata +LABEL io.parity.image.vendor="Parity Technologies" \ + io.parity.image.title="paritytech/contracts-verifiable" \ + io.parity.image.description="Inherits from docker.io/bitnami/minideb:bullseye. \ + rust nightly, clippy, rustfmt, miri, rust-src, rustc-dev, grcov, rust-covfix, \ + llvm-tools-preview, cargo-contract, xargo, binaryen, parallel, codecov, ink, solang" \ + io.parity.image.documentation="https://github.com/paritytech/cargo-contract/blob/master/\ + build-image/README.md" \ + io.parity.version.rust=${RUST_VERSION} \ + io.parity.version.cargo-contract.version=${CARGO_CONTRACT_VERSION} \ + io.parity.version.cargo-contract.git.repository=${CARGO_CONTRACT_GIT} \ + io.parity.version.cargo-contract.git.branch=${CARGO_CONTRACT_BRANCH} \ + io.parity.version.cargo-contract.git.revision=${CARGO_CONTRACT_REV} \ + io.parity.version.cargo-contract.git.tag=${CARGO_CONTRACT_TAG} \ + io.parity.version.gcc=${GCC_VERSION} \ + io.parity.version.wget=${WGET_VERSION} \ + io.parity.version.g_plus_plus=${G_VERSION} \ + io.parity.version.musl=${MUSL_V} + +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH + +# Minimal Rust dependencies. +RUN set -eux \ + && apt-get update && apt-get -y install wget=${WGET_VERSION} \ + && url="https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init" \ + && wget "$url" \ + && chmod +x rustup-init \ + && ./rustup-init -y --no-modify-path --profile minimal --component rust-src rustfmt --default-toolchain $RUST_VERSION \ + && rm rustup-init \ + && chmod -R a+w $RUSTUP_HOME $CARGO_HOME \ + && rustup --version \ + && cargo --version \ + && rustc --version \ + && apt-get remove -y --auto-remove wget \ + && apt-get -y install gcc=${GCC_VERSION} \ + && rm -rf /var/lib/apt/lists/* + +FROM slimmed-rust as cc-builder +ARG CARGO_CONTRACT_VERSION +ARG GCC_VERSION +ARG G_VERSION +ARG MUSL_V +ARG CARGO_CONTRACT_BRANCH +ARG CARGO_CONTRACT_TAG +ARG CARGO_CONTRACT_REV +ARG CARGO_CONTRACT_GIT + +# This is important, see https://github.com/rust-lang/docker-rust/issues/85 +ENV RUSTFLAGS="-C target-feature=-crt-static" + +# Install required packages for `cargo-contract` +RUN apt-get -y update && apt-get -y install gcc=${GCC_VERSION} g++=${G_VERSION} musl-dev=${MUSL_V} \ + # Install cargo contract from git if the arg is set + && if [ -n "$CARGO_CONTRACT_GIT" ]; then \ + COMMAND="cargo install --git ${CARGO_CONTRACT_GIT}" ; \ + else \ + COMMAND="cargo install cargo-contract --locked --version ${CARGO_CONTRACT_VERSION}" ;\ + fi \ + && if [ -n "$CARGO_CONTRACT_BRANCH" ] && [ -n "$CARGO_CONTRACT_GIT" ]; then \ + COMMAND="${COMMAND} --branch ${CARGO_CONTRACT_BRANCH}" ; \ + fi \ + && if [ -n "$CARGO_CONTRACT_REV" ] && [ -n "$CARGO_CONTRACT_GIT" ]; then \ + COMMAND="${COMMAND} --rev ${CARGO_CONTRACT_REV}" ; \ + fi \ + && if [ -n "$CARGO_CONTRACT_TAG" ] && [ -n "$CARGO_CONTRACT_GIT" ]; then \ + COMMAND="cargo install --git ${CARGO_CONTRACT_GIT} --tag ${CARGO_CONTRACT_TAG}" ; \ + fi \ + && echo "Executing ${COMMAND}" \ + && eval "${COMMAND}" \ + # Cleanup after `cargo install` + && rm -rf ${CARGO_HOME}/"registry" ${CARGO_HOME}/"git" /root/.cache/sccache \ + # apt clean up + && apt-get remove -y gnupg \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +FROM slimmed-rust as ink-dev + +COPY --from=cc-builder /usr/local/cargo/bin/cargo-contract /usr/local/bin/cargo-contract + +WORKDIR /contract + +# default entry point +ENTRYPOINT ["cargo", "contract"] diff --git a/build-image/README.md b/build-image/README.md new file mode 100644 index 000000000..241da3394 --- /dev/null +++ b/build-image/README.md @@ -0,0 +1,52 @@ +# Verifiable builds using Docker + +Docker image based on the minimalistic Debian image `bitnami/minideb:bullseye-amd64`. + +Used for reproducible builds in `cargo contract build --verifiable` + +**Rust versions:** + +Currently, the 1.69 toolchain is temporarily required to build ink! contracts because of https://github.com/paritytech/cargo-contract/issues/1139 + +**Rust tools & toolchains:** + +We use stable releases from crates.io + +- `cargo-contract` +- `wasm32-unknown-unknown`: The toolchain to compile Rust codebases for Wasm. + +[Click here](https://hub.docker.com/repository/docker/paritytech/contracts-verifiable) for the registry. + +## Usage + +The default entry point is `ENTRYPOINT [ "cargo", "contract", "build", "--release" ]` +and work directory is set to `/contract`. You need to mount the folder with the contract to the container. + +```bash +docker run -d \ + --name ink-container \ + --mount type=bind,source="$(pwd)",target="/contract" \ + paritytech/contracts-verified +``` + +For multi-contract projects, like in the example below: +``` +my-app/ +├─ ink-project-a/ +│ ├─ Cargo.toml +│ ├─ lib.rs +├─ ink-project-b/ +│ ├─ Cargo.toml +│ ├─ lib.rs +├─ rust-toolchain +``` +Make sure to run the command inside `my-app` directory and specify a relative manifest path +to the root contract: +`cargo contract build --verifiable --release --manifest-path ink-project-a/Cargo.toml` + + +**Apple Silicon performance** + +It is a known issue that running AMD64 image on the ARM host architecture significantly impacts the performance +and build times. To solve this issues, enable _Use Rosetta for x86/amd64 emulation on Apple Silicon_ in +_Settings_ -> _Features in development_ tab in Docker Desktop App. diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 472605ebb..5ef8df051 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -14,6 +14,7 @@ keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"] include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE", "build.rs", "templates", "riscv_memory_layout.ld"] [dependencies] +regex = "1" anyhow = "1.0.71" blake2 = "0.10.6" cargo_metadata = "0.15.4" @@ -38,9 +39,16 @@ wasm-opt = "0.113.0" which = "4.4.0" zip = { version = "0.6.6", default-features = false } strum = { version = "0.25", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio-stream = "0.1" +bollard = "0.14" +indicatif = "0.17" contract-metadata = { version = "3.0.1", path = "../metadata" } +[target.'cfg(unix)'.dependencies] +users = "0.11" + [build-dependencies] anyhow = "1.0.71" walkdir = "2.3.3" diff --git a/crates/build/README.md b/crates/build/README.md index ad051ff54..b8feec1ad 100644 --- a/crates/build/README.md +++ b/crates/build/README.md @@ -1,6 +1,6 @@ # contract-build -A crate for building [`ink!`](https://github.com/paritytech/ink) smart contracts. Used by +A crate for building [`ink!`](https://github.com/paritytech/ink) smart contracts. Used by [`cargo-contract`](https://github.com/paritytech/cargo-contract). ## Usage @@ -17,6 +17,7 @@ use contract_build::{ OutputType, UnstableFlags, Target, + ImageVariant, }; let manifest_path = ManifestPath::new("my-contract/Cargo.toml").unwrap(); @@ -36,7 +37,8 @@ let args = contract_build::ExecuteArgs { skip_wasm_validation: false, target: Target::Wasm, max_memory_pages: 16, + image: ImageVariant::Default, }; contract_build::execute(args); -``` \ No newline at end of file +``` diff --git a/crates/build/src/args.rs b/crates/build/src/args.rs index 80169e082..869476af5 100644 --- a/crates/build/src/args.rs +++ b/crates/build/src/args.rs @@ -45,7 +45,7 @@ impl TryFrom<&VerbosityFlags> for Verbosity { } /// Denotes if output should be printed to stdout. -#[derive(Clone, Copy, Default, serde::Serialize, Eq, PartialEq)] +#[derive(Clone, Copy, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq)] pub enum Verbosity { /// Use default output #[default] @@ -89,7 +89,15 @@ impl Network { /// Describes which artifacts to generate #[derive( - Copy, Clone, Default, Eq, PartialEq, Debug, clap::ValueEnum, serde::Serialize, + Copy, + Clone, + Default, + Eq, + PartialEq, + Debug, + clap::ValueEnum, + serde::Serialize, + serde::Deserialize, )] #[clap(name = "build-artifacts")] pub enum BuildArtifacts { @@ -223,8 +231,10 @@ pub enum BuildMode { /// Functionality to output debug messages is build into the contract. #[default] Debug, - /// The contract is build without any debugging functionality. + /// The contract is built without any debugging functionality. Release, + /// the contract is built in release mode and in a deterministic environment. + Verifiable, } impl fmt::Display for BuildMode { @@ -232,6 +242,7 @@ impl fmt::Display for BuildMode { match self { Self::Debug => write!(f, "debug"), Self::Release => write!(f, "release"), + Self::Verifiable => write!(f, "verifiable"), } } } diff --git a/crates/build/src/docker.rs b/crates/build/src/docker.rs new file mode 100644 index 000000000..e9c35018f --- /dev/null +++ b/crates/build/src/docker.rs @@ -0,0 +1,559 @@ +// Copyright 2018-2023 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// cargo-contract is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// cargo-contract is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with cargo-contract. If not, see . + +//! This module provides a simple interface to execute the verifiable build +//! inside the docker container. +//! +//! For the correct behaviour, the docker engine must be running, +//! and the socket to be accessible. +//! +//! It is also important that the docker registry contains the tag +//! that matches the current version of this crate. +//! +//! The process of the build is following: +//! 1. Pull the image from the registry or use the local copy if available +//! 2. Parse other arguments that were passed to the host execution context +//! 3. Calculate the digest of the command and use it +//! to uniquely identify the container +//! 4. If the container exists, we just start the build, if not, we create it +//! 5. After the build, the docker container produces metadata with +//! paths relative to its internal storage structure, we parse the file +//! and overwrite those paths relative to the host machine. + +use std::{ + collections::{ + hash_map::DefaultHasher, + HashMap, + }, + hash::{ + Hash, + Hasher, + }, + io::{ + BufReader, + Write, + }, + path::Path, + time::Duration, +}; + +use anyhow::{ + Context, + Result, +}; +use bollard::{ + container::{ + AttachContainerOptions, + AttachContainerResults, + Config, + CreateContainerOptions, + ListContainersOptions, + LogOutput, + }, + image::{ + CreateImageOptions, + ListImagesOptions, + }, + service::{ + HostConfig, + ImageSummary, + Mount, + MountTypeEnum, + }, + Docker, +}; +use contract_metadata::ContractMetadata; +use indicatif::{ + ProgressBar, + ProgressStyle, +}; +use tokio_stream::StreamExt; + +use crate::{ + verbose_eprintln, + BuildArtifacts, + BuildResult, + BuildSteps, + CrateMetadata, + ExecuteArgs, + Verbosity, +}; + +use colored::Colorize; +/// Default image to be used for the build. +const IMAGE: &str = "paritytech/contracts-verifiable"; +/// We assume the docker image contains the same tag as the current version of the crate. +const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// The default directory to be mounted in the container. +const MOUNT_DIR: &str = "/contract"; + +/// The image to be used. +#[derive(Clone, Debug, Default)] +pub enum ImageVariant { + /// The default image is used, specified in the `IMAGE` constant. + #[default] + Default, + /// Custom image is used. + Custom(String), +} + +/// Launches the docker container to execute verifiable build. +pub fn docker_build(args: ExecuteArgs) -> Result { + let ExecuteArgs { + manifest_path, + verbosity, + output_type, + target, + build_artifact, + image, + .. + } = args; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()? + .block_on(async { + let mut build_steps = BuildSteps::new(); + + build_steps.set_total_steps(3); + if build_artifact == BuildArtifacts::CodeOnly { + build_steps.set_total_steps(2); + } + + let crate_metadata = CrateMetadata::collect(&manifest_path, target)?; + let host_folder = std::env::current_dir()?; + let args = compose_build_args()?; + + let client = Docker::connect_with_socket_defaults().map_err(|e| { + anyhow::anyhow!("{}\nDo you have the docker engine installed in path?", e) + })?; + let _ = client.ping().await.map_err(|e| { + anyhow::anyhow!("{}\nIs your docker engine up and running?", e) + })?; + + let image = match image { + ImageVariant::Custom(i) => i.clone(), + ImageVariant::Default => { + format!("{}:{}", IMAGE, VERSION) + } + }; + + let container = create_container( + &client, + args.clone(), + &image, + &crate_metadata.contract_artifact_name, + &host_folder, + &verbosity, + &mut build_steps, + ) + .await?; + + let mut build_result = + run_build(&client, &container, &verbosity, &mut build_steps).await?; + + update_build_result(&host_folder, &mut build_result)?; + + update_metadata(&build_result, &verbosity, &mut build_steps, &image, &client) + .await?; + + build_steps.increment_current(); + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + "Displaying results".bright_cyan().bold(), + ); + + Ok(BuildResult { + output_type, + verbosity, + ..build_result + }) + }) +} + +/// Updates `build_result` paths to the artefacts. +fn update_build_result(host_folder: &Path, build_result: &mut BuildResult) -> Result<()> { + let new_path = host_folder.join( + build_result + .target_directory + .as_path() + .strip_prefix(MOUNT_DIR)?, + ); + build_result.target_directory = new_path; + + let new_path = build_result.dest_wasm.as_ref().map(|p| { + host_folder.join( + p.as_path() + .strip_prefix(MOUNT_DIR) + .expect("cannot strip prefix"), + ) + }); + build_result.dest_wasm = new_path; + + build_result.metadata_result.as_mut().map(|m| { + m.dest_bundle = host_folder.join( + m.dest_bundle + .as_path() + .strip_prefix(MOUNT_DIR) + .expect("cannot strip prefix"), + ); + m.dest_metadata = host_folder.join( + m.dest_metadata + .as_path() + .strip_prefix(MOUNT_DIR) + .expect("cannot strip prefix"), + ); + m + }); + Ok(()) +} + +/// Overwrites `build_result` and `image` fields in the metadata. +async fn update_metadata( + build_result: &BuildResult, + verbosity: &Verbosity, + build_steps: &mut BuildSteps, + build_image: &str, + client: &Docker, +) -> Result<()> { + if let Some(metadata_artifacts) = &build_result.metadata_result { + let mut metadata = ContractMetadata::load(&metadata_artifacts.dest_bundle)?; + + let build_image = find_local_image(client, build_image.to_string()) + .await? + .context("Image summary does not exist")?; + // find alternative unique identifier of the image, otherwise grab the digest + let image_tag = match build_image + .repo_tags + .iter() + .find(|t| !t.ends_with("latest")) + { + Some(tag) => tag.to_owned(), + None => build_image.id.clone(), + }; + + metadata.image = Some(image_tag); + + crate::metadata::write_metadata( + metadata_artifacts, + metadata, + build_steps, + verbosity, + true, + )?; + } + Ok(()) +} + +/// Searches for the local copy of the docker image. +async fn find_local_image( + client: &Docker, + image: String, +) -> Result> { + let images = client + .list_images(Some(ListImagesOptions:: { + all: true, + ..Default::default() + })) + .await?; + let build_image = images.iter().find(|i| i.repo_tags.contains(&image)); + + Ok(build_image.cloned()) +} + +/// Creates the container, returning the container id if successful. +/// +/// If the image is not available locally, it will be pulled from the registry. +async fn create_container( + client: &Docker, + mut build_args: Vec, + build_image: &str, + contract_name: &str, + host_folder: &Path, + verbosity: &Verbosity, + build_steps: &mut BuildSteps, +) -> Result { + let entrypoint = vec!["cargo".to_string(), "contract".to_string()]; + + let mut cmd = vec![ + "build".to_string(), + "--release".to_string(), + "--output-json".to_string(), + ]; + + cmd.append(&mut build_args); + + let digest_code = container_digest(cmd.clone(), build_image.to_string()); + let container_name = + format!("ink-verified-{}-{}", contract_name, digest_code.clone()); + + let mut filters = HashMap::new(); + filters.insert("name".to_string(), vec![container_name.clone()]); + + let containers = client + .list_containers(Some(ListContainersOptions:: { + all: true, + filters, + ..Default::default() + })) + .await?; + + let container_option = containers.first(); + + if container_option.is_some() { + return Ok(container_name) + } + + let mount = Mount { + target: Some(String::from(MOUNT_DIR)), + source: Some( + host_folder + .to_str() + .context("Cannot convert path to string.")? + .to_string(), + ), + typ: Some(MountTypeEnum::BIND), + ..Default::default() + }; + let host_cfg = Some(HostConfig { + mounts: Some(vec![mount]), + ..Default::default() + }); + + let user; + #[cfg(unix)] + { + user = Some(format!( + "{}:{}", + users::get_current_uid(), + users::get_current_gid() + )); + }; + #[cfg(windows)] + { + user = None; + } + + let config = Config { + image: Some(build_image.to_string()), + entrypoint: Some(entrypoint), + cmd: Some(cmd), + host_config: host_cfg, + attach_stderr: Some(true), + user, + ..Default::default() + }; + let options = Some(CreateContainerOptions { + name: container_name.as_str(), + platform: Some("linux/amd64"), + }); + + match client + .create_container(options.clone(), config.clone()) + .await + { + Ok(_) => Ok(container_name), + Err(err) => { + if matches!( + err, + bollard::errors::Error::DockerResponseServerError { + status_code: 404, + .. + } + ) { + // no such image locally, so pull and try again + pull_image(client, build_image.to_string(), verbosity, build_steps) + .await?; + client + .create_container(options, config) + .await + .context("Failed to create docker container") + .map(|_| container_name) + } else { + Err(err.into()) + } + } + } +} + +/// Starts the container and executed the build inside it. +async fn run_build( + client: &Docker, + container_name: &str, + verbosity: &Verbosity, + build_steps: &mut BuildSteps, +) -> Result { + client + .start_container::(container_name, None) + .await?; + + let AttachContainerResults { mut output, .. } = client + .attach_container( + container_name, + Some(AttachContainerOptions:: { + stdout: Some(true), + stderr: Some(true), + stream: Some(true), + ..Default::default() + }), + ) + .await?; + + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + format!("Started the build inside the container: {}", container_name) + .bright_cyan() + .bold(), + ); + + // pipe docker attach output into stdout + let stderr = std::io::stderr(); + let mut stderr = stderr.lock(); + let mut build_result = None; + while let Some(Ok(output)) = output.next().await { + match output { + LogOutput::StdOut { message } => { + build_result = Some( + serde_json::from_reader(BufReader::new(message.as_ref())) + .context("Error decoding BuildResult"), + ); + } + LogOutput::StdErr { message } => { + stderr.write_all(message.as_ref())?; + stderr.flush()?; + } + LogOutput::Console { message: _ } => { + panic!("LogOutput::Console") + } + LogOutput::StdIn { message: _ } => panic!("LogOutput::StdIn"), + } + } + + if let Some(build_result) = build_result { + build_result + } else { + Err(anyhow::anyhow!( + "Failed to read build result from docker build" + )) + } +} + +/// Takes CLI args from the host and appends them to the build command inside the docker. +fn compose_build_args() -> Result> { + use regex::Regex; + let mut args: Vec = Vec::new(); + // match --image with arg with 1 or more white spaces surrounded + let rex = Regex::new(r#"--image[ ]*[^ ]*[ ]*"#)?; + // we join the args together, so we can remove `--image ` + let args_string: String = std::env::args().collect::>().join(" "); + let args_string = rex.replace_all(&args_string, "").to_string(); + + // and then we turn it back to the vec, filtering out commands and arguments + // that should not be passed to the docker build command + let mut os_args: Vec = args_string + .split_ascii_whitespace() + .filter(|a| { + a != &"--verifiable" + && !a.contains("cargo-contract") + && a != &"cargo" + && a != &"contract" + && a != &"build" + && a != &"--output-json" + }) + .map(|s| s.to_string()) + .collect(); + + args.append(&mut os_args); + + Ok(args) +} + +/// Pulls the docker image from the registry. +async fn pull_image( + client: &Docker, + image: String, + verbosity: &Verbosity, + build_steps: &mut BuildSteps, +) -> Result<()> { + let mut pull_image_stream = client.create_image( + Some(CreateImageOptions { + from_image: image, + ..Default::default() + }), + None, + None, + ); + + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + "Image does not exist. Pulling one from the registry" + .bright_cyan() + .bold() + ); + build_steps.increment_current(); + + if verbosity.is_verbose() { + let spinner_style = ProgressStyle::with_template( + " {spinner:.cyan} [{wide_bar:.cyan/blue}]\n {wide_msg}", + )? + .progress_chars("#>-") + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "); + let pb = ProgressBar::new(1000); + pb.set_style(spinner_style); + pb.enable_steady_tick(Duration::from_millis(100)); + + while let Some(summary_result) = pull_image_stream.next().await { + let summary = summary_result?; + + if let Some(progress_detail) = summary.progress_detail { + let total = progress_detail.total.map_or(1000, |v| v) as u64; + let current_step = progress_detail.current.map_or(1000, |v| v) as u64; + pb.set_length(total); + pb.set_position(current_step); + + if let Some(msg) = summary.status { + pb.set_message(msg); + } + } + } + + pb.finish(); + } else { + while pull_image_stream.next().await.is_some() {} + } + + Ok(()) +} + +/// Calculates the unique container's code. +fn container_digest(entrypoint: Vec, image_digest: String) -> String { + // in order to optimise the container usage + // we are hashing the inputted command + // in order to reuse the container for the same permutation of arguments + let mut s = DefaultHasher::new(); + // the data is set of commands and args and the image digest + let data = (entrypoint, image_digest); + data.hash(&mut s); + let digest = s.finish(); + // taking the first 5 digits to be a unique identifier + let digest_code: String = digest.to_string().chars().take(5).collect(); + digest_code +} diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs index 57b85bbf1..c832d935a 100644 --- a/crates/build/src/lib.rs +++ b/crates/build/src/lib.rs @@ -21,6 +21,7 @@ use which as _; mod args; mod crate_metadata; +mod docker; pub mod metadata; mod new; #[cfg(test)] @@ -68,6 +69,10 @@ pub use self::{ }; use crate::wasm_opt::WasmOptHandler; +pub use docker::{ + docker_build, + ImageVariant, +}; use anyhow::{ Context, @@ -119,6 +124,7 @@ pub struct ExecuteArgs { pub skip_wasm_validation: bool, pub target: Target, pub max_memory_pages: u32, + pub image: ImageVariant, } impl Default for ExecuteArgs { @@ -138,12 +144,13 @@ impl Default for ExecuteArgs { skip_wasm_validation: Default::default(), target: Default::default(), max_memory_pages: DEFAULT_MAX_MEMORY_PAGES, + image: Default::default(), } } } /// Result of the build process. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct BuildResult { /// Path to the resulting Wasm file. pub dest_wasm: Option, @@ -159,8 +166,10 @@ pub struct BuildResult { pub build_artifact: BuildArtifacts, /// The verbosity flags. pub verbosity: Verbosity, + /// Image used for the verifiable build + pub image: Option, /// The type of formatting to use for the build output. - #[serde(skip_serializing)] + #[serde(skip_serializing, skip_deserializing)] pub output_type: OutputType, } @@ -263,11 +272,11 @@ fn exec_cargo_for_onchain_target( crate_metadata: &CrateMetadata, command: &str, features: &Features, - build_mode: BuildMode, - network: Network, - verbosity: Verbosity, + build_mode: &BuildMode, + network: &Network, + verbosity: &Verbosity, unstable_flags: &UnstableFlags, - target: Target, + target: &Target, ) -> Result<()> { let cargo_build = |manifest_path: &ManifestPath| { let target_dir = format!( @@ -285,7 +294,7 @@ fn exec_cargo_for_onchain_target( network.append_to_args(&mut args); let mut features = features.clone(); - if build_mode == BuildMode::Debug { + if build_mode == &BuildMode::Debug { features.push("ink/ink-debug"); } else { args.push("-Zbuild-std-features=panic_immediate_abort".to_owned()); @@ -327,7 +336,7 @@ fn exec_cargo_for_onchain_target( }; let cargo = - util::cargo_cmd(command, &args, manifest_path.directory(), verbosity, env); + util::cargo_cmd(command, &args, manifest_path.directory(), *verbosity, env); invoke_cargo_and_scan_for_error(cargo) }; @@ -683,20 +692,24 @@ pub fn execute(args: ExecuteArgs) -> Result { build_artifact, unstable_flags, optimization_passes, - keep_debug_symbols, lint, output_type, - skip_wasm_validation, target, - max_memory_pages, - } = args; + .. + } = &args; + + // if image exists, then --verifiable was called and we need to build inside docker. + if build_mode == &BuildMode::Verifiable { + return docker_build(args) + } // The CLI flag `optimization-passes` overwrites optimization passes which are // potentially defined in the `Cargo.toml` profile. let optimization_passes = match optimization_passes { - Some(opt_passes) => opt_passes, + Some(opt_passes) => *opt_passes, None => { let mut manifest = Manifest::new(manifest_path.clone())?; + match manifest.profile_optimization_passes() { // if no setting is found, neither on the cli nor in the profile, // then we use the default @@ -706,162 +719,13 @@ pub fn execute(args: ExecuteArgs) -> Result { } }; - let crate_metadata = CrateMetadata::collect(&manifest_path, target)?; + let crate_metadata = CrateMetadata::collect(manifest_path, *target)?; - assert_compatible_ink_dependencies(&manifest_path, verbosity)?; - if build_mode == BuildMode::Debug { + assert_compatible_ink_dependencies(manifest_path, *verbosity)?; + if build_mode == &BuildMode::Debug { assert_debug_mode_supported(&crate_metadata.ink_version)?; } - let maybe_lint = |steps: &mut BuildSteps| -> Result<()> { - let total_steps = build_artifact.steps(); - if lint { - steps.set_total_steps(total_steps + 1); - verbose_eprintln!( - verbosity, - " {} {}", - format!("{steps}").bold(), - "Checking ink! linting rules".bright_green().bold() - ); - steps.increment_current(); - exec_cargo_dylint(&crate_metadata, verbosity)?; - Ok(()) - } else { - steps.set_total_steps(total_steps); - Ok(()) - } - }; - - let build = - || -> Result<(Option, BuildInfo, PathBuf, BuildSteps)> { - let mut build_steps = BuildSteps::new(); - let pre_fingerprint = Fingerprint::new(&crate_metadata)?; - - verbose_eprintln!( - verbosity, - " {} {}", - format!("{build_steps}").bold(), - "Building cargo project".bright_green().bold() - ); - build_steps.increment_current(); - exec_cargo_for_onchain_target( - &crate_metadata, - "build", - &features, - build_mode, - network, - verbosity, - &unstable_flags, - target, - )?; - - // we persist the latest target we used so we trigger a rebuild when we switch - fs::write(&crate_metadata.target_file_path, target.llvm_target())?; - - let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) { - version - } else { - anyhow::bail!( - "Unable to parse version number for the currently running \ - `cargo-contract` binary." - ); - }; - - let build_info = BuildInfo { - rust_toolchain: util::rust_toolchain()?, - cargo_contract_version, - build_mode, - wasm_opt_settings: WasmOptSettings { - optimization_passes, - keep_debug_symbols, - }, - }; - - let post_fingerprint = - Fingerprint::new(&crate_metadata)?.ok_or_else(|| { - anyhow::anyhow!( - "Expected '{}' to be generated by build", - crate_metadata.original_code.display() - ) - })?; - - tracing::debug!( - "Fingerprint before build: {:?}, after build: {:?}", - pre_fingerprint, - post_fingerprint - ); - - let dest_code_path = crate_metadata.dest_code.clone(); - - if pre_fingerprint == Some(post_fingerprint) - && crate_metadata.dest_code.exists() - { - tracing::info!( - "No changes in the original wasm at {}, fingerprint {:?}. \ - Skipping Wasm optimization and metadata generation.", - crate_metadata.original_code.display(), - pre_fingerprint - ); - return Ok((None, build_info, dest_code_path, build_steps)) - } - - maybe_lint(&mut build_steps)?; - - verbose_eprintln!( - verbosity, - " {} {}", - format!("{build_steps}").bold(), - "Post processing code".bright_green().bold() - ); - build_steps.increment_current(); - - // remove build artifacts so we don't have anything stale lingering around - for t in Target::iter() { - fs::remove_file( - crate_metadata.dest_code.with_extension(t.dest_extension()), - ) - .ok(); - } - - let original_size = - fs::metadata(&crate_metadata.original_code)?.len() as f64 / 1000.0; - - match target { - Target::Wasm => { - post_process_wasm( - &crate_metadata, - skip_wasm_validation, - &verbosity, - max_memory_pages, - )?; - let handler = - WasmOptHandler::new(optimization_passes, keep_debug_symbols)?; - handler.optimize( - &crate_metadata.dest_code, - &crate_metadata.contract_artifact_name, - )?; - } - Target::RiscV => { - fs::copy(&crate_metadata.original_code, &crate_metadata.dest_code)?; - } - } - - let optimized_size = fs::metadata(&dest_code_path)?.len() as f64 / 1000.0; - - let optimization_result = OptimizationResult { - dest_wasm: crate_metadata.dest_code.clone(), - original_size, - optimized_size, - }; - - Ok(( - Some(optimization_result), - build_info, - crate_metadata.dest_code.clone(), - build_steps, - )) - }; - let clean_metadata = || { fs::remove_file(crate_metadata.metadata_path()).ok(); fs::remove_file(crate_metadata.contract_bundle_path()).ok(); @@ -870,7 +734,13 @@ pub fn execute(args: ExecuteArgs) -> Result { let (opt_result, metadata_result, dest_wasm) = match build_artifact { BuildArtifacts::CheckOnly => { let mut build_steps = BuildSteps::new(); - maybe_lint(&mut build_steps)?; + maybe_lint( + &mut build_steps, + *build_artifact, + *lint, + &crate_metadata, + verbosity, + )?; verbose_eprintln!( verbosity, @@ -881,11 +751,11 @@ pub fn execute(args: ExecuteArgs) -> Result { exec_cargo_for_onchain_target( &crate_metadata, "check", - &features, - BuildMode::Release, + features, + &BuildMode::Release, network, verbosity, - &unstable_flags, + unstable_flags, target, )?; (None, None, None) @@ -893,16 +763,19 @@ pub fn execute(args: ExecuteArgs) -> Result { BuildArtifacts::CodeOnly => { // when building only the code metadata will become stale clean_metadata(); - let (opt_result, _, dest_wasm, _) = build()?; + let (opt_result, _, dest_wasm, _) = + local_build(&crate_metadata, &optimization_passes, &args)?; (opt_result, None, Some(dest_wasm)) } BuildArtifacts::All => { let (opt_result, build_info, dest_wasm, build_steps) = - build().map_err(|e| { - // build error -> bundle is stale - clean_metadata(); - e - })?; + local_build(&crate_metadata, &optimization_passes, &args).map_err( + |e| { + // build error -> bundle is stale + clean_metadata(); + e + }, + )?; let metadata_result = MetadataArtifacts { dest_metadata: crate_metadata.metadata_path(), @@ -921,11 +794,11 @@ pub fn execute(args: ExecuteArgs) -> Result { &crate_metadata, dest_wasm.as_path(), &metadata_result, - &features, - network, - verbosity, + features, + *network, + *verbosity, build_steps, - &unstable_flags, + unstable_flags, build_info, )?; } @@ -938,11 +811,184 @@ pub fn execute(args: ExecuteArgs) -> Result { metadata_result, target_directory: crate_metadata.target_directory, optimization_result: opt_result, + build_mode: *build_mode, + build_artifact: *build_artifact, + verbosity: *verbosity, + image: None, + output_type: output_type.clone(), + }) +} + +/// Build the contract on host locally +fn local_build( + crate_metadata: &CrateMetadata, + optimization_passes: &OptimizationPasses, + args: &ExecuteArgs, +) -> Result<(Option, BuildInfo, PathBuf, BuildSteps)> { + let ExecuteArgs { + verbosity, + features, build_mode, + network, build_artifact, + unstable_flags, + keep_debug_symbols, + lint, + skip_wasm_validation, + target, + max_memory_pages, + .. + } = args; + + let mut build_steps = BuildSteps::new(); + let pre_fingerprint = Fingerprint::new(crate_metadata)?; + + verbose_eprintln!( verbosity, - output_type, - }) + " {} {}", + format!("{build_steps}").bold(), + "Building cargo project".bright_green().bold() + ); + build_steps.increment_current(); + exec_cargo_for_onchain_target( + crate_metadata, + "build", + features, + build_mode, + network, + verbosity, + unstable_flags, + target, + )?; + + // We persist the latest target we used so we trigger a rebuild when we switch + fs::write(&crate_metadata.target_file_path, target.llvm_target())?; + + let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) { + version + } else { + anyhow::bail!( + "Unable to parse version number for the currently running \ + `cargo-contract` binary." + ); + }; + + let build_info = BuildInfo { + rust_toolchain: util::rust_toolchain()?, + cargo_contract_version, + build_mode: *build_mode, + wasm_opt_settings: WasmOptSettings { + optimization_passes: *optimization_passes, + keep_debug_symbols: *keep_debug_symbols, + }, + }; + + let post_fingerprint = Fingerprint::new(crate_metadata)?.ok_or_else(|| { + anyhow::anyhow!( + "Expected '{}' to be generated by build", + crate_metadata.original_code.display() + ) + })?; + + tracing::debug!( + "Fingerprint before build: {:?}, after build: {:?}", + pre_fingerprint, + post_fingerprint + ); + + let dest_code_path = crate_metadata.dest_code.clone(); + + if pre_fingerprint == Some(post_fingerprint) && crate_metadata.dest_code.exists() { + tracing::info!( + "No changes in the original wasm at {}, fingerprint {:?}. \ + Skipping Wasm optimization and metadata generation.", + crate_metadata.original_code.display(), + pre_fingerprint + ); + return Ok((None, build_info, dest_code_path, build_steps)) + } + + maybe_lint( + &mut build_steps, + *build_artifact, + *lint, + crate_metadata, + verbosity, + )?; + + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + "Post processing code".bright_green().bold() + ); + build_steps.increment_current(); + + // remove build artifacts so we don't have anything stale lingering around + for t in Target::iter() { + fs::remove_file(crate_metadata.dest_code.with_extension(t.dest_extension())).ok(); + } + + let original_size = + fs::metadata(&crate_metadata.original_code)?.len() as f64 / 1000.0; + + match target { + Target::Wasm => { + post_process_wasm( + crate_metadata, + *skip_wasm_validation, + verbosity, + *max_memory_pages, + )?; + let handler = WasmOptHandler::new(*optimization_passes, *keep_debug_symbols)?; + handler.optimize( + &crate_metadata.dest_code, + &crate_metadata.contract_artifact_name, + )?; + } + Target::RiscV => { + fs::copy(&crate_metadata.original_code, &crate_metadata.dest_code)?; + } + } + + let optimized_size = fs::metadata(&dest_code_path)?.len() as f64 / 1000.0; + + let optimization_result = OptimizationResult { + original_size, + optimized_size, + }; + + Ok(( + Some(optimization_result), + build_info, + crate_metadata.dest_code.clone(), + build_steps, + )) +} + +pub fn maybe_lint( + steps: &mut BuildSteps, + build_artifact: BuildArtifacts, + lint: bool, + crate_metadata: &CrateMetadata, + verbosity: &Verbosity, +) -> Result<()> { + let total_steps = build_artifact.steps(); + if lint { + steps.set_total_steps(total_steps + 1); + verbose_eprintln!( + verbosity, + " {} {}", + format!("{steps}").bold(), + "Checking ink! linting rules".bright_green().bold() + ); + steps.increment_current(); + exec_cargo_dylint(crate_metadata, *verbosity)?; + Ok(()) + } else { + steps.set_total_steps(total_steps); + Ok(()) + } } /// Unique fingerprint for a file to detect whether it has changed. @@ -1085,13 +1131,13 @@ mod unit_tests { }, "target_directory": "/path/to/target", "optimization_result": { - "dest_wasm": "/path/to/contract.wasm", "original_size": 64.0, "optimized_size": 32.0 }, "build_mode": "Debug", "build_artifact": "All", - "verbosity": "Quiet" + "verbosity": "Quiet", + "image": null }"#; let build_result = BuildResult { @@ -1102,12 +1148,12 @@ mod unit_tests { }), target_directory: PathBuf::from("/path/to/target"), optimization_result: Some(OptimizationResult { - dest_wasm: PathBuf::from("/path/to/contract.wasm"), original_size: 64.0, optimized_size: 32.0, }), build_mode: Default::default(), build_artifact: Default::default(), + image: None, verbosity: Verbosity::Quiet, output_type: OutputType::Json, }; diff --git a/crates/build/src/metadata.rs b/crates/build/src/metadata.rs index db0591284..cf829fe6d 100644 --- a/crates/build/src/metadata.rs +++ b/crates/build/src/metadata.rs @@ -56,7 +56,7 @@ use std::{ use url::Url; /// Artifacts resulting from metadata generation. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct MetadataArtifacts { /// Path to the resulting metadata file. pub dest_metadata: PathBuf, @@ -109,7 +109,7 @@ pub struct WasmOptSettings { /// /// It does so by generating and invoking a temporary workspace member. #[allow(clippy::too_many_arguments)] -pub(crate) fn execute( +pub fn execute( crate_metadata: &CrateMetadata, final_contract_wasm: &Path, metadata_artifacts: &MetadataArtifacts, @@ -160,23 +160,15 @@ pub(crate) fn execute( let ink_meta: serde_json::Map = serde_json::from_slice(&output.stdout)?; - let metadata = ContractMetadata::new(source, contract, user, ink_meta); - { - let mut metadata = metadata.clone(); - metadata.remove_source_wasm_attribute(); - let contents = serde_json::to_string_pretty(&metadata)?; - fs::write(&metadata_artifacts.dest_metadata, contents)?; - build_steps.increment_current(); - } + let metadata = ContractMetadata::new(source, contract, None, user, ink_meta); - verbose_eprintln!( - verbosity, - " {} {}", - format!("{build_steps}").bold(), - "Generating bundle".bright_green().bold() - ); - let contents = serde_json::to_string(&metadata)?; - fs::write(&metadata_artifacts.dest_bundle, contents)?; + write_metadata( + metadata_artifacts, + metadata, + &mut build_steps, + &verbosity, + false, + )?; Ok(()) }; @@ -199,6 +191,42 @@ pub(crate) fn execute( Ok(()) } +pub fn write_metadata( + metadata_artifacts: &MetadataArtifacts, + metadata: ContractMetadata, + build_steps: &mut BuildSteps, + verbosity: &Verbosity, + overwrite: bool, +) -> Result<()> { + { + let mut metadata = metadata.clone(); + metadata.remove_source_wasm_attribute(); + let contents = serde_json::to_string_pretty(&metadata)?; + fs::write(&metadata_artifacts.dest_metadata, contents)?; + build_steps.increment_current(); + } + + if overwrite { + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + "Updating paths".bright_cyan().bold() + ); + } else { + verbose_eprintln!( + verbosity, + " {} {}", + format!("{build_steps}").bold(), + "Generating bundle".bright_green().bold() + ); + } + let contents = serde_json::to_string(&metadata)?; + fs::write(&metadata_artifacts.dest_bundle, contents)?; + + Ok(()) +} + /// Generate the extended contract project metadata fn extended_metadata( crate_metadata: &CrateMetadata, diff --git a/crates/build/src/wasm_opt.rs b/crates/build/src/wasm_opt.rs index 8fa3cc717..0acefc9f3 100644 --- a/crates/build/src/wasm_opt.rs +++ b/crates/build/src/wasm_opt.rs @@ -164,10 +164,8 @@ impl From for OptimizationOptions { } /// Result of the optimization process. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct OptimizationResult { - /// The path of the optimized Wasm file. - pub dest_wasm: PathBuf, /// The original Wasm size. pub original_size: f64, /// The Wasm size after optimizations have been applied. diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index 8a67789cf..202ff9fb7 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -30,7 +30,10 @@ which = "4.4.0" colored = "2.0.4" serde_json = "1.0.102" url = { version = "2.4.0", features = ["serde"] } -async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } + + +# dependencies for extrinsics (deploying and calling a contract) +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } subxt = "0.29.0" hex = "0.4.3" diff --git a/crates/cargo-contract/src/cmd/build.rs b/crates/cargo-contract/src/cmd/build.rs index 4adf6f7c2..75192d197 100644 --- a/crates/cargo-contract/src/cmd/build.rs +++ b/crates/cargo-contract/src/cmd/build.rs @@ -21,6 +21,7 @@ use contract_build::{ BuildResult, ExecuteArgs, Features, + ImageVariant, ManifestPath, Network, OptimizationPasses, @@ -121,6 +122,13 @@ pub struct BuildCommand { /// The maximum number of pages available for a wasm contract to allocate. #[clap(long, default_value_t = contract_build::DEFAULT_MAX_MEMORY_PAGES)] max_memory_pages: u32, + /// Executes the build inside a docker container to produce a verifiable bundle. + /// Requires docker daemon running. + #[clap(long, default_value_t = false)] + verifiable: bool, + /// Specify a custom image for the verifiable build + #[clap(long, default_value = None)] + image: Option, } impl BuildCommand { @@ -130,9 +138,13 @@ impl BuildCommand { TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?; let verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; - let build_mode = match self.build_release { - true => BuildMode::Release, - false => BuildMode::Debug, + let build_mode = if self.verifiable { + BuildMode::Verifiable + } else { + match self.build_release { + true => BuildMode::Release, + false => BuildMode::Debug, + } }; let network = match self.build_offline { @@ -145,6 +157,15 @@ impl BuildCommand { false => OutputType::HumanReadable, }; + if self.image.is_some() && build_mode != BuildMode::Verifiable { + anyhow::bail!("--image flag can only be used with verifiable builds!"); + } + + let image = match &self.image { + Some(i) => ImageVariant::Custom(i.clone()), + None => ImageVariant::Default, + }; + let args = ExecuteArgs { manifest_path, verbosity, @@ -160,8 +181,8 @@ impl BuildCommand { skip_wasm_validation: self.skip_wasm_validation, target: self.target, max_memory_pages: self.max_memory_pages, + image, }; - contract_build::execute(args) } } @@ -204,6 +225,7 @@ impl CheckCommand { skip_wasm_validation: false, target: self.target, max_memory_pages: 0, + image: ImageVariant::Default, }; contract_build::execute(args) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 3486344bd..69d8018cc 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -28,6 +28,7 @@ use subxt::{ Config, OnlineClient, }; +use tokio::runtime::Runtime; #[derive(Debug, clap::Args)] #[clap(name = "info", about = "Get infos from a contract")] @@ -55,7 +56,7 @@ impl InfoCommand { self.contract ); - async_std::task::block_on(async { + Runtime::new()?.block_on(async { let url = self.url.clone(); let client = OnlineClient::::from_url(url).await?; diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index 7e4a2226f..9b3fa4d0d 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1.0.168", default-features = false, features = ["derive"] } serde_json = "1.0.102" url = { version = "2.4.0", features = ["serde"] } rust_decimal = "1.30" -async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } sp-core = "21.0.0" sp-runtime = "24.0.0" sp-weights = "20.0.0" diff --git a/crates/extrinsics/src/call.rs b/crates/extrinsics/src/call.rs index 29b94d232..6ac3a248f 100644 --- a/crates/extrinsics/src/call.rs +++ b/crates/extrinsics/src/call.rs @@ -49,6 +49,7 @@ use contract_transcode::Value; use pallet_contracts_primitives::ContractExecResult; use scale::Encode; use sp_weights::Weight; +use tokio::runtime::Runtime; use std::fmt::Debug; use subxt::{ @@ -102,59 +103,65 @@ impl CallCommand { let signer = super::pair_signer(self.extrinsic_opts.signer()?); - async_std::task::block_on(async { - let url = self.extrinsic_opts.url_to_string(); - let client = OnlineClient::from_url(url.clone()).await?; + Runtime::new()? + .block_on(async { + let url = self.extrinsic_opts.url_to_string(); + let client = OnlineClient::from_url(url.clone()).await?; - if !self.extrinsic_opts.execute { - let result = self - .call_dry_run(call_data.clone(), &client, &signer) - .await?; - match result.result { - Ok(ref ret_val) => { - let value = transcoder - .decode_message_return(&self.message, &mut &ret_val.data[..]) - .context(format!( - "Failed to decode return value {:?}", - &ret_val - ))?; - let dry_run_result = CallDryRunResult { - reverted: ret_val.did_revert(), - data: value, - gas_consumed: result.gas_consumed, - gas_required: result.gas_required, - storage_deposit: StorageDeposit::from( - &result.storage_deposit, - ), - }; - if self.output_json { - println!("{}", dry_run_result.to_json()?); - } else { - dry_run_result.print(); - display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH>( - &result, - )?; - display_dry_run_result_warning("message"); - }; - } - Err(ref err) => { - let metadata = client.metadata(); - let object = ErrorVariant::from_dispatch_error(err, &metadata)?; - if self.output_json { - return Err(object) - } else { - name_value_println!("Result", object, MAX_KEY_COL_WIDTH); - display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>( - &result, - )?; + if !self.extrinsic_opts.execute { + let result = self + .call_dry_run(call_data.clone(), &client, &signer) + .await?; + match result.result { + Ok(ref ret_val) => { + let value = transcoder + .decode_message_return( + &self.message, + &mut &ret_val.data[..], + ) + .context(format!( + "Failed to decode return value {:?}", + &ret_val + ))?; + let dry_run_result = CallDryRunResult { + reverted: ret_val.did_revert(), + data: value, + gas_consumed: result.gas_consumed, + gas_required: result.gas_required, + storage_deposit: StorageDeposit::from( + &result.storage_deposit, + ), + }; + if self.output_json { + println!("{}", dry_run_result.to_json()?); + } else { + dry_run_result.print(); + display_contract_exec_result_debug::< + _, + DEFAULT_KEY_COL_WIDTH, + >(&result)?; + display_dry_run_result_warning("message"); + }; + } + Err(ref err) => { + let metadata = client.metadata(); + let object = + ErrorVariant::from_dispatch_error(err, &metadata)?; + if self.output_json { + return Err(object) + } else { + name_value_println!("Result", object, MAX_KEY_COL_WIDTH); + display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>( + &result, + )?; + } } } + } else { + self.call(&client, call_data, &signer, &transcoder).await?; } - } else { - self.call(&client, call_data, &signer, &transcoder).await?; - } - Ok(()) - }) + Ok(()) + }) } async fn call_dry_run( diff --git a/crates/extrinsics/src/error.rs b/crates/extrinsics/src/error.rs index ace0c208d..6a0e79704 100644 --- a/crates/extrinsics/src/error.rs +++ b/crates/extrinsics/src/error.rs @@ -66,6 +66,12 @@ impl From<&str> for ErrorVariant { } } +impl From for ErrorVariant { + fn from(value: std::io::Error) -> Self { + Self::Generic(GenericError::from_message(value.to_string())) + } +} + #[derive(serde::Serialize)] pub struct ModuleError { pub pallet: String, diff --git a/crates/extrinsics/src/instantiate.rs b/crates/extrinsics/src/instantiate.rs index 71182f153..203c03d00 100644 --- a/crates/extrinsics/src/instantiate.rs +++ b/crates/extrinsics/src/instantiate.rs @@ -59,6 +59,7 @@ use subxt::{ Config, OnlineClient, }; +use tokio::runtime::Runtime; #[derive(Debug, clap::Args)] pub struct InstantiateCommand { @@ -121,7 +122,7 @@ impl InstantiateCommand { }; let salt = self.salt.clone().map(|s| s.0).unwrap_or_default(); - async_std::task::block_on(async move { + Runtime::new()?.block_on(async { let client = OnlineClient::from_url(url.clone()).await?; let token_metadata = TokenMetadata::query(&client).await?; diff --git a/crates/extrinsics/src/integration_tests.rs b/crates/extrinsics/src/integration_tests.rs index 44a3b29f4..3b6dda1ce 100644 --- a/crates/extrinsics/src/integration_tests.rs +++ b/crates/extrinsics/src/integration_tests.rs @@ -155,7 +155,7 @@ pub fn init_tracing_subscriber() { /// Requires [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node/) to /// be installed and available on the `PATH`, and the no other process running using the /// default port `9944`. -#[async_std::test] +#[tokio::test] async fn build_upload_instantiate_call() { init_tracing_subscriber(); @@ -235,7 +235,7 @@ async fn build_upload_instantiate_call() { /// Sanity test the whole lifecycle of: /// build -> upload -> remove -#[async_std::test] +#[tokio::test] async fn build_upload_remove() { init_tracing_subscriber(); @@ -300,7 +300,7 @@ async fn build_upload_remove() { /// Requires [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node/) to /// be installed and available on the `PATH`, and the no other process running using the /// default port `9944`. -#[async_std::test] +#[tokio::test] async fn build_upload_instantiate_info() { init_tracing_subscriber(); diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 90763433d..96d8d5b7c 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -88,7 +88,10 @@ pub use balance::{ pub use call::CallCommand; use contract_metadata::ContractMetadata; pub use contract_transcode::ContractMessageTranscoder; -pub use error::ErrorVariant; +pub use error::{ + ErrorVariant, + GenericError, +}; pub use instantiate::InstantiateCommand; pub use remove::RemoveCommand; pub use subxt::PolkadotConfig as DefaultConfig; diff --git a/crates/extrinsics/src/remove.rs b/crates/extrinsics/src/remove.rs index 451565a77..73aa99662 100644 --- a/crates/extrinsics/src/remove.rs +++ b/crates/extrinsics/src/remove.rs @@ -38,6 +38,7 @@ use subxt::{ Config, OnlineClient, }; +use tokio::runtime::Runtime; #[derive(Debug, clap::Args)] #[clap(name = "remove", about = "Remove a contract's code")] @@ -81,7 +82,7 @@ impl RemoveCommand { } }?; - async_std::task::block_on(async { + Runtime::new()?.block_on(async { let url = self.extrinsic_opts.url_to_string(); let client = OnlineClient::from_url(url.clone()).await?; if let Some(code_removed) = self diff --git a/crates/extrinsics/src/upload.rs b/crates/extrinsics/src/upload.rs index d2dceb5c5..b6bb66cf3 100644 --- a/crates/extrinsics/src/upload.rs +++ b/crates/extrinsics/src/upload.rs @@ -42,6 +42,7 @@ use subxt::{ Config, OnlineClient, }; +use tokio::runtime::Runtime; #[derive(Debug, clap::Args)] #[clap(name = "upload", about = "Upload a contract's code")] @@ -71,54 +72,55 @@ impl UploadCommand { })?; let code_hash = code.code_hash(); - async_std::task::block_on(async { - let url = self.extrinsic_opts.url_to_string(); - let client = OnlineClient::from_url(url.clone()).await?; - - if !self.extrinsic_opts.execute { - match self.upload_code_rpc(code, &client, &signer).await? { - Ok(result) => { - let upload_result = UploadDryRunResult { - result: String::from("Success!"), - code_hash: format!("{:?}", result.code_hash), - deposit: result.deposit, - }; - if self.output_json { - println!("{}", upload_result.to_json()?); - } else { - upload_result.print(); - display_dry_run_result_warning("upload"); + Runtime::new()? + .block_on(async { + let url = self.extrinsic_opts.url_to_string(); + let client = OnlineClient::from_url(url.clone()).await?; + + if !self.extrinsic_opts.execute { + match self.upload_code_rpc(code, &client, &signer).await? { + Ok(result) => { + let upload_result = UploadDryRunResult { + result: String::from("Success!"), + code_hash: format!("{:?}", result.code_hash), + deposit: result.deposit, + }; + if self.output_json { + println!("{}", upload_result.to_json()?); + } else { + upload_result.print(); + display_dry_run_result_warning("upload"); + } } - } - Err(err) => { - let metadata = client.metadata(); - let err = ErrorVariant::from_dispatch_error(&err, &metadata)?; - if self.output_json { - return Err(err) - } else { - name_value_println!("Result", err); + Err(err) => { + let metadata = client.metadata(); + let err = ErrorVariant::from_dispatch_error(&err, &metadata)?; + if self.output_json { + return Err(err) + } else { + name_value_println!("Result", err); + } } } - } - } else if let Some(code_stored) = - self.upload_code(&client, code, &signer).await? - { - let upload_result = UploadResult { - code_hash: format!("{:?}", code_stored.code_hash), - }; - if self.output_json { - println!("{}", upload_result.to_json()?); + } else if let Some(code_stored) = + self.upload_code(&client, code, &signer).await? + { + let upload_result = UploadResult { + code_hash: format!("{:?}", code_stored.code_hash), + }; + if self.output_json { + println!("{}", upload_result.to_json()?); + } else { + upload_result.print(); + } } else { - upload_result.print(); + let code_hash = hex::encode(code_hash); + return Err(anyhow::anyhow!( + "This contract has already been uploaded with code hash: 0x{code_hash}" + ) + .into()) } - } else { - let code_hash = hex::encode(code_hash); - return Err(anyhow::anyhow!( - "This contract has already been uploaded with code hash: 0x{code_hash}" - ) - .into()); - } - Ok(()) + Ok(()) }) } diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs index 474fdb5a8..0e3b30f76 100644 --- a/crates/metadata/src/lib.rs +++ b/crates/metadata/src/lib.rs @@ -54,8 +54,11 @@ //! let user = User::new(user_json); //! // contract abi raw json generated by contract compilation //! let abi_json: Map = Map::new(); +//! // image name and tag used for the verifiable build. +//! let image = String::from("paritytech/contracts-verifiable:3.0.1"); //! -//! let metadata = ContractMetadata::new(source, contract, Some(user), abi_json); +//! let metadata = +//! ContractMetadata::new(source, contract, Some(image), Some(user), abi_json); //! //! // serialize to json //! let json = serde_json::to_value(&metadata).unwrap(); @@ -99,6 +102,9 @@ pub struct ContractMetadata { pub source: Source, /// Metadata about the contract. pub contract: Contract, + /// If the contract is meant to be verifiable, + /// then the Docker image is specified. + pub image: Option, /// Additional user-defined metadata. #[serde(skip_serializing_if = "Option::is_none")] pub user: Option, @@ -112,12 +118,14 @@ impl ContractMetadata { pub fn new( source: Source, contract: Contract, + image: Option, user: Option, abi: Map, ) -> Self { Self { source, contract, + image, user, abi, } @@ -737,7 +745,13 @@ mod tests { .unwrap() .clone(); - let metadata = ContractMetadata::new(source, contract, Some(user), abi_json); + let metadata = ContractMetadata::new( + source, + contract, + Some(String::from("paritytech/contracts-verifiable:3.0.1")), + Some(user), + abi_json, + ); let json = serde_json::to_value(&metadata).unwrap(); let expected = json! { @@ -753,6 +767,7 @@ mod tests { "example_name": "increment" } }, + "image": "paritytech/contracts-verifiable:3.0.1", "contract": { "name": "incrementer", "version": "2.1.0", @@ -808,7 +823,7 @@ mod tests { .unwrap() .clone(); - let metadata = ContractMetadata::new(source, contract, None, abi_json); + let metadata = ContractMetadata::new(source, contract, None, None, abi_json); let json = serde_json::to_value(&metadata).unwrap(); let expected = json! { @@ -820,6 +835,7 @@ mod tests { "Parity Technologies " ], }, + "image": serde_json::Value::Null, "source": { "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "language": "ink! 2.1.0", @@ -895,7 +911,8 @@ mod tests { .unwrap() .clone(); - let metadata = ContractMetadata::new(source, contract, Some(user), abi_json); + let metadata = + ContractMetadata::new(source, contract, None, Some(user), abi_json); let json = serde_json::to_value(&metadata).unwrap(); let decoded = serde_json::from_value::(json);