diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index d64df4860abe..b41f736677bc 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3347,6 +3347,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "contains", + "description": "Returns true if `search` is found in `subject`, false otherwise.", + "declaration": "function contains(string calldata subject, string calldata search) external returns (bool result);", + "visibility": "external", + "mutability": "", + "signature": "contains(string,string)", + "selector": "0x3fb18aec", + "selectorBytes": [ + 63, + 177, + 138, + 236 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "cool", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 761d64e9bedb..2a342a04438e 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1976,6 +1976,9 @@ interface Vm { /// Returns 0 in case of an empty `key`. #[cheatcode(group = String)] function indexOf(string calldata input, string calldata key) external pure returns (uint256); + /// Returns true if `search` is found in `subject`, false otherwise. + #[cheatcode(group = String)] + function contains(string calldata subject, string calldata search) external returns (bool result); // ======== JSON Parsing and Manipulation ======== diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index e7435d541048..a4c06eef650b 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -144,6 +144,14 @@ impl Cheatcode for indexOfCall { } } +// contains +impl Cheatcode for containsCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { subject, search } = self; + Ok(subject.contains(search).abi_encode()) + } +} + pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { parse_value(s, ty).map(|v| v.abi_encode()) } diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 2572108972b1..e47a79cc65dd 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -162,6 +162,7 @@ interface Vm { function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); + function contains(string calldata subject, string calldata search) external returns (bool result); function cool(address target) external; function copyFile(string calldata from, string calldata to) external returns (uint64 copied); function copyStorage(address from, address to) external; diff --git a/testdata/default/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol index b65346a7a4e0..256d65302a44 100644 --- a/testdata/default/cheats/StringUtils.t.sol +++ b/testdata/default/cheats/StringUtils.t.sol @@ -51,4 +51,10 @@ contract StringManipulationTest is DSTest { assertEq(vm.indexOf(input, key3), 0); assertEq(vm.indexOf(input, key4), type(uint256).max); } + + function testContains() public { + string memory subject = "this is a test"; + assert(vm.contains(subject, "test")); + assert(!vm.contains(subject, "foundry")); + } }