From a416c1001c7dba772d008a5fc43e43b024a9d684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Sat, 27 Jul 2024 08:36:28 +0200 Subject: [PATCH] feat(cheatcodes): add `vm.getFoundryVersion()` (#8530) * feat: implement `vm.getFoundryVersion` * test: implement dummy test for `vm.getFoundryVersion` * chore: modify implementation to return cargo version and build timestamp * test: modify test * docs: add sample output * chore: cargo cheats * fix: failing test and vergen setup * test: update getFoundryVersion * docs: mention built timestamps issue --------- Co-authored-by: Matthias Seitz --- Cargo.lock | 2 ++ crates/cheatcodes/Cargo.toml | 10 ++++++- crates/cheatcodes/assets/cheatcodes.json | 20 ++++++++++++++ crates/cheatcodes/build.rs | 3 +++ crates/cheatcodes/spec/src/vm.rs | 9 +++++++ crates/cheatcodes/src/test.rs | 17 ++++++++++++ testdata/cheats/Vm.sol | 1 + .../default/cheats/GetFoundryVersion.t.sol | 26 +++++++++++++++++++ 8 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 crates/cheatcodes/build.rs create mode 100644 testdata/default/cheats/GetFoundryVersion.t.sol diff --git a/Cargo.lock b/Cargo.lock index 29d6286536db..a5f35ad65f9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3576,6 +3576,7 @@ dependencies = [ "alloy-signer-local", "alloy-sol-types", "base64 0.22.1", + "chrono", "dialoguer", "eyre", "foundry-cheatcodes-spec", @@ -3598,6 +3599,7 @@ dependencies = [ "thiserror", "toml 0.8.15", "tracing", + "vergen", "walkdir", ] diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 60304a47dd0d..80336d4e8f64 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -14,6 +14,13 @@ exclude.workspace = true [lints] workspace = true +[build-dependencies] +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } + [dependencies] foundry-cheatcodes-spec.workspace = true foundry-common.workspace = true @@ -54,6 +61,7 @@ semver.workspace = true rustc-hash.workspace = true dialoguer = "0.11.0" rand = "0.8" +chrono.workspace = true [dev-dependencies] -proptest.workspace = true \ No newline at end of file +proptest.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index ec12113eb31b..61f626477705 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -4991,6 +4991,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getFoundryVersion", + "description": "Returns the Foundry version.\nFormat: ++\nSample output: 0.2.0+faa94c384+202407110019\nNote: Build timestamps may vary slightly across platforms due to separate CI jobs.\nFor reliable version comparisons, use YYYYMMDD0000 format (e.g., >= 202407110000)\nto compare timestamps while ignoring minor time differences.", + "declaration": "function getFoundryVersion() external view returns (string memory version);", + "visibility": "external", + "mutability": "view", + "signature": "getFoundryVersion()", + "selector": "0xea991bb5", + "selectorBytes": [ + 234, + 153, + 27, + 181 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getLabel", diff --git a/crates/cheatcodes/build.rs b/crates/cheatcodes/build.rs new file mode 100644 index 000000000000..c2f550fb6f82 --- /dev/null +++ b/crates/cheatcodes/build.rs @@ -0,0 +1,3 @@ +fn main() { + vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); +} diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index ff9a92e58930..935d44e53a4e 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -702,6 +702,15 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function breakpoint(string calldata char, bool value) external; + /// Returns the Foundry version. + /// Format: ++ + /// Sample output: 0.2.0+faa94c384+202407110019 + /// Note: Build timestamps may vary slightly across platforms due to separate CI jobs. + /// For reliable version comparisons, use YYYYMMDD0000 format (e.g., >= 202407110000) + /// to compare timestamps while ignoring minor time differences. + #[cheatcode(group = Testing, safety = Safe)] + function getFoundryVersion() external view returns (string memory version); + /// Returns the RPC url for the given alias. #[cheatcode(group = Testing, safety = Safe)] function rpcUrl(string calldata rpcAlias) external view returns (string memory json); diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 4bccabda253e..279150dff89f 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,5 +1,8 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. +use chrono::DateTime; +use std::env; + use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Error, Result, Vm::*}; use alloy_primitives::Address; use alloy_sol_types::SolValue; @@ -33,6 +36,20 @@ impl Cheatcode for breakpoint_1Call { } } +impl Cheatcode for getFoundryVersionCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self {} = self; + let cargo_version = env!("CARGO_PKG_VERSION"); + let git_sha = env!("VERGEN_GIT_SHA"); + let build_timestamp = DateTime::parse_from_rfc3339(env!("VERGEN_BUILD_TIMESTAMP")) + .expect("Invalid build timestamp format") + .format("%Y%m%d%H%M") + .to_string(); + let foundry_version = format!("{cargo_version}+{git_sha}+{build_timestamp}"); + Ok(foundry_version.abi_encode()) + } +} + impl Cheatcode for rpcUrlCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { rpcAlias } = self; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index dfe466d3aada..f42fb22983ea 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -245,6 +245,7 @@ interface Vm { function getBlockTimestamp() external view returns (uint256 timestamp); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + function getFoundryVersion() external view returns (string memory version); function getLabel(address account) external view returns (string memory currentLabel); function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent); function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol new file mode 100644 index 000000000000..6f850561d71b --- /dev/null +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetFoundryVersionTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetFoundryVersion() public view { + string memory fullVersionString = vm.getFoundryVersion(); + + string[] memory versionComponents = vm.split(fullVersionString, "+"); + require(versionComponents.length == 3, "Invalid version format"); + + string memory semanticVersion = versionComponents[0]; + require(bytes(semanticVersion).length > 0, "Semantic version is empty"); + + string memory commitHash = versionComponents[1]; + require(bytes(commitHash).length > 0, "Commit hash is empty"); + + uint256 buildUnixTimestamp = vm.parseUint(versionComponents[2]); + uint256 minimumAcceptableTimestamp = 202406111234; + require(buildUnixTimestamp >= minimumAcceptableTimestamp, "Build timestamp is too old"); + } +}