contract_metadata/
compatibility.rsuse std::collections::HashMap;
use anyhow::{
anyhow,
bail,
Result,
};
use semver::{
Version,
VersionReq,
};
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>,
}
pub fn check_contract_ink_compatibility(
ink_version: &Version,
cargo_contract_version: Option<Version>,
) -> Result<()> {
let compatibility_list = include_str!("../compatibility_list.json");
let compatibility: Compatibility = serde_json::from_str(compatibility_list)?;
let cargo_contract_version = if let Some(version) = cargo_contract_version {
version
} else {
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;
if ink_req.is_empty() {
bail!(
"Missing ink! requirements for cargo-contract: {}",
cargo_contract_version
);
}
if !ink_req.iter().any(|req| req.matches(ink_version)) {
let ink_required_versions = ink_req
.iter()
.map(|req| format!("'{}'", req))
.collect::<Vec<_>>()
.join(", ");
let ink_update_message = format!(
"change the ink! version of your contract to {}",
ink_required_versions
);
let contract_not_compatible_message = "This version of cargo-contract is not \
compatible with the contract's ink! 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!(
"{} Please use cargo-contract in 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 cargo_contract_version = Some(Version::new(3, 2, 0));
let res = check_contract_ink_compatibility(
&ink_version,
cargo_contract_version.clone(),
)
.expect_err("Ink version check should fail");
assert_eq!(
res.to_string(),
"This version of cargo-contract is not compatible with the contract's ink! version. \
Please use cargo-contract in version '1.5.0' or change \
the ink! version of your contract to '^4.0.0-alpha.3', '^4.0.0'"
);
let ink_version =
Version::parse("4.0.0-alpha.1").expect("Parsing version must work");
let res = check_contract_ink_compatibility(&ink_version, cargo_contract_version)
.expect_err("Ink version check should fail");
assert_eq!(
res.to_string(),
"This version of cargo-contract is not compatible with the contract's ink! version. \
Please use cargo-contract in version '1.5.0' or change \
the ink! version of your contract to '^4.0.0-alpha.3', '^4.0.0'"
);
}
#[test]
fn ink_check_succeeds_when_compatible_version() {
let ink_version = Version::new(4, 2, 0);
let cargo_contract_version = Some(Version::new(3, 2, 0));
let res = check_contract_ink_compatibility(&ink_version, cargo_contract_version);
assert!(res.is_ok());
let ink_version =
Version::parse("5.0.0-rc.3").expect("Parsing version must work");
let res = check_contract_ink_compatibility(&ink_version, None);
assert!(res.is_ok());
}
}