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