Skip to content

Commit

Permalink
Add contract's ink! compatibility check (use-ink#1334)
Browse files Browse the repository at this point in the history
* Initial ink version compatibility check

* Fixed issues

* fmt applied

* unit test fixed for check ink compatibility

* Added json file with version requirements

* Formatting fixed

* Added empty line to the end of json file

* Replaced unwrap with expect

* Changed ink versionreq to start from stable version

* Add total contract deposit

* Revert "Add total contract deposit"

This reverts commit a58ae70.

* Remove custom matches implementation for VersionReq

* Code refactored

* Code fmt

* Renamed structures

* Code refactored
  • Loading branch information
smiasojed authored Oct 4, 2023
1 parent f17926e commit 09864b6
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `--binary` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/)
- Add `--all` flag for `info` command - [#1319](https://github.com/paritytech/cargo-contract/pull/1319)
- Fix for a Url to String conversion in `info` command - [#1330](https://github.com/paritytech/cargo-contract/pull/1330)
- Add warning message when using incompatible contract's ink! version [#1334](https://github.com/paritytech/cargo-contract/pull/1334)

## [4.0.0-alpha]

Expand Down
2 changes: 1 addition & 1 deletion crates/build/src/crate_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl CrateMetadata {
.packages
.iter()
.find_map(|package| {
if package.name == "ink" {
if package.name == "ink" || package.name == "ink_lang" {
Some(
Version::parse(&package.version.to_string())
.expect("Invalid ink crate version string"),
Expand Down
9 changes: 8 additions & 1 deletion crates/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
#![doc = include_str!("../README.md")]
#![deny(unused_crate_dependencies)]

use contract_metadata::ContractMetadata;
use contract_metadata::{
compatibility::check_contract_ink_compatibility,
ContractMetadata,
};
use which as _;

mod args;
Expand Down Expand Up @@ -855,6 +858,10 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
assert_debug_mode_supported(&crate_metadata.ink_version)?;
}

if let Err(e) = check_contract_ink_compatibility(&crate_metadata.ink_version) {
eprintln!("{} {}", "warning:".yellow().bold(), e.to_string().bold());
}

let clean_metadata = || {
fs::remove_file(crate_metadata.metadata_path()).ok();
fs::remove_file(crate_metadata.contract_bundle_path()).ok();
Expand Down
7 changes: 7 additions & 0 deletions crates/extrinsics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod upload;
#[cfg(feature = "integration-tests")]
mod integration_tests;

use colored::Colorize;
use subxt::utils::AccountId32;

use anyhow::{
Expand Down Expand Up @@ -195,6 +196,12 @@ impl ContractArtifacts {
)
}
};

if let Some(contract_metadata) = metadata.as_ref() {
if let Err(e) = contract_metadata.check_ink_compatibility() {
eprintln!("{} {}", "warning:".yellow().bold(), e.to_string().bold());
}
}
Ok(Self {
artifacts_path: path.into(),
metadata_path,
Expand Down
10 changes: 10 additions & 0 deletions crates/metadata/compatibility_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"cargo-contract": {
"1.5.0": {
"ink": [">=3.0.0,<4.0.0", ">=3.0.0-alpha, <4.0.0-alpha.3"]
},
"4.0.0-alpha": {
"ink": ["4.0.0-alpha.3", "4.0.0", "5.0.0-alpha"]
}
}
}
164 changes: 164 additions & 0 deletions crates/metadata/src/compatibility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright 2018-2023 Parity Technologies (UK) Ltd.
// This file is part of cargo-contract.
//
// cargo-contract is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cargo-contract is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with cargo-contract. If not, see <http://www.gnu.org/licenses/>.

use std::collections::HashMap;

use anyhow::{
anyhow,
bail,
Result,
};
use semver::{
Version,
VersionReq,
};

/// Version of the currently executing `cargo-contract` binary.
const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, serde::Deserialize)]
struct Compatibility {
#[serde(rename = "cargo-contract")]
cargo_contract_compatibility: HashMap<Version, Requirements>,
}

#[derive(Debug, serde::Deserialize)]
struct Requirements {
#[serde(rename = "ink")]
ink_requirements: Vec<VersionReq>,
}

/// Checks whether the contract's ink! version is compatible with the cargo-contract
/// binary.
pub fn check_contract_ink_compatibility(ink_version: &Version) -> Result<()> {
let compatibility_list = include_str!("../compatibility_list.json");
let compatibility: Compatibility = serde_json::from_str(compatibility_list)?;
let cargo_contract_version =
semver::Version::parse(VERSION).expect("Parsing version failed");
let ink_req = &compatibility
.cargo_contract_compatibility
.get(&cargo_contract_version)
.ok_or(anyhow!(
"Missing compatibility configuration for cargo-contract: {}",
cargo_contract_version
))?
.ink_requirements;

// Ink! requirements can not be empty
if ink_req.is_empty() {
bail!(
"Missing ink! requirements for cargo-contract: {}",
cargo_contract_version
);
}

// Check if the ink! version matches any of the requirement
if !ink_req.iter().any(|req| req.matches(ink_version)) {
// Get required ink! versions
let ink_required_versions = ink_req
.iter()
.map(|req| format!("'{}'", req))
.collect::<Vec<_>>()
.join(", ");

let ink_update_message = format!(
"update the contract ink! to a version of {}",
ink_required_versions
);
let contract_not_compatible_message = "The cargo-contract is not compatible \
with the contract's ink! version. Please";

// Find best cargo-contract version
let best_cargo_contract_version = compatibility
.cargo_contract_compatibility
.iter()
.filter_map(|(ver, reqs)| {
if reqs
.ink_requirements
.iter()
.any(|req| req.matches(ink_version))
{
return Some(ver)
}
None
})
.max_by(|&a, &b| {
match (!a.pre.is_empty(), !b.pre.is_empty()) {
(false, true) => std::cmp::Ordering::Greater,
(true, false) => std::cmp::Ordering::Less,
(_, _) => a.cmp(b),
}
})
.ok_or(anyhow!(
"{} {}",
contract_not_compatible_message,
ink_update_message
))?;

bail!(
"{} update the cargo-contract to version \
'{}' or {}",
contract_not_compatible_message,
best_cargo_contract_version,
ink_update_message
);
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn ink_check_failes_when_incompatible_version() {
let ink_version = Version::new(3, 2, 0);
let res = check_contract_ink_compatibility(&ink_version)
.expect_err("Ink version check should fail");

assert_eq!(
res.to_string(),
"The cargo-contract is not compatible with the contract's ink! version. \
Please update the cargo-contract to version '1.5.0' or update \
the contract ink! to a version of '^4.0.0-alpha.3', '^4.0.0', '^5.0.0-alpha'"
);

let ink_version =
Version::parse("4.0.0-alpha.1").expect("Parsing version must work");
let res = check_contract_ink_compatibility(&ink_version)
.expect_err("Ink version check should fail");

assert_eq!(
res.to_string(),
"The cargo-contract is not compatible with the contract's ink! version. \
Please update the cargo-contract to version '1.5.0' or update \
the contract ink! to a version of '^4.0.0-alpha.3', '^4.0.0', '^5.0.0-alpha'"
);
}

#[test]
fn ink_check_succeeds_when_compatible_version() {
let ink_version = Version::new(4, 2, 3);
let res = check_contract_ink_compatibility(&ink_version);
assert!(res.is_ok());

let ink_version =
Version::parse("4.0.0-alpha.4").expect("Parsing version must work");
let res = check_contract_ink_compatibility(&ink_version);
assert!(res.is_ok());
}
}
12 changes: 12 additions & 0 deletions crates/metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#![deny(unused_crate_dependencies)]

mod byte_str;
pub mod compatibility;

use anyhow::{
Context,
Expand Down Expand Up @@ -148,6 +149,17 @@ impl ContractMetadata {
path.display()
))
}

/// Checks whether the contract's ink! version is compatible with the cargo-contract
/// binary
pub fn check_ink_compatibility(&self) -> Result<()> {
if let Language::Ink = self.source.language.language {
compatibility::check_contract_ink_compatibility(
&self.source.language.version,
)?;
}
Ok(())
}
}

/// Representation of the Wasm code hash.
Expand Down

0 comments on commit 09864b6

Please sign in to comment.