foundry_cheatcodes/
version.rs1use crate::{Cheatcode, Result, Vm::*};
2use alloy_sol_types::SolValue;
3use foundry_common::version::SEMVER_VERSION;
4use semver::Version;
5use std::cmp::Ordering;
6
7impl Cheatcode for foundryVersionCmpCall {
8 fn apply_context_free(&self) -> Result {
9 let Self { version } = self;
10 foundry_version_cmp(version).map(|cmp| (cmp as i8).abi_encode())
11 }
12}
13
14impl Cheatcode for foundryVersionAtLeastCall {
15 fn apply_context_free(&self) -> Result {
16 let Self { version } = self;
17 foundry_version_cmp(version).map(|cmp| cmp.is_ge().abi_encode())
18 }
19}
20
21fn foundry_version_cmp(version: &str) -> Result<Ordering> {
22 version_cmp(strip_semver_metadata(SEMVER_VERSION), version)
23}
24
25fn strip_semver_metadata(version: &str) -> &str {
29 version.split(['-', '+']).next().unwrap()
30}
31
32fn version_cmp(version_a: &str, version_b: &str) -> Result<Ordering> {
33 let version_a = parse_version(version_a)?;
34 let version_b = parse_version(version_b)?;
35 Ok(version_a.cmp(&version_b))
36}
37
38fn parse_version(version: &str) -> Result<Version> {
39 let version =
40 Version::parse(version).map_err(|e| fmt_err!("invalid version `{version}`: {e}"))?;
41 if !version.pre.is_empty() {
42 return Err(fmt_err!(
43 "invalid version `{version}`: pre-release versions are not supported"
44 ));
45 }
46 if !version.build.is_empty() {
47 return Err(fmt_err!("invalid version `{version}`: build metadata is not supported"));
48 }
49 Ok(version)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn strips_build_metadata_only() {
58 assert_eq!(strip_semver_metadata("1.7.1+abc1234567.1737036656.release"), "1.7.1");
60 }
61
62 #[test]
63 fn strips_pre_release_and_build_metadata() {
64 assert_eq!(strip_semver_metadata("1.7.1-nightly+abc1234567.1737036656.release"), "1.7.1");
66 assert_eq!(strip_semver_metadata("1.7.1-dev+abc1234567.1737036656.debug"), "1.7.1");
68 }
69
70 #[test]
71 fn strips_plain_version() {
72 assert_eq!(strip_semver_metadata("1.7.1"), "1.7.1");
73 }
74
75 #[test]
76 fn version_cmp_orders_correctly() {
77 assert_eq!(version_cmp("1.7.1", "1.7.1").unwrap(), Ordering::Equal);
78 assert_eq!(version_cmp("1.7.1", "1.7.0").unwrap(), Ordering::Greater);
79 assert_eq!(version_cmp("1.7.1", "1.7.2").unwrap(), Ordering::Less);
80 assert_eq!(version_cmp("1.7.1", "0.0.1").unwrap(), Ordering::Greater);
81 assert_eq!(version_cmp("1.7.1", "99.0.0").unwrap(), Ordering::Less);
82 }
83
84 #[test]
85 fn parse_version_rejects_pre_release_and_build_metadata() {
86 assert!(parse_version("1.7.1-nightly").is_err());
88 assert!(parse_version("1.7.1+abc").is_err());
89 assert!(parse_version("not-a-version").is_err());
90 assert!(parse_version("1.7.1").is_ok());
91 }
92
93 #[test]
94 fn cmp_works_against_full_semver_version_strings() {
95 for current in [
97 "1.7.1+abc1234567.1737036656.release",
98 "1.7.1-nightly+abc1234567.1737036656.release",
99 "1.7.1-dev+abc1234567.1737036656.debug",
100 "1.7.1",
101 ] {
102 let stripped = strip_semver_metadata(current);
103 assert_eq!(version_cmp(stripped, "1.7.1").unwrap(), Ordering::Equal);
104 assert_eq!(version_cmp(stripped, "1.7.0").unwrap(), Ordering::Greater);
105 assert_eq!(version_cmp(stripped, "1.7.2").unwrap(), Ordering::Less);
106 }
107 }
108}