diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7cea2c730..fec8250a0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout sources & submodules - uses: actions/checkout@master + uses: actions/checkout@v2 with: fetch-depth: 1 submodules: recursive @@ -36,20 +36,33 @@ jobs: - name: Install toolchain id: toolchain - uses: actions-rs/toolchain@master + uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} components: rust-src override: true + - name: Install cargo-dylint + uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-dylint + version: 1 + + - name: Install dylint-link + uses: baptiste0928/cargo-install@v1 + with: + crate: dylint-link + version: 1 + - name: Rust Cache - uses: Swatinem/rust-cache@v1.2.0 + uses: Swatinem/rust-cache@v1.3.0 - name: Build contract template on ${{ matrix.platform }}-${{ matrix.toolchain }} run: | wasm-opt --version cargo -vV + cargo dylint --version cargo run -- contract --version cargo run -- contract new foobar echo "[workspace]" >> foobar/Cargo.toml diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e9a179bf3..ada95c81d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -32,27 +32,40 @@ jobs: pathInArchive: "binaryen-/bin/wasm-opt.exe" - name: Checkout sources & submodules - uses: actions/checkout@master + uses: actions/checkout@v2 with: fetch-depth: 1 submodules: recursive - name: Install toolchain id: toolchain - uses: actions-rs/toolchain@master + uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} components: rust-src override: true + - name: Install cargo-dylint + uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-dylint + version: 1 + + - name: Install dylint-link + uses: baptiste0928/cargo-install@v1 + with: + crate: dylint-link + version: 1 + - name: Rust Cache - uses: Swatinem/rust-cache@v1.2.0 + uses: Swatinem/rust-cache@v1.3.0 - name: Build contract template on ${{ matrix.platform }}-${{ matrix.toolchain }} run: | wasm-opt --version cargo -vV + cargo dylint --version cargo run -- contract --version cargo run -- contract new foobar echo "[workspace]" >> foobar/Cargo.toml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99f0db2bc..3a687921f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,11 +39,17 @@ workflow: - cargo -vV - rustc -vV - rustup show + - cargo dylint --version - bash --version - ./scripts/ci/pre_cache.sh # global RUSTFLAGS overrides the linker args so this way is better to pass the flags - printf '[build]\nrustflags = ["-C", "link-dead-code"]\n' > ${CARGO_HOME}/config - sccache -s + + # needed until /~https://github.com/mozilla/sccache/issues/1000 is fixed. + # this is unfortunate, since it disables `sccache` for this entire file. + - unset RUSTC_WRAPPER + - git show rules: - if: $CI_PIPELINE_SOURCE == "web" @@ -81,6 +87,26 @@ clippy: #### stage: test (all features) +test-dylint: + stage: test + <<: *docker-env + script: + - cd ink_linting/ + + # Installing these components here is necessary because + # `ink_linting/` has a fixed `rust-toolchain` file. + # We can't move this line to the Docker container, since + # that would then make it impossible to upgrade the + # `ink_linting/rust-toolchain` file while still having + # this CI job succeed. + - rustup component add rustfmt clippy rust-src + + - cargo check --verbose + - cargo fmt --verbose --all -- --check + - cargo clippy --verbose -- -D warnings; + + - cargo test --verbose --all-features + test: stage: test <<: *docker-env diff --git a/CHANGELOG.md b/CHANGELOG.md index d2934edce..336f7cdd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Linting rules for smart contracts + +We are introducing a linter for ink! smart contracts in this release! +From now on `cargo-contract` checks if the ink! smart contract that is +`build` or `check`-ed follows certain rules. + +As a starting point we've only added one linting rule so far; it asserts correct +initialization of the [`ink_storage::Mapping`](https://paritytech.github.io/ink/ink_storage/struct.Mapping.html) +data structure. + +In order for the linting to work with your smart contract, the contract has to be +written in at least ink! 3.0.0-rc9. If it's older the linting will just always succeed. + +### Added +- Add linting to assert correct initialization of [`ink_storage::Mapping`](https://paritytech.github.io/ink/ink_storage/struct.Mapping.html) - [#431](/~https://github.com/paritytech/cargo-contract/pull/431) + ## [0.17.0] - 2022-01-19 ### Changed diff --git a/Cargo.toml b/Cargo.toml index b5cf33f5c..7d8aec3a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ zip = { version = "0.5.13", default-features = false } walkdir = "2.3.2" substrate-build-script-utils = "3.0.0" platforms = "2.0.0" +which = "4.2.4" [dev-dependencies] assert_cmd = "2.0.4" diff --git a/README.md b/README.md index 23ac25649..1894b6916 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ More relevant links: There's only an old version in your distributions package manager? Just use a [binary release](/~https://github.com/WebAssembly/binaryen/releases). +* Step 4: `cargo install cargo-dylint dylint-link`. + * Step 3: `cargo install --force cargo-contract`. You can always update the `cargo-contract` binary to the latest version by running the Step 3. diff --git a/build.rs b/build.rs index fd0c5fd31..be6466249 100644 --- a/build.rs +++ b/build.rs @@ -35,43 +35,160 @@ use substrate_build_script_utils::rerun_if_git_head_changed; const DEFAULT_UNIX_PERMISSIONS: u32 = 0o755; fn main() { + generate_cargo_keys(); + rerun_if_git_head_changed(); + let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR") .expect("CARGO_MANIFEST_DIR should be set by cargo") .into(); let out_dir: PathBuf = env::var("OUT_DIR") .expect("OUT_DIR should be set by cargo") .into(); + let res = zip_template_and_build_dylint_driver(manifest_dir, out_dir); - let template_dir = manifest_dir.join("templates").join("new"); - let dst_file = out_dir.join("template.zip"); + match res { + Ok(()) => std::process::exit(0), + Err(err) => { + eprintln!("Encountered error: {:?}", err); + std::process::exit(1) + } + } +} + +/// This method: +/// * Creates a zip archive of the `new` project template. +/// * Builds the `dylint` driver found in `ink_linting`, the compiled +/// driver is put into a zip archive as well. +fn zip_template_and_build_dylint_driver(manifest_dir: PathBuf, out_dir: PathBuf) -> Result<()> { + zip_template(&manifest_dir, &out_dir)?; + + check_dylint_link_installed()?; + // This zip file will contain the `dylint` driver, this is one file named in the form of + // `libink_linting@nightly-2021-11-04-x86_64-unknown-linux-gnu.so`. This file is obtained + // by building the crate in `ink_linting/`. + let dylint_driver_dst_file = out_dir.join("ink-dylint-driver.zip"); + build_and_zip_dylint_driver(manifest_dir, out_dir, dylint_driver_dst_file)?; + + Ok(()) +} + +/// Creates a zip archive `template.zip` of the `new` project template in `out_dir`. +fn zip_template(manifest_dir: &Path, out_dir: &Path) -> Result<()> { + let template_dir = manifest_dir.join("templates").join("new"); + let template_dst_file = out_dir.join("template.zip"); println!( "Creating template zip: template_dir '{}', destination archive '{}'", template_dir.display(), - dst_file.display() + template_dst_file.display() ); + zip_dir(&template_dir, &template_dst_file, CompressionMethod::Stored).map(|_| { + println!( + "Done: {} written to {}", + template_dir.display(), + template_dst_file.display() + ); + }) +} - generate_cargo_keys(); - rerun_if_git_head_changed(); +/// Builds the crate in `ink_linting/`. This crate contains the `dylint` driver with ink! specific +/// linting rules. +#[cfg(feature = "cargo-clippy")] +fn build_and_zip_dylint_driver( + _manifest_dir: PathBuf, + _out_dir: PathBuf, + dylint_driver_dst_file: PathBuf, +) -> Result<()> { + // For `clippy` runs it is not necessary to build the `dylint` driver. + // Furthermore the fixed Rust nightly specified in `ink_linting/rust-toolchain` + // contains a bug that results in an `error[E0786]: found invalid metadata files` ICE. + // + // We still have to create an empty file though, due to the `include_bytes!` macro. + File::create(dylint_driver_dst_file) + .map_err(|err| { + anyhow::anyhow!( + "Failed creating an empty ink-dylint-driver.zip file: {:?}", + err + ) + }) + .map(|_| ()) +} - std::process::exit( - match zip_dir(&template_dir, &dst_file, CompressionMethod::Stored) { - Ok(_) => { - println!( - "done: {} written to {}", - template_dir.display(), - dst_file.display() - ); - 0 - } - Err(e) => { - eprintln!("Error: {:?}", e); - 1 - } - }, +/// Builds the crate in `ink_linting/`. This crate contains the `dylint` driver with ink! specific +/// linting rules. +#[cfg(not(feature = "cargo-clippy"))] +fn build_and_zip_dylint_driver( + manifest_dir: PathBuf, + out_dir: PathBuf, + dylint_driver_dst_file: PathBuf, +) -> Result<()> { + let ink_dylint_driver_dir = manifest_dir.join("ink_linting"); + + let mut cmd = Command::new("cargo"); + + let manifest_arg = format!( + "--manifest-path={}", + ink_dylint_driver_dir.join("Cargo.toml").display() + ); + let target_dir = format!("--target-dir={}", out_dir.display()); + cmd.args(vec![ + "build", + "--release", + "--locked", + &target_dir, + &manifest_arg, + ]); + + // We need to remove those environment variables because `dylint` uses a + // fixed Rust toolchain via the `ink_linting/rust-toolchain` file. By removing + // these env variables we avoid issues with different Rust toolchains + // interfering with each other. + cmd.env_remove("RUSTUP_TOOLCHAIN"); + cmd.env_remove("CARGO_TARGET_DIR"); + + println!( + "Setting cargo working dir to '{}'", + ink_dylint_driver_dir.display() + ); + cmd.current_dir(ink_dylint_driver_dir.clone()); + + println!("Invoking cargo: {:?}", cmd); + + let child = cmd + // capture the stdout to return from this function as bytes + .stdout(std::process::Stdio::piped()) + .spawn()?; + let output = child.wait_with_output()?; + + if !output.status.success() { + anyhow::bail!( + "`{:?}` failed with exit code: {:?}", + cmd, + output.status.code() + ); + } + + println!( + "Creating ink-dylint-driver.zip: destination archive '{}'", + dylint_driver_dst_file.display() ); + + zip_dylint_driver( + &out_dir.join("release"), + &dylint_driver_dst_file, + CompressionMethod::Stored, + ) + .map(|_| { + println!( + "Done: {} written to {}", + ink_dylint_driver_dir.display(), + dylint_driver_dst_file.display() + ); + () + }) } +/// Creates a zip archive at `dst_file` with the content of the `src_dir`. fn zip_dir(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result<()> { if !src_dir.exists() { anyhow::bail!("src_dir '{}' does not exist", src_dir.display()); @@ -118,6 +235,50 @@ fn zip_dir(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result Ok(()) } +/// Creates a zip archive at `dst_file` with the `dylint` driver found in `src_dir`. +/// +/// `dylint` drivers have a file name of the form `libink_linting@toolchain.[so,dll]`. +#[cfg(not(feature = "cargo-clippy"))] +fn zip_dylint_driver(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result<()> { + if !src_dir.exists() { + anyhow::bail!("src_dir '{}' does not exist", src_dir.display()); + } + if !src_dir.is_dir() { + anyhow::bail!("src_dir '{}' is not a directory", src_dir.display()); + } + + let file = File::create(dst_file)?; + + let mut zip = ZipWriter::new(file); + let options = FileOptions::default() + .compression_method(method) + .unix_permissions(DEFAULT_UNIX_PERMISSIONS); + + let mut buffer = Vec::new(); + + let walkdir = WalkDir::new(src_dir); + let it = walkdir.into_iter().filter_map(|e| e.ok()); + + for entry in it { + let path = entry.path(); + let name = path.strip_prefix(&src_dir)?.to_path_buf(); + let file_path = name.as_os_str().to_string_lossy(); + + if path.is_file() && path.display().to_string().contains("libink_linting@") { + zip.start_file(file_path, options)?; + let mut f = File::open(path)?; + + f.read_to_end(&mut buffer)?; + zip.write_all(&*buffer)?; + buffer.clear(); + + zip.finish()?; + break; + } + } + Ok(()) +} + /// Generate the `cargo:` key output fn generate_cargo_keys() { let output = Command::new("git") @@ -168,3 +329,17 @@ fn get_platform() -> String { TARGET_ENV.map(|x| x.as_str()).unwrap_or(""), ) } + +/// Checks if `dylint-link` is installed, i.e. if the `dylint-link` executable +/// can be executed with a `--version` argument. +fn check_dylint_link_installed() -> Result<()> { + let which = which::which("dylint-link"); + if which.is_err() { + anyhow::bail!( + "dylint-link was not found!\n\ + Make sure it is installed and the binary is in your PATH environment.\n\n\ + You can install it by executing `cargo install dylint-link`." + ); + } + Ok(()) +} diff --git a/ink_linting/.cargo/config.toml b/ink_linting/.cargo/config.toml new file mode 100644 index 000000000..e8488c83f --- /dev/null +++ b/ink_linting/.cargo/config.toml @@ -0,0 +1,11 @@ +# This file corresponds to the `.cargo/config.toml` file used for the `dylint` examples here: +# /~https://github.com/trailofbits/dylint/tree/master/examples. + +[target.x86_64-apple-darwin] +linker = "dylint-link" + +[target.x86_64-unknown-linux-gnu] +linker = "dylint-link" + +[target.x86_64-pc-windows-msvc] +linker = "dylint-link" diff --git a/ink_linting/.rustfmt.toml b/ink_linting/.rustfmt.toml new file mode 100644 index 000000000..8ebf443f8 --- /dev/null +++ b/ink_linting/.rustfmt.toml @@ -0,0 +1,3 @@ +license_template_path = "../FILE_HEADER" # changed +report_todo = "Always" +report_fixme = "Always" diff --git a/ink_linting/Cargo.lock b/ink_linting/Cargo.lock new file mode 100644 index 000000000..915d4557b --- /dev/null +++ b/ink_linting/Cargo.lock @@ -0,0 +1,1425 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + +[[package]] +name = "array-init" +version = "2.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "6945cc5422176fc5e602e590c2878d2c2acd9a4fe20a4baa7c28022521698ec6" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b94ba84325db59637ffc528bbe8c7f86c02c57cff5c0e2b9b00f9a851f42f309" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1d30c751592b77c499e7bce34d99d67c2c11bdc0574e9a488ddade14150a4698" + +[[package]] +name = "camino" +version = "1.0.7" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clippy_utils" +version = "0.1.58" +source = "git+/~https://github.com/rust-lang/rust-clippy?tag=rust-1.58.0#e18101137866b79045fee0ef996e696e68c920b4" +dependencies = [ + "if_chain", + "rustc-semver", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "compiletest_rs" +version = "0.7.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "29843cb8d351febf86557681d049d1e1652b81a086a190fa1173c07fd17fbf83" +dependencies = [ + "diff", + "filetime", + "getopts", + "lazy_static", + "libc", + "log", + "miow", + "regex", + "rustfix", + "serde", + "serde_derive", + "serde_json", + "tester", + "winapi", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dylint" +version = "1.0.13" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "893050152de22362e5e50525d9da750a8ee939cae9f476a26a7a55c5617ba047" +dependencies = [ + "ansi_term", + "anyhow", + "atty", + "cargo_metadata", + "dirs", + "dylint_internal", + "heck", + "lazy_static", + "log", + "semver", + "serde", + "serde_json", + "tempfile", + "walkdir", +] + +[[package]] +name = "dylint_internal" +version = "1.0.13" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "522be37595510d89d37096959ab258cc0dcc1867e64e956d41a1a48ff53e5892" +dependencies = [ + "ansi_term", + "anyhow", + "cargo_metadata", + "if_chain", + "log", + "sedregex", +] + +[[package]] +name = "dylint_linting" +version = "1.0.13" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "15ec587418bda2f8e0dc6a40e21b2878c3901647f7148d5f833b2d657f46ea17" + +[[package]] +name = "dylint_testing" +version = "1.0.13" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b346641d5d618646f03e719d90857b88dc5077a7ba7b8b1e577a3fdaf6ea7137" +dependencies = [ + "anyhow", + "cargo_metadata", + "compiletest_rs", + "dylint", + "dylint_internal", + "env_logger", + "lazy_static", + "log", + "regex", + "serde_json", + "tempfile", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_allocator" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_env" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "arrayref", + "blake2", + "cfg-if", + "derive_more", + "ink_allocator", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "num-traits", + "parity-scale-codec", + "paste", + "rand 0.8.5", + "rlibc", + "scale-info", + "secp256k1", + "sha2", + "sha3", + "static_assertions", +] + +[[package]] +name = "ink_eth_compatibility" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "ink_env", + "libsecp256k1", +] + +[[package]] +name = "ink_lang" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "derive_more", + "ink_env", + "ink_eth_compatibility", + "ink_lang_macro", + "ink_prelude", + "ink_primitives", + "ink_storage", + "parity-scale-codec", +] + +[[package]] +name = "ink_lang_codegen" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "blake2", + "derive_more", + "either", + "heck", + "impl-serde", + "ink_lang_ir", + "itertools", + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_lang_ir" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "blake2", + "either", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ink_lang_macro" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "ink_lang_codegen", + "ink_lang_ir", + "ink_primitives", + "parity-scale-codec", + "proc-macro2", + "syn", +] + +[[package]] +name = "ink_linting" +version = "0.1.0" +dependencies = [ + "clippy_utils", + "dylint_linting", + "dylint_testing", + "if_chain", + "ink_env", + "ink_lang", + "ink_metadata", + "ink_primitives", + "ink_storage", + "log", + "parity-scale-codec", + "regex", + "scale-info", +] + +[[package]] +name = "ink_metadata" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "derive_more", + "impl-serde", + "ink_prelude", + "ink_primitives", + "scale-info", + "serde", +] + +[[package]] +name = "ink_prelude" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_primitives" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "cfg-if", + "ink_prelude", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "array-init", + "cfg-if", + "derive_more", + "ink_env", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage_derive", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage_derive" +version = "3.0.0-rc8" +source = "git+/~https://github.com/paritytech/ink#e345679f4a6888bf253d430374c8db518d957d7d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.118" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" + +[[package]] +name = "libsecp256k1" +version = "0.7.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "parity-scale-codec" +version = "3.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2a7f3fcf5e45fc28b84dcdab6b983e77f197ec01f325a33f404ba6855afd1070" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2c6e626dc84025ff56bf1476ed0e30d10c84d7f89a475ef46ebabee1095a8fba" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9dada8c9981fcf32929c3c0f0cd796a9284aca335565227ed88c83babb1d43dc" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rlibc" +version = "1.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" + +[[package]] +name = "rustc-semver" +version = "1.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5be1bdc7edf596692617627bbfeaba522131b18e06ca4df2b6b689e3c5d5ce84" + +[[package]] +name = "rustfix" +version = "0.5.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f2c50b74badcddeb8f7652fa8323ce440b95286f8e4b64ebfd871c609672704e" +dependencies = [ + "anyhow", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "7d3b4d0b178e3af536f7988303bc73a0766c816de2138c08262015f8ec7be568" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.0.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "b7805950c36512db9e3251c970bb7ac425f326716941862205d612ab3b5e46e2" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "secp256k1" +version = "0.21.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ab7883017d5b21f011ef8040ea9c6c7ac90834c0df26a69e4c0b06276151f125" +dependencies = [ + "rand 0.6.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "sedregex" +version = "0.2.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "19411e23596093f03bbd11dc45603b6329bb4bfec77b9fd13e2b9fc9b02efe3e" +dependencies = [ + "regex", +] + +[[package]] +name = "semver" +version = "1.0.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.10.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "tester" +version = "0.9.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "0639d10d8f4615f223a57275cf40f9bdb7cfbb806bcb7f7cc56e3beb55a576eb" +dependencies = [ + "cfg-if", + "getopts", + "libc", + "num_cpus", + "term", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] diff --git a/ink_linting/Cargo.toml b/ink_linting/Cargo.toml new file mode 100644 index 000000000..6cf5e0029 --- /dev/null +++ b/ink_linting/Cargo.toml @@ -0,0 +1,95 @@ +[package] +name = "ink_linting" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +license = "Apache-2.0" +readme = "README.md" +repository = "/~https://github.com/paritytech/cargo-contract" +documentation = "https://docs.rs/ink_linting" +homepage = "/~https://github.com/paritytech/cargo-contract" +description = "Linting tool for ink! smart contracts." +keywords = ["parity", "blockchain", "ink", "smart contracts", "substrate"] +include = ["Cargo.toml", "*.rs", "LICENSE"] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +clippy_utils = { git = "/~https://github.com/rust-lang/rust-clippy", tag = "rust-1.58.0" } +dylint_linting = "1.0.13" +if_chain = "1.0.2" +log = "0.4.14" +regex = "1.5.4" + +[dev-dependencies] +dylint_testing = "1.0.13" + +# The following are ink! dependencies, they are only required for the `ui` tests. +ink_primitives = { git = "/~https://github.com/paritytech/ink", default-features = false } +ink_metadata = { git = "/~https://github.com/paritytech/ink", default-features = false, features = ["derive"] } +ink_env = { git = "/~https://github.com/paritytech/ink", default-features = false } +ink_storage = { git = "/~https://github.com/paritytech/ink", default-features = false } +ink_lang = { git = "/~https://github.com/paritytech/ink", default-features = false } +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +# For the moment we have to include the tests as examples and +# then use `dylint_testing::ui_test_examples`. +# +# The reason is that the `dylint_testing` API currently does not +# provide any other option to run the tests on those files +# *while giving us the option to specify the dependencies*. +# +# Those files require the ink! dependencies though, by having +# them as examples here, they inherit the `dev-dependencies`. +[[example]] +name = "fail_mapping_one_constructor" +path = "ui/fail/mapping-one-constructor.rs" + +[[example]] +name = "fail_mapping_two_constructors_01" +path = "ui/fail/mapping-two-constructors-01.rs" + +[[example]] +name = "fail_mapping_two_constructors_02" +path = "ui/fail/mapping-two-constructors-02.rs" + +[[example]] +name = "fail_mapping_nested_initialize_call" +path = "ui/fail/mapping-nested-initialize-call.rs" + +[[example]] +name = "pass_mapping_one_constructor" +path = "ui/pass/mapping-one-constructor.rs" + +[[example]] +name = "pass_mapping_two_constructors" +path = "ui/pass/mapping-two-constructors.rs" + +[[example]] +name = "pass_mapping_additional_logic_constructor" +path = "ui/pass/mapping-additional-logic-constructor.rs" + +[[example]] +name = "pass_dont_use_fully_qualified_path" +path = "ui/pass/mapping-dont-use-fully-qualified-path.rs" + +[package.metadata.rust-analyzer] +rustc_private = true + +[workspace] + +[features] +default = ["std"] +std = [ + "ink_metadata/std", + "ink_env/std", + "ink_storage/std", + "ink_primitives/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] diff --git a/ink_linting/LICENSE b/ink_linting/LICENSE new file mode 120000 index 000000000..99d5fc26c --- /dev/null +++ b/ink_linting/LICENSE @@ -0,0 +1 @@ +../metadata/LICENSE \ No newline at end of file diff --git a/ink_linting/README.md b/ink_linting/README.md new file mode 100644 index 000000000..80279a661 --- /dev/null +++ b/ink_linting/README.md @@ -0,0 +1,17 @@ +# ink! linting rules + +This crate uses [`dylint`](/~https://github.com/trailofbits/dylint) to define custom +linting rules for [ink!](/~https://github.com/paritytech/ink); + +You can use it this way: + +```bash +# Install all prerequisites. +cargo install cargo-dylint dylint-link + +cargo build --release + +# Run the linting on a contract. +DYLINT_LIBRARY_PATH=$PWD/target/release cargo dylint contract_instantiated + --manifest-path ../ink/examples/erc20/Cargo.toml +``` \ No newline at end of file diff --git a/ink_linting/rust-toolchain b/ink_linting/rust-toolchain new file mode 100644 index 000000000..480368fe1 --- /dev/null +++ b/ink_linting/rust-toolchain @@ -0,0 +1,6 @@ +# This file corresponds to the `rust-toolchain` file used for the `dylint` examples here: +# /~https://github.com/trailofbits/dylint/tree/master/examples. + +[toolchain] +channel = "nightly-2021-11-04" +components = ["llvm-tools-preview", "rustc-dev"] diff --git a/ink_linting/src/lib.rs b/ink_linting/src/lib.rs new file mode 100644 index 000000000..7253469e5 --- /dev/null +++ b/ink_linting/src/lib.rs @@ -0,0 +1,40 @@ +// Copyright 2018-2022 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 . + +#![feature(rustc_private)] + +dylint_linting::dylint_library!(); + +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; + +mod mapping_initialized; + +#[doc(hidden)] +#[no_mangle] +pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) { + lint_store.register_lints(&[mapping_initialized::MAPPING_INITIALIZED]); + lint_store.register_late_pass(|| Box::new(mapping_initialized::MappingInitialized)); +} + +#[test] +fn ui() { + dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME")); +} diff --git a/ink_linting/src/mapping_initialized.rs b/ink_linting/src/mapping_initialized.rs new file mode 100644 index 000000000..12b9d09d0 --- /dev/null +++ b/ink_linting/src/mapping_initialized.rs @@ -0,0 +1,342 @@ +// Copyright 2018-2022 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 . + +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use if_chain::if_chain; +use regex::Regex; +use rustc_errors::Applicability; +use rustc_hir::{ + def_id::DefId, + intravisit::{walk_fn, walk_item, walk_qpath, FnKind, NestedVisitorMap, Visitor}, + BodyId, FnDecl, HirId, Item, ItemKind, +}; +use rustc_hir::{QPath, VariantData}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{hir::map::Map, ty::Attributes}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::source_map::Span; + +declare_lint! { + /// **What it does:** Checks for ink! contracts that use + /// [`ink_storage::Mapping`](https://paritytech.github.io/ink/ink_storage/struct.Mapping.html) + /// in their storage without initializing it properly in the ink! constructor. + /// + /// **Why is this bad?** If `Mapping` is not properly initialized corruption of storage + /// might occur. + /// + /// **Known problems:** The lint currently requires for the + /// `ink_lang::utils::initialize_contract(…)` function call to be made on the top + /// level of the constructor *and* it has be made explicitly in this form. + /// + /// The lint can currently not detect if `initialize_contract` would be called in a + /// nested function within the constructor. + /// + /// **Example:** + /// + /// ```rust + /// // Good + /// use ink_storage::{traits::SpreadAllocate, Mapping}; + /// + /// #[ink(storage)] + /// #[derive(SpreadAllocate)] + /// pub struct MyContract { + /// balances: Mapping, + /// } + /// + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// ink_lang::utils::initialize_contract(Self::new_init) + /// } + /// + /// /// Default initializes the contract. + /// fn new_init(&mut self) { + /// let caller = Self::env().caller(); + /// let value: Balance = Default::default(); + /// self.balances.insert(&caller, &value); + /// } + /// ``` + /// + /// ```rust + /// // Bad + /// use ink_storage::{traits::SpreadAllocate, Mapping}; + /// + /// #[ink(storage)] + /// #[derive(SpreadAllocate)] + /// pub struct MyContract { + /// balances: Mapping, + /// } + /// + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self { + /// balances: Default::default(), + /// } + /// } + /// ``` + pub MAPPING_INITIALIZED, + Deny, + "Error on ink! contracts that use `ink_storage::Mapping` without initializing it." +} + +declare_lint_pass!(MappingInitialized => [MAPPING_INITIALIZED]); + +/// An ink! attribute. +#[derive(PartialEq)] +enum InkAttribute { + // #[ink(storage)] + Storage, + // #[ink(constructor)] + Constructor, +} + +/// Returns `Some(InkAttribute)` if an ink! attribute is among `attributes`. +fn get_ink_attribute(attributes: Attributes) -> Option { + const INK_STORAGE: &str = "__ink_dylint_Storage"; + const INK_CONSTRUCTOR: &str = "__ink_dylint_Constructor"; + + let attrs = format!("symbol: \"{:?}\"", attributes); + if attrs.contains(INK_STORAGE) { + Some(InkAttribute::Storage) + } else if attrs.contains(INK_CONSTRUCTOR) { + Some(InkAttribute::Constructor) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for MappingInitialized { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let ink_attrs = get_ink_attribute(attrs); + if ink_attrs.is_none() { + return; + } + + if let ItemKind::Struct(variant_data, _) = &item.kind { + if let VariantData::Unit(..) = variant_data { + return; + } + check_struct(cx, item, variant_data); + } + } +} + +/// Examines a `struct`. If the struct is annotated as an ink! storage struct +/// we examine if there is a `ink_storage::Mapping` in it; in case there +/// is we continue checking all ink! constructors for correct usage of the +/// `ink_lang::utils::initialize_contract` API. +/// +/// This function is backwards compatible, in case no annotated ink! struct is +/// found nothing happens. +fn check_struct<'a>(cx: &LateContext<'a>, item: &'a Item, data: &VariantData) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + match get_ink_attribute(attrs) { + Some(InkAttribute::Storage) => {} + _ => return, + } + + let mut marker = None; + let fields = data.fields(); + let storage_contains_mapping = fields.iter().any(|field| { + let re = Regex::new(r"ink_storage\[.*\]::lazy::mapping::Mapping") + .expect("failed creating regex"); + if re.is_match(&format!("{:?}", field.ty.kind)) { + marker = Some(field); + return true; + } + false + }); + + if !storage_contains_mapping { + log::debug!("Found `#[ink(storage)]` struct without `Mapping`"); + return; + } + log::debug!("Found `#[ink(storage)]` struct with `Mapping`"); + + let inherent_impls = cx.tcx.inherent_impls(item.def_id); + let constructors_without_initialize: Vec = inherent_impls + .iter() + .map(|imp_did| item_from_def_id(cx, *imp_did)) + .filter_map(|item| constructor_but_no_initialize(cx, item)) + .collect(); + log::debug!( + "Result of searching for constructors without initialize: {:?}", + constructors_without_initialize + ); + + if_chain! { + if storage_contains_mapping; + if !constructors_without_initialize.is_empty(); + then { + let constructor_span = constructors_without_initialize.get(0) + .expect("at least one faulty constructor must exist"); + let snippet = snippet_opt(cx, *constructor_span) + .expect("snippet must exist"); + span_lint_and_then( + cx, + MAPPING_INITIALIZED, + item.span, + &format!( + "`#[ink(storage)]` on `{}` contains `ink_storage::Mapping` without initializing it in the contract constructor.", + item.ident + ), + |diag| { + diag.span_suggestion( + *constructor_span, + "add an `ink_lang::utils::initialize_contract(…)` function in this constructor", + snippet, + Applicability::Unspecified, + ); + diag.span_help(marker.expect("marker must exist").span, "this field uses `ink_storage::Mapping`"); + }); + } + } +} + +/// Returns `Some(span)` if a constructor without a call of +/// `ink_lang::utils::initialize_contract(…)` was found. +fn constructor_but_no_initialize<'tcx>( + cx: &LateContext<'tcx>, + item: &'tcx Item<'_>, +) -> Option { + let mut visitor = InkAttributeVisitor { + cx, + ink_attribute: None, + constructor_info: None, + }; + + walk_item(&mut visitor, item); + + match visitor.constructor_info { + Some(info) if !info.uses_initialize_contract => Some(info.span), + _ => None, + } +} + +/// Visitor for ink! attributes. +struct InkAttributeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ink_attribute: Option, + constructor_info: Option, +} + +// Information about an ink! constructor. +struct InkConstructor { + // The constructor has a call to `ink_lang::utils::initialize_contract(…)` + // in its function. + uses_initialize_contract: bool, + // The span for the constructor function. + span: Span, +} + +impl<'tcx> Visitor<'tcx> for InkAttributeVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + span: Span, + id: HirId, + ) { + // We can return immediately if an incorrect constructor was already found + if let Some(constructor) = &self.constructor_info { + if !constructor.uses_initialize_contract { + return; + } + } + + let attrs = self.cx.tcx.get_attrs(id.owner.to_def_id()); + self.ink_attribute = get_ink_attribute(attrs); + + if self.ink_attribute == Some(InkAttribute::Storage) { + return; + } else if self.ink_attribute == Some(InkAttribute::Constructor) { + log::debug!("Found constructor, starting to search for `initialize_contract`"); + let mut visitor = InitializeContractVisitor { + cx: self.cx, + uses_initialize_contract: false, + }; + walk_fn(&mut visitor, kind, decl, body_id, span, id); + + log::debug!( + "Has `initialize_contract`? {:?}", + visitor.uses_initialize_contract + ); + self.constructor_info = Some(InkConstructor { + uses_initialize_contract: visitor.uses_initialize_contract, + span, + }); + + return; + } + + walk_fn(self, kind, decl, body_id, span, id); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +/// Visitor to determine if a `fn` contains a call to `ink_lang::utils::initialize_contract(…)`. +/// +/// # Known Limitation +/// +/// This function currently only finds call to `initialize_contract` which happen +/// on the first level of the `fn` ‒ no nested calls are found! So if you would +/// call `initialize_contract` within a sub-function of the ink! constructor +/// this is not recognized! +struct InitializeContractVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + uses_initialize_contract: bool, +} + +impl<'tcx> Visitor<'tcx> for InitializeContractVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_qpath(&mut self, qpath: &'tcx QPath<'_>, id: HirId, span: Span) { + log::debug!("Visiting path {:?}", qpath); + if self.uses_initialize_contract { + return; + } + + if let QPath::Resolved(_, path) = qpath { + log::debug!("QPath: {:?}", path.res); + let re = + Regex::new(r"ink_lang\[.*\]::codegen::dispatch::execution::initialize_contract") + .expect("failed creating regex"); + if re.is_match(&format!("{:?}", path.res)) { + self.uses_initialize_contract = true; + return; + } + } + + walk_qpath(self, qpath, id, span); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::All(self.cx.tcx.hir()) + } +} + +/// Returns the `rustc_hir::Item` for a `rustc_hir::def_id::DefId`. +fn item_from_def_id<'tcx>(cx: &LateContext<'tcx>, def_id: DefId) -> &'tcx Item<'tcx> { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); + cx.tcx.hir().expect_item(hir_id) +} diff --git a/ink_linting/ui/fail/mapping-nested-initialize-call.rs b/ink_linting/ui/fail/mapping-nested-initialize-call.rs new file mode 100644 index 000000000..a8064b724 --- /dev/null +++ b/ink_linting/ui/fail/mapping-nested-initialize-call.rs @@ -0,0 +1,58 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + /// The linter currently does not detect if `initialize_contract` is + /// called in a nested function. + #[ink(constructor)] + pub fn new1() -> Self { + Self::foo() + } + + fn foo() -> Self { + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/fail/mapping-nested-initialize-call.stderr b/ink_linting/ui/fail/mapping-nested-initialize-call.stderr new file mode 100644 index 000000000..2bce8d28d --- /dev/null +++ b/ink_linting/ui/fail/mapping-nested-initialize-call.stderr @@ -0,0 +1,27 @@ +error: `#[ink(storage)]` on `MyContract` contains `ink_storage::Mapping` without initializing it in the contract constructor. + --> $DIR/mapping-nested-initialize-call.rs:26:5 + | +LL | / #[derive(SpreadAllocate)] +LL | | pub struct MyContract { +LL | | balances: Mapping, +LL | | } + | |_____^ + | + = note: `#[deny(mapping_initialized)]` on by default +help: this field uses `ink_storage::Mapping` + --> $DIR/mapping-nested-initialize-call.rs:28:9 + | +LL | balances: Mapping, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add an `ink_lang::utils::initialize_contract(…)` function in this constructor + | +LL ~ /// The linter currently does not detect if `initialize_contract` is +LL + /// called in a nested function. +LL + #[ink(constructor)] +LL + pub fn new1() -> Self { +LL + Self::foo() +LL + } + | + +error: aborting due to previous error + diff --git a/ink_linting/ui/fail/mapping-one-constructor.rs b/ink_linting/ui/fail/mapping-one-constructor.rs new file mode 100644 index 000000000..18fbe1b78 --- /dev/null +++ b/ink_linting/ui/fail/mapping-one-constructor.rs @@ -0,0 +1,47 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + #[ink(constructor)] + pub fn new() -> Self { + Self { + balances: Default::default(), + } + } + + /// Returns the total token supply. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/fail/mapping-one-constructor.stderr b/ink_linting/ui/fail/mapping-one-constructor.stderr new file mode 100644 index 000000000..c8f71934a --- /dev/null +++ b/ink_linting/ui/fail/mapping-one-constructor.stderr @@ -0,0 +1,26 @@ +error: `#[ink(storage)]` on `MyContract` contains `ink_storage::Mapping` without initializing it in the contract constructor. + --> $DIR/mapping-one-constructor.rs:26:5 + | +LL | / #[derive(SpreadAllocate)] +LL | | pub struct MyContract { +LL | | balances: Mapping, +LL | | } + | |_____^ + | + = note: `#[deny(mapping_initialized)]` on by default +help: this field uses `ink_storage::Mapping` + --> $DIR/mapping-one-constructor.rs:28:9 + | +LL | balances: Mapping, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add an `ink_lang::utils::initialize_contract(…)` function in this constructor + | +LL ~ pub fn new() -> Self { +LL + Self { +LL + balances: Default::default(), +LL + } +LL + } + | + +error: aborting due to previous error + diff --git a/ink_linting/ui/fail/mapping-two-constructors-01.rs b/ink_linting/ui/fail/mapping-two-constructors-01.rs new file mode 100644 index 000000000..645ce9a50 --- /dev/null +++ b/ink_linting/ui/fail/mapping-two-constructors-01.rs @@ -0,0 +1,62 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + /// This constructor is correct. + /// It comes before the incorrect constructor. + #[ink(constructor)] + pub fn new1() -> Self { + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// This constructor is **not** correct. + #[ink(constructor)] + pub fn new2() -> Self { + Self { + balances: Default::default(), + } + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/fail/mapping-two-constructors-01.stderr b/ink_linting/ui/fail/mapping-two-constructors-01.stderr new file mode 100644 index 000000000..4ce040883 --- /dev/null +++ b/ink_linting/ui/fail/mapping-two-constructors-01.stderr @@ -0,0 +1,27 @@ +error: `#[ink(storage)]` on `MyContract` contains `ink_storage::Mapping` without initializing it in the contract constructor. + --> $DIR/mapping-two-constructors-01.rs:26:5 + | +LL | / #[derive(SpreadAllocate)] +LL | | pub struct MyContract { +LL | | balances: Mapping, +LL | | } + | |_____^ + | + = note: `#[deny(mapping_initialized)]` on by default +help: this field uses `ink_storage::Mapping` + --> $DIR/mapping-two-constructors-01.rs:28:9 + | +LL | balances: Mapping, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add an `ink_lang::utils::initialize_contract(…)` function in this constructor + | +LL ~ /// This constructor is **not** correct. +LL + #[ink(constructor)] +LL + pub fn new2() -> Self { +LL + Self { +LL + balances: Default::default(), +LL + } + ... + +error: aborting due to previous error + diff --git a/ink_linting/ui/fail/mapping-two-constructors-02.rs b/ink_linting/ui/fail/mapping-two-constructors-02.rs new file mode 100644 index 000000000..253ac1a5b --- /dev/null +++ b/ink_linting/ui/fail/mapping-two-constructors-02.rs @@ -0,0 +1,62 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + /// This constructor is **not** correct. + /// It comes before the correct constructor. + #[ink(constructor)] + pub fn new2() -> Self { + Self { + balances: Default::default(), + } + } + + /// This constructor is correct. + #[ink(constructor)] + pub fn new1() -> Self { + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/fail/mapping-two-constructors-02.stderr b/ink_linting/ui/fail/mapping-two-constructors-02.stderr new file mode 100644 index 000000000..982f01e46 --- /dev/null +++ b/ink_linting/ui/fail/mapping-two-constructors-02.stderr @@ -0,0 +1,27 @@ +error: `#[ink(storage)]` on `MyContract` contains `ink_storage::Mapping` without initializing it in the contract constructor. + --> $DIR/mapping-two-constructors-02.rs:26:5 + | +LL | / #[derive(SpreadAllocate)] +LL | | pub struct MyContract { +LL | | balances: Mapping, +LL | | } + | |_____^ + | + = note: `#[deny(mapping_initialized)]` on by default +help: this field uses `ink_storage::Mapping` + --> $DIR/mapping-two-constructors-02.rs:28:9 + | +LL | balances: Mapping, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add an `ink_lang::utils::initialize_contract(…)` function in this constructor + | +LL ~ /// This constructor is **not** correct. +LL + /// It comes before the correct constructor. +LL + #[ink(constructor)] +LL + pub fn new2() -> Self { +LL + Self { +LL + balances: Default::default(), + ... + +error: aborting due to previous error + diff --git a/ink_linting/ui/pass/mapping-additional-logic-constructor.rs b/ink_linting/ui/pass/mapping-additional-logic-constructor.rs new file mode 100644 index 000000000..964d8a91c --- /dev/null +++ b/ink_linting/ui/pass/mapping-additional-logic-constructor.rs @@ -0,0 +1,61 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + /// The `Mapping` must be recognized, even if it is imported + /// under a different name. + use ink_storage::{traits::SpreadAllocate, Mapping as SomeOtherName}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: SomeOtherName, + } + + impl MyContract { + #[ink(constructor)] + pub fn new() -> Self { + let a = 1; + let b = 2; + assert_eq!(add(a, b), 3); + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } + + fn add(a: u32, b: u32) -> u32 { + a + b + } +} + +fn main() {} diff --git a/ink_linting/ui/pass/mapping-dont-use-fully-qualified-path.rs b/ink_linting/ui/pass/mapping-dont-use-fully-qualified-path.rs new file mode 100644 index 000000000..c56c189d2 --- /dev/null +++ b/ink_linting/ui/pass/mapping-dont-use-fully-qualified-path.rs @@ -0,0 +1,53 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_lang::utils::initialize_contract as foo; + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + #[ink(constructor)] + pub fn new() -> Self { + foo(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/pass/mapping-one-constructor.rs b/ink_linting/ui/pass/mapping-one-constructor.rs new file mode 100644 index 000000000..48544660d --- /dev/null +++ b/ink_linting/ui/pass/mapping-one-constructor.rs @@ -0,0 +1,52 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + #[ink(constructor)] + pub fn new() -> Self { + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/ink_linting/ui/pass/mapping-two-constructors.rs b/ink_linting/ui/pass/mapping-two-constructors.rs new file mode 100644 index 000000000..f16cddfa2 --- /dev/null +++ b/ink_linting/ui/pass/mapping-two-constructors.rs @@ -0,0 +1,63 @@ +// Copyright 2018-2022 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract] +mod my_contract { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + /// First constructor which uses the `new_init` helper function. + #[ink(constructor)] + pub fn new1() -> Self { + ink_lang::utils::initialize_contract(Self::new_init) + } + + /// Default initializes the contract. + fn new_init(&mut self) { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + self.balances.insert(&caller, &value); + } + + /// Second constructor which doesn't use the `new_init` helper function. + #[ink(constructor)] + pub fn new2() -> Self { + ink_lang::utils::initialize_contract(|contract: &mut Self| { + let caller = Self::env().caller(); + let value: Balance = Default::default(); + contract.balances.insert(&caller, &value); + }) + } + + /// Returns something. + #[ink(message)] + pub fn get(&self) { + // ... + } + } +} + +fn main() {} diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 8f40212f8..f0136753b 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -247,13 +247,6 @@ fn exec_cargo_for_wasm_target( ) -> Result<()> { util::assert_channel()?; - // set linker args via RUSTFLAGS. - // Currently will override user defined RUSTFLAGS from .cargo/config. See /~https://github.com/paritytech/cargo-contract/issues/98. - std::env::set_var( - "RUSTFLAGS", - "-C link-arg=-zstack-size=65536 -C link-arg=--import-memory -Clinker-plugin-lto", - ); - let cargo_build = |manifest_path: &ManifestPath| { let target_dir = &crate_metadata.target_directory; let target_dir = format!("--target-dir={}", target_dir.to_string_lossy()); @@ -272,7 +265,11 @@ fn exec_cargo_for_wasm_target( } else { args.push("-Zbuild-std-features=panic_immediate_abort"); } - util::invoke_cargo(command, &args, manifest_path.directory(), verbosity)?; + let env = vec![( + "RUSTFLAGS", + Some("-C link-arg=-zstack-size=65536 -C link-arg=--import-memory -Clinker-plugin-lto"), + )]; + util::invoke_cargo(command, &args, manifest_path.directory(), verbosity, env)?; Ok(()) }; @@ -298,8 +295,112 @@ fn exec_cargo_for_wasm_target( .using_temp(cargo_build)?; } - // clear RUSTFLAGS - std::env::remove_var("RUSTFLAGS"); + Ok(()) +} + +/// Executes `cargo dylint` with the ink! linting driver that is built during +/// the `build.rs`. +/// +/// We create a temporary folder, extract the linting driver there and run +/// `cargo dylint` with it. +fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Result<()> { + check_dylint_requirements(crate_metadata.manifest_path.directory())?; + + let tmp_dir = tempfile::Builder::new() + .prefix("cargo-contract-dylint_") + .tempdir()?; + log::debug!("Using temp workspace at '{}'", tmp_dir.path().display()); + + let driver = include_bytes!(concat!(env!("OUT_DIR"), "/ink-dylint-driver.zip")); + crate::util::unzip(driver, tmp_dir.path().to_path_buf(), None)?; + + let manifest_path = crate_metadata.manifest_path.cargo_arg()?; + let args = vec!["--lib", "ink_linting", &manifest_path]; + let tmp_dir_path = tmp_dir.path().as_os_str().to_string_lossy(); + let env = vec![ + ("DYLINT_LIBRARY_PATH", Some(tmp_dir_path.as_ref())), + // For tests we need to set the `DYLINT_DRIVER_PATH` to a tmp folder, + // otherwise tests running in parallel will try to write to the same + // file at the same time which will result in a `Text file busy` error. + #[cfg(test)] + ("DYLINT_DRIVER_PATH", Some(tmp_dir_path.as_ref())), + // We need to remove the `CARGO_TARGET_DIR` environment variable in + // case `cargo dylint` is invoked. + // + // This is because the ink! dylint driver crate found in `dylint` uses a + // fixed Rust toolchain via the `ink_linting/rust-toolchain` file. By + // removing this env variable we avoid issues with different Rust toolchains + // interfering with each other. + ("CARGO_TARGET_DIR", None), + ]; + let working_dir = crate_metadata + .manifest_path + .directory() + .unwrap_or_else(|| Path::new(".")) + .canonicalize()?; + + let verbosity = if verbosity == Verbosity::Verbose { + // `dylint` is verbose by default, it doesn't have a `--verbose` argument, + Verbosity::Default + } else { + verbosity + }; + util::invoke_cargo("dylint", &args, Some(working_dir), verbosity, env)?; + + Ok(()) +} + +/// Checks if all requirements for `dylint` are installed. +/// +/// We require only an installed version of `cargo-dylint` here and don't +/// check for an installed version of `dylint-link`. This is because +/// `dylint-link` is only required for the `dylint` driver build process +/// in `build.rs`. +/// +/// This function takes a `_working_dir` which is only used for unit tests. +fn check_dylint_requirements(_working_dir: Option<&Path>) -> Result<()> { + let execute_cmd = |cmd: &mut Command| { + // when testing this function we set the `PATH` to the `working_dir` + // so that we can have mocked binaries in there which are executed + // instead of the real ones. + #[cfg(test)] + { + let default_dir = PathBuf::from("."); + let working_dir = _working_dir.unwrap_or(default_dir.as_path()); + let path_env = std::env::var("PATH").unwrap(); + let path_env = format!("{}:{}", working_dir.to_string_lossy(), path_env); + cmd.env("PATH", path_env); + } + + cmd.stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + .map_err(|err| { + log::debug!("Error spawning `{:?}`", cmd); + err + })? + .wait() + .map(|res| res.success()) + .map_err(|err| { + log::debug!("Error waiting for `{:?}`: {:?}", cmd, err); + err + }) + }; + + // when testing this function we should never fall back to a `cargo` specified + // in the env variable, as this would mess with the mocked binaries. + #[cfg(not(test))] + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + #[cfg(test)] + let cargo = "cargo"; + + if !execute_cmd(Command::new(cargo).arg("dylint").arg("--version"))? { + anyhow::bail!("cargo-dylint was not found!\n\ + Make sure it is installed and the binary is in your PATH environment.\n\n\ + You can install it by executing `cargo install cargo-dylint`." + .to_string() + .bright_yellow()); + } Ok(()) } @@ -450,7 +551,7 @@ fn do_optimization( let which = which::which("wasm-opt"); if which.is_err() { anyhow::bail!( - "wasm-opt not found! Make sure the binary is in your PATH environment.\n\ + "wasm-opt not found! Make sure the binary is in your PATH environment.\n\n\ We use this tool to optimize the size of your contract's Wasm binary.\n\n\ wasm-opt is part of the binaryen package. You can find detailed\n\ installation instructions on /~https://github.com/WebAssembly/binaryen#tools.\n\n\ @@ -465,7 +566,7 @@ fn do_optimization( } let wasm_opt_path = which .as_ref() - .expect("we just checked if which returned an err; qed") + .expect("we just checked if `which` returned an err; qed") .as_path(); log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); @@ -615,16 +716,15 @@ fn assert_compatible_ink_dependencies( ) -> Result<()> { for dependency in ["parity-scale-codec", "scale-info"].iter() { let args = ["-i", dependency, "--duplicates"]; - let _ = util::invoke_cargo("tree", &args, manifest_path.directory(), verbosity).map_err( - |_| { + let _ = util::invoke_cargo("tree", &args, manifest_path.directory(), verbosity, vec![]) + .map_err(|_| { anyhow::anyhow!( "Mismatching versions of `{}` were found!\n\ Please ensure that your contract and your ink! dependencies use a compatible \ version of this package.", dependency ) - }, - )?; + })?; } Ok(()) } @@ -671,6 +771,14 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { verbosity, " {} {}", format!("[1/{}]", build_artifact.steps()).bold(), + "Checking ink! linting rules".bright_green().bold() + ); + exec_cargo_dylint(&crate_metadata, verbosity)?; + + maybe_println!( + verbosity, + " {} {}", + format!("[2/{}]", build_artifact.steps()).bold(), "Building cargo project".bright_green().bold() ); exec_cargo_for_wasm_target( @@ -685,7 +793,7 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { maybe_println!( verbosity, " {} {}", - format!("[2/{}]", build_artifact.steps()).bold(), + format!("[3/{}]", build_artifact.steps()).bold(), "Post processing wasm file".bright_green().bold() ); post_process_wasm(&crate_metadata)?; @@ -693,7 +801,7 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { maybe_println!( verbosity, " {} {}", - format!("[3/{}]", build_artifact.steps()).bold(), + format!("[4/{}]", build_artifact.steps()).bold(), "Optimizing wasm file".bright_green().bold() ); let optimization_result = @@ -704,6 +812,20 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { let (opt_result, metadata_result) = match build_artifact { BuildArtifacts::CheckOnly => { + maybe_println!( + verbosity, + " {} {}", + format!("[1/{}]", build_artifact.steps()).bold(), + "Checking ink! linting rules".bright_green().bold() + ); + exec_cargo_dylint(&crate_metadata, verbosity)?; + + maybe_println!( + verbosity, + " {} {}", + format!("[2/{}]", build_artifact.steps()).bold(), + "Executing `cargo check`".bright_green().bold() + ); exec_cargo_for_wasm_target( &crate_metadata, "check", @@ -790,6 +912,20 @@ mod tests_ci_only { .any(|e| e.name() == "name") } + /// Creates an executable file at `path` with the content `content`. + /// + /// Currently works only on `unix`. + #[cfg(unix)] + fn create_executable(path: &Path, content: &str) { + { + let mut file = std::fs::File::create(&path).unwrap(); + file.write_all(content.as_bytes()) + .expect("writing of executable failed"); + } + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) + .expect("setting permissions failed"); + } + /// Creates an executable `wasm-opt-mocked` file which outputs /// "wasm-opt version `version`". /// @@ -799,14 +935,8 @@ mod tests_ci_only { #[cfg(unix)] fn mock_wasm_opt_version(tmp_dir: &Path, version: &str) -> PathBuf { let path = tmp_dir.join("wasm-opt-mocked"); - { - let mut file = std::fs::File::create(&path).unwrap(); - let version = format!("#!/bin/sh\necho \"wasm-opt version {}\"", version); - file.write_all(version.as_bytes()) - .expect("writing wasm-opt-mocked failed"); - } - std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) - .expect("setting permissions failed"); + let content = format!("#!/bin/sh\necho \"wasm-opt version {}\"", version); + create_executable(&path, &content); path } @@ -1273,4 +1403,96 @@ mod tests_ci_only { Ok(()) }) } + + // This test has to be ignored until the next ink! rc. + // Before that we don't have the `__ink_dylint_…` markers available + // to actually run `dylint`. + #[test] + #[ignore] + fn dylint_must_find_issue() { + with_new_contract_project(|manifest_path| { + // given + let contract = r#" + #![cfg_attr(not(feature = "std"), no_std)] + + use ink_lang as ink; + + #[ink::contract] + mod fail_mapping_01 { + use ink_storage::{traits::SpreadAllocate, Mapping}; + + #[ink(storage)] + #[derive(SpreadAllocate)] + pub struct MyContract { + balances: Mapping, + } + + impl MyContract { + #[ink(constructor)] + pub fn new() -> Self { + Self { + balances: Default::default(), + } + } + + /// Returns the total token supply. + #[ink(message)] + pub fn get(&self) { + // ... + } + } + }"#; + let project_dir = manifest_path.directory().expect("directory must exist"); + let lib = project_dir.join("lib.rs"); + std::fs::write(&lib, contract)?; + + let args = crate::cmd::build::ExecuteArgs { + manifest_path, + build_artifact: BuildArtifacts::CheckOnly, + ..Default::default() + }; + + // when + let res = super::execute(args); + + // then + match res { + Err(err) => { + eprintln!("err: {:?}", err); + assert!(err.to_string().contains( + "help: add an `initialize_contract` function in this constructor" + )); + } + _ => panic!("build succeeded, but must fail!"), + }; + Ok(()) + }) + } + + #[cfg(unix)] + #[test] + fn missing_cargo_dylint_installation_must_be_detected() { + with_new_contract_project(|manifest_path| { + // given + let manifest_dir = manifest_path.directory().unwrap(); + + // mock existing `dylint-link` binary + create_executable(&manifest_dir.join("dylint-link"), "#!/bin/sh\nexit 0"); + + // mock a non-existing `cargo dylint` installation. + create_executable(&manifest_dir.join("cargo"), "#!/bin/sh\nexit 1"); + + // when + let args = crate::cmd::build::ExecuteArgs { + manifest_path, + ..Default::default() + }; + let res = super::execute(args).map(|_| ()).unwrap_err(); + + // then + assert!(format!("{:?}", res).contains("cargo-dylint was not found!")); + + Ok(()) + }) + } } diff --git a/src/cmd/metadata.rs b/src/cmd/metadata.rs index 19f183fde..687ed8a5b 100644 --- a/src/cmd/metadata.rs +++ b/src/cmd/metadata.rs @@ -93,13 +93,14 @@ pub(crate) fn execute( &[ "--package", "metadata-gen", - &manifest_path.cargo_arg(), + &manifest_path.cargo_arg()?, &target_dir_arg, "--release", &network.to_string(), ], crate_metadata.manifest_path.directory(), verbosity, + vec![], )?; let ink_meta: serde_json::Map = serde_json::from_slice(&stdout)?; diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 271f19ac1..f0b95e307 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -14,14 +14,9 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use std::{ - env, fs, - io::{Cursor, Read, Seek, SeekFrom, Write}, - path::Path, -}; +use std::{env, fs, path::Path}; use anyhow::Result; -use heck::ToUpperCamelCase as _; pub(crate) fn execute

