Skip to content

Commit 7e8c6e9

Browse files
feat: add vm.foundryVersionCmp and vm.foundryVersionAtLeast cheatcodes (foundry-rs#9845)
* feat: add foundryVersionCmp and foundryVersionAtLeast cheatcode * fix: change the acceptable version string in version cmp cheatcodes to major.minor.patch --------- Co-authored-by: grandizzy <[email protected]>
1 parent fdd78d9 commit 7e8c6e9

File tree

6 files changed

+155
-0
lines changed

6 files changed

+155
-0
lines changed

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,21 @@ interface Vm {
16771677
string calldata error
16781678
) external pure;
16791679

1680+
/// Returns true if the current Foundry version is at least the given version.
1681+
/// Version string can be in the format `major.minor.patch`.
1682+
#[cheatcode(group = Testing, safety = Safe)]
1683+
function foundryVersionAtLeast(string calldata version) external view returns (bool);
1684+
1685+
/// Compares the current Foundry version with the given version string.
1686+
/// Version string can be in the format `major.minor.patch`.
1687+
/// Returns:
1688+
/// -1 if current version is less than the given version
1689+
/// 0 if current version equals the given version
1690+
/// 1 if current version is greater than the given version
1691+
#[cheatcode(group = Testing, safety = Safe)]
1692+
function foundryVersionCmp(string calldata version) external view returns (int256);
1693+
1694+
16801695
// ======== OS and Filesystem ========
16811696

16821697
// -------- Metadata --------

crates/cheatcodes/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ mod config;
3737

3838
mod crypto;
3939

40+
mod version;
41+
4042
mod env;
4143
pub use env::set_execution_context;
4244

crates/cheatcodes/src/version.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
2+
use alloy_sol_types::SolValue;
3+
use foundry_common::version::SEMVER_VERSION;
4+
use semver::Version;
5+
use std::cmp::Ordering;
6+
7+
impl Cheatcode for foundryVersionCmpCall {
8+
fn apply(&self, _state: &mut Cheatcodes) -> Result {
9+
let Self { version } = self;
10+
11+
if version.contains("+") || version.contains("-") {
12+
return Err(fmt_err!("Version must be in only major.minor.patch format"));
13+
}
14+
15+
let parsed_version = Version::parse(version)
16+
.map_err(|e| fmt_err!("Invalid semver format '{}': {}", version, e))?;
17+
let current_semver = Version::parse(SEMVER_VERSION)
18+
.map_err(|_| fmt_err!("Invalid current version format"))?;
19+
20+
let current_version =
21+
Version::new(current_semver.major, current_semver.minor, current_semver.patch);
22+
// Note: returns -1 if current < provided, 0 if equal, 1 if current > provided.
23+
let cmp_result = match current_version.cmp(&parsed_version) {
24+
Ordering::Less => -1i32,
25+
Ordering::Equal => 0i32,
26+
Ordering::Greater => 1i32,
27+
};
28+
Ok(cmp_result.abi_encode())
29+
}
30+
}
31+
32+
impl Cheatcode for foundryVersionAtLeastCall {
33+
fn apply(&self, _state: &mut Cheatcodes) -> Result {
34+
let Self { version } = self;
35+
36+
if version.contains("+") || version.contains("-") {
37+
return Err(fmt_err!("Version must be in only major.minor.patch format"));
38+
}
39+
40+
let parsed_version =
41+
Version::parse(version).map_err(|_| fmt_err!("Invalid version format"))?;
42+
let current_semver = Version::parse(SEMVER_VERSION)
43+
.map_err(|_| fmt_err!("Invalid current version format"))?;
44+
45+
let current_version =
46+
Version::new(current_semver.major, current_semver.minor, current_semver.patch);
47+
48+
let at_least = current_version.cmp(&parsed_version) != Ordering::Less;
49+
Ok(at_least.abi_encode())
50+
}
51+
}

testdata/cheats/Vm.sol

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testdata/default/cheats/GetFoundryVersion.t.sol

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,49 @@ contract GetFoundryVersionTest is DSTest {
4242
// Validate build profile (e.g., "debug" or "release")
4343
require(bytes(buildType).length > 0, "Build type is empty");
4444
}
45+
46+
function testFoundryVersionCmp() public {
47+
// Should return -1 if current version is less than argument
48+
assertEq(vm.foundryVersionCmp("99.0.0"), -1);
49+
50+
// (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug)
51+
string memory fullVersionString = vm.getFoundryVersion();
52+
53+
// Step 1: Split the version at "+"
54+
string[] memory plusSplit = vm.split(fullVersionString, "+");
55+
require(plusSplit.length == 2, "Invalid version format: Missing '+' separator");
56+
57+
// Step 2: Extract parts
58+
string memory semanticVersion = plusSplit[0]; // "0.3.0-dev"
59+
string[] memory semanticSplit = vm.split(semanticVersion, "-");
60+
61+
semanticVersion = semanticSplit[0]; // "0.3.0"
62+
// Should return 0 if current version is equal to argument
63+
assertEq(vm.foundryVersionCmp(semanticVersion), 0);
64+
65+
// Should return 1 if current version is greater than argument
66+
assertEq(vm.foundryVersionCmp("0.0.1"), 1);
67+
}
68+
69+
function testFoundryVersionAtLeast() public {
70+
// Should return false for future versions
71+
assertEq(vm.foundryVersionAtLeast("99.0.0"), false);
72+
73+
// (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug)
74+
string memory fullVersionString = vm.getFoundryVersion();
75+
76+
// Step 1: Split the version at "+"
77+
string[] memory plusSplit = vm.split(fullVersionString, "+");
78+
require(plusSplit.length == 2, "Invalid version format: Missing '+' separator");
79+
80+
// Step 2: Extract parts
81+
string memory semanticVersion = plusSplit[0]; // "0.3.0-dev"
82+
string[] memory semanticSplit = vm.split(semanticVersion, "-");
83+
84+
semanticVersion = semanticSplit[0]; // "0.3.0"
85+
assertTrue(vm.foundryVersionAtLeast(semanticVersion));
86+
87+
// Should return true for past versions
88+
assertTrue(vm.foundryVersionAtLeast("0.2.0"));
89+
}
4590
}

0 commit comments

Comments
 (0)