contract_metadata/
compatibility.rs1use std::collections::HashMap;
18
19use anyhow::{
20 anyhow,
21 bail,
22 Result,
23};
24use semver::{
25 Version,
26 VersionReq,
27};
28
29const VERSION: &str = env!("CARGO_PKG_VERSION");
31
32#[derive(Debug, serde::Deserialize)]
33struct Compatibility {
34 #[serde(rename = "cargo-contract")]
35 cargo_contract_compatibility: HashMap<Version, Requirements>,
36}
37
38#[derive(Debug, serde::Deserialize)]
39struct Requirements {
40 #[serde(rename = "ink")]
41 ink_requirements: Vec<VersionReq>,
42}
43
44pub fn check_contract_ink_compatibility(
47 ink_version: &Version,
48 cargo_contract_version: Option<Version>,
49) -> Result<()> {
50 let compatibility_list = include_str!("../compatibility_list.json");
51 let compatibility: Compatibility = serde_json::from_str(compatibility_list)?;
52 let cargo_contract_version = if let Some(version) = cargo_contract_version {
53 version
54 } else {
55 semver::Version::parse(VERSION).expect("Parsing version failed")
56 };
57 let ink_req = &compatibility
58 .cargo_contract_compatibility
59 .get(&cargo_contract_version)
60 .ok_or(anyhow!(
61 "Missing compatibility configuration for cargo-contract: {}",
62 cargo_contract_version
63 ))?
64 .ink_requirements;
65
66 if ink_req.is_empty() {
68 bail!(
69 "Missing ink! requirements for cargo-contract: {}",
70 cargo_contract_version
71 );
72 }
73
74 if !ink_req.iter().any(|req| req.matches(ink_version)) {
76 let ink_required_versions = ink_req
78 .iter()
79 .map(|req| format!("'{}'", req))
80 .collect::<Vec<_>>()
81 .join(", ");
82
83 let ink_update_message = format!(
84 "change the ink! version of your contract to {}",
85 ink_required_versions
86 );
87 let contract_not_compatible_message = "This version of cargo-contract is not \
88 compatible with the contract's ink! version.";
89
90 let best_cargo_contract_version = compatibility
92 .cargo_contract_compatibility
93 .iter()
94 .filter_map(|(ver, reqs)| {
95 if reqs
96 .ink_requirements
97 .iter()
98 .any(|req| req.matches(ink_version))
99 {
100 return Some(ver)
101 }
102 None
103 })
104 .max_by(|&a, &b| {
105 match (!a.pre.is_empty(), !b.pre.is_empty()) {
106 (false, true) => std::cmp::Ordering::Greater,
107 (true, false) => std::cmp::Ordering::Less,
108 (_, _) => a.cmp(b),
109 }
110 })
111 .ok_or(anyhow!(
112 "{} {}",
113 contract_not_compatible_message,
114 ink_update_message
115 ))?;
116
117 bail!(
118 "{} Please use cargo-contract in version \
119 '{}' or {}",
120 contract_not_compatible_message,
121 best_cargo_contract_version,
122 ink_update_message
123 );
124 }
125 Ok(())
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use pretty_assertions::assert_eq;
132
133 #[test]
134 fn ink_check_failes_when_incompatible_version() {
135 let ink_version = Version::new(3, 2, 0);
136 let cargo_contract_version = Some(Version::new(3, 2, 0));
137 let res = check_contract_ink_compatibility(
138 &ink_version,
139 cargo_contract_version.clone(),
140 )
141 .expect_err("Ink version check should fail");
142
143 assert_eq!(
144 res.to_string(),
145 "This version of cargo-contract is not compatible with the contract's ink! version. \
146 Please use cargo-contract in version '1.5.0' or change \
147 the ink! version of your contract to '^4.0.0-alpha.3', '^4.0.0'"
148 );
149
150 let ink_version =
151 Version::parse("4.0.0-alpha.1").expect("Parsing version must work");
152 let res = check_contract_ink_compatibility(&ink_version, cargo_contract_version)
153 .expect_err("Ink version check should fail");
154
155 assert_eq!(
156 res.to_string(),
157 "This version of cargo-contract is not compatible with the contract's ink! version. \
158 Please use cargo-contract in version '1.5.0' or change \
159 the ink! version of your contract to '^4.0.0-alpha.3', '^4.0.0'"
160 );
161 }
162
163 #[test]
164 fn ink_check_succeeds_when_compatible_version() {
165 let ink_version = Version::new(4, 2, 0);
166 let cargo_contract_version = Some(Version::new(3, 2, 0));
167 let res = check_contract_ink_compatibility(&ink_version, cargo_contract_version);
168 assert!(res.is_ok());
169
170 let ink_version =
171 Version::parse("5.0.0-rc.3").expect("Parsing version must work");
172 let res = check_contract_ink_compatibility(&ink_version, None);
173 assert!(res.is_ok());
174 }
175
176 #[test]
177 fn check_current_ink_and_cargo_contract_versions() {
178 let cargo_contract_version = Some(Version::new(5, 0, 3));
180
181 let ink_version = Version::new(5, 1, 1);
182 let res = check_contract_ink_compatibility(
183 &ink_version,
184 cargo_contract_version.clone(),
185 );
186 assert!(res.is_ok());
187
188 let ink_version = Version::new(5, 0, 0);
189 let res = check_contract_ink_compatibility(
190 &ink_version,
191 cargo_contract_version.clone(),
192 );
193 assert!(res.is_ok());
194
195 let ink_version = Version::new(5, 1, 0);
196 let res = check_contract_ink_compatibility(&ink_version, cargo_contract_version);
197 assert!(res.is_ok());
198 }
199}