(name: &str, dir: Option

) -> Result<()> where @@ -51,56 +46,8 @@ where } let template = include_bytes!(concat!(env!("OUT_DIR"), "/template.zip")); - let mut cursor = Cursor::new(Vec::new()); - cursor.write_all(template)?; - cursor.seek(SeekFrom::Start(0))?; - - let mut archive = zip::ZipArchive::new(cursor)?; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - - // replace template placeholders - let contents = contents.replace("{{name}}", name); - let contents = contents.replace("{{camel_name}}", &name.to_upper_camel_case()); - - let outpath = out_dir.join(file.name()); - - if (*file.name()).ends_with('/') { - fs::create_dir_all(&outpath)?; - } else { - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(&p)?; - } - } - let mut outfile = fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(outpath.clone()) - .map_err(|e| { - if e.kind() == std::io::ErrorKind::AlreadyExists { - anyhow::anyhow!("New contract file {} already exists", file.name()) - } else { - anyhow::anyhow!(e) - } - })?; - - outfile.write_all(contents.as_bytes())?; - } - - // Get and set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?; - } - } - } + + crate::util::unzip(template, out_dir, Some(name))?; Ok(()) } @@ -177,7 +124,7 @@ mod tests { assert!(result.is_err(), "Should fail"); assert_eq!( result.err().unwrap().to_string(), - "New contract file .gitignore already exists" + "File .gitignore already exists" ); Ok(()) }) diff --git a/src/cmd/test.rs b/src/cmd/test.rs index a242bbabb..bf624e413 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -65,7 +65,7 @@ pub(crate) fn execute(manifest_path: &ManifestPath, verbosity: Verbosity) -> Res "Running tests".bright_green().bold() ); - let stdout = util::invoke_cargo("test", &[""], manifest_path.directory(), verbosity)?; + let stdout = util::invoke_cargo("test", &[""], manifest_path.directory(), verbosity, vec![])?; Ok(TestResult { stdout, verbosity }) } diff --git a/src/main.rs b/src/main.rs index 241a2eed5..eb7adacf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,7 +140,7 @@ pub struct VerbosityFlags { } /// Denotes if output should be printed to stdout. -#[derive(Clone, Copy, serde::Serialize)] +#[derive(Clone, Copy, serde::Serialize, Eq, PartialEq)] pub enum Verbosity { /// Use default output Default, diff --git a/src/util.rs b/src/util.rs index 26dbbfcb1..59acf86e0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -16,8 +16,15 @@ use crate::Verbosity; use anyhow::{Context, Result}; +use heck::ToUpperCamelCase as _; use rustc_version::Channel; -use std::{ffi::OsStr, path::Path, process::Command}; +use std::{ + ffi::OsStr, + fs, + io::{Cursor, Read, Seek, SeekFrom, Write}, + path::{Path, PathBuf}, + process::Command, +}; /// Check whether the current rust channel is valid: `nightly` is recommended. pub fn assert_channel() -> Result<()> { @@ -35,14 +42,24 @@ pub fn assert_channel() -> Result<()> { } } -/// Run cargo with the supplied args +/// Invokes `cargo` with the subcommand `command` and the supplied `args`. /// -/// If successful, returns the stdout bytes +/// In case `working_dir` is set, the command will be invoked with that folder +/// as the working directory. +/// +/// In case `env` is given environment variables can be either set or unset: +/// * To _set_ push an item a la `("VAR_NAME", Some("VAR_VALUE"))` to +/// the `env` vector. +/// * To _unset_ push an item a la `("VAR_NAME", None)` to the `env` +/// vector. +/// +/// If successful, returns the stdout bytes. pub(crate) fn invoke_cargo( command: &str, args: I, working_dir: Option

, verbosity: Verbosity, + env: Vec<(&str, Option<&str>)>, ) -> Result> where I: IntoIterator + std::fmt::Debug, @@ -51,6 +68,14 @@ where { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let mut cmd = Command::new(cargo); + + env.iter().for_each(|(env_key, maybe_env_val)| { + match maybe_env_val { + Some(env_val) => cmd.env(env_key, env_val), + None => cmd.env_remove(env_key), + }; + }); + if let Some(path) = working_dir { log::debug!("Setting cargo working dir to '{}'", path.as_ref().display()); cmd.current_dir(path); @@ -60,7 +85,13 @@ where cmd.args(args); match verbosity { Verbosity::Quiet => cmd.arg("--quiet"), - Verbosity::Verbose => cmd.arg("--verbose"), + Verbosity::Verbose => { + if command != "dylint" { + cmd.arg("--verbose") + } else { + &mut cmd + } + } Verbosity::Default => &mut cmd, }; @@ -189,3 +220,66 @@ pub mod tests { }) } } + +// Unzips the file at `template` to `out_dir`. +// +// In case `name` is set the zip file is treated as if it were a template for a new +// contract. Replacements in `Cargo.toml` for `name`-placeholders are attempted in +// that case. +pub fn unzip(template: &[u8], out_dir: PathBuf, name: Option<&str>) -> Result<()> { + let mut cursor = Cursor::new(Vec::new()); + cursor.write_all(template)?; + cursor.seek(SeekFrom::Start(0))?; + + let mut archive = zip::ZipArchive::new(cursor)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let outpath = out_dir.join(file.name()); + + if (*file.name()).ends_with('/') { + fs::create_dir_all(&outpath)?; + } else { + if let Some(p) = outpath.parent() { + if !p.exists() { + fs::create_dir_all(&p)?; + } + } + let mut outfile = fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(outpath.clone()) + .map_err(|e| { + if e.kind() == std::io::ErrorKind::AlreadyExists { + anyhow::anyhow!("File {} already exists", file.name(),) + } else { + anyhow::anyhow!(e) + } + })?; + + if let Some(name) = name { + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let contents = contents.replace("{{name}}", name); + let contents = contents.replace("{{camel_name}}", &name.to_upper_camel_case()); + outfile.write_all(contents.as_bytes())?; + } else { + let mut v = Vec::new(); + file.read_to_end(&mut v)?; + outfile.write_all(v.as_slice())?; + } + } + + // Get and set permissions + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + if let Some(mode) = file.unix_mode() { + fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?; + } + } + } + + Ok(()) +} diff --git a/src/workspace/manifest.rs b/src/workspace/manifest.rs index dc0bdd304..e9f5bf553 100644 --- a/src/workspace/manifest.rs +++ b/src/workspace/manifest.rs @@ -52,8 +52,12 @@ impl ManifestPath { } /// Create an arg `--manifest-path=` for `cargo` command - pub fn cargo_arg(&self) -> String { - format!("--manifest-path={}", self.path.to_string_lossy()) + pub fn cargo_arg(&self) -> Result { + let path = self + .path + .canonicalize() + .map_err(|err| anyhow::anyhow!("Failed to canonicalize {:?}: {:?}", self.path, err))?; + Ok(format!("--manifest-path={}", path.to_string_lossy())) } /// The directory path of the manifest path.