contract_metadata/
compatibility.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use std::collections::HashMap;
18
19use anyhow::{
20    anyhow,
21    bail,
22    Result,
23};
24use semver::{
25    Version,
26    VersionReq,
27};
28
29/// Version of the currently executing `cargo-contract` binary.
30const 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
44/// Checks whether the contract's ink! version is compatible with the cargo-contract
45/// binary.
46pub 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    // Ink! requirements can not be empty
67    if ink_req.is_empty() {
68        bail!(
69            "Missing ink! requirements for cargo-contract: {}",
70            cargo_contract_version
71        );
72    }
73
74    // Check if the ink! version matches any of the requirement
75    if !ink_req.iter().any(|req| req.matches(ink_version)) {
76        // Get required ink! versions
77        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        // Find best cargo-contract version
91        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        // Current `cargo-contract` version is 5.0.3
179        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}