From c61dc80ce75156d717b968c9f0f882b5d021aa1e Mon Sep 17 00:00:00 2001 From: Darex <1010adigupta@gmail.com> Date: Fri, 26 Jan 2024 03:02:54 +0530 Subject: [PATCH] native string utils cheatcodes (#6891) * string utils cheatcodes created * fmt: run rustfmt * tests: solidity tests added * cargo cheats * update * update * chore: update defs --------- Co-authored-by: Matthias Seitz Co-authored-by: Enrique Ortiz --- crates/cheatcodes/assets/cheatcodes.json | 100 +++++++++++++++++++++++ crates/cheatcodes/spec/src/vm.rs | 16 ++++ crates/cheatcodes/src/string.rs | 47 ++++++++++- testdata/cheats/StringUtils.t.sol | 42 ++++++++++ testdata/cheats/Vm.sol | 5 ++ 5 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 testdata/cheats/StringUtils.t.sol diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 6ccbcd21f968..7198f615288a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5913,6 +5913,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "replace", + "description": "Replaces occurrences of `from` in the given `string` with `to`.", + "declaration": "function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "replace(string,string,string)", + "selector": "0xe00ad03e", + "selectorBytes": [ + 224, + 10, + 208, + 62 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "resetNonce", @@ -6713,6 +6733,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "split", + "description": "Splits the given `string` into an array of strings divided by the `delimiter`.", + "declaration": "function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs);", + "visibility": "external", + "mutability": "pure", + "signature": "split(string,string)", + "selector": "0x8bb75533", + "selectorBytes": [ + 139, + 183, + 85, + 51 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "startBroadcast_0", @@ -7033,6 +7073,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "toLowercase", + "description": "Converts the given `string` value to Lowercase.", + "declaration": "function toLowercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toLowercase(string)", + "selector": "0x50bb0884", + "selectorBytes": [ + 80, + 187, + 8, + 132 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "toString_0", @@ -7153,6 +7213,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "toUppercase", + "description": "Converts the given `string` value to Uppercase.", + "declaration": "function toUppercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toUppercase(string)", + "selector": "0x074ae3d7", + "selectorBytes": [ + 7, + 74, + 227, + 215 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "transact_0", @@ -7193,6 +7273,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "trim", + "description": "Trims leading and trailing whitespace from the given `string` value.", + "declaration": "function trim(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "trim(string)", + "selector": "0xb2dad155", + "selectorBytes": [ + 178, + 218, + 209, + 85 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "tryFfi", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 69f1e084572d..6ab18d10935b 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1623,6 +1623,22 @@ interface Vm { #[cheatcode(group = String)] function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); + /// Converts the given `string` value to Lowercase. + #[cheatcode(group = String)] + function toLowercase(string calldata input) external pure returns (string memory output); + /// Converts the given `string` value to Uppercase. + #[cheatcode(group = String)] + function toUppercase(string calldata input) external pure returns (string memory output); + /// Trims leading and trailing whitespace from the given `string` value. + #[cheatcode(group = String)] + function trim(string calldata input) external pure returns (string memory output); + /// Replaces occurrences of `from` in the given `string` with `to`. + #[cheatcode(group = String)] + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); + /// Splits the given `string` into an array of strings divided by the `delimiter`. + #[cheatcode(group = String)] + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); + // ======== JSON Parsing and Manipulation ======== // -------- Reading -------- diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index f55350592ea1..b7992b9450fd 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -94,6 +94,47 @@ impl Cheatcode for parseBoolCall { } } +// toLowercase +impl Cheatcode for toLowercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_lowercase().abi_encode()) + } +} + +// toUppercase +impl Cheatcode for toUppercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_uppercase().abi_encode()) + } +} + +// trim +impl Cheatcode for trimCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.trim().abi_encode()) + } +} + +// Replace +impl Cheatcode for replaceCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, from, to } = self; + Ok(input.replace(from, to).abi_encode()) + } +} + +// Split +impl Cheatcode for splitCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, delimiter } = self; + let parts: Vec<&str> = input.split(delimiter).collect(); + Ok(parts.abi_encode()) + } +} + pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { parse_value(s, ty).map(|v| v.abi_encode()) } @@ -136,7 +177,9 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option false, s if s.eq_ignore_ascii_case("true") => true, s if s.eq_ignore_ascii_case("false") => false, - _ => return None, + _ => { + return None; + } }; return Some(Ok(DynSolValue::Bool(b))); } @@ -145,7 +188,7 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option { if !s.starts_with("0x") && s.chars().all(|c| c.is_ascii_hexdigit()) { - return Some(Err("missing hex prefix (\"0x\") for hex string")) + return Some(Err("missing hex prefix (\"0x\") for hex string")); } } _ => {} diff --git a/testdata/cheats/StringUtils.t.sol b/testdata/cheats/StringUtils.t.sol new file mode 100644 index 000000000000..4fe8bba01447 --- /dev/null +++ b/testdata/cheats/StringUtils.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "./Vm.sol"; + +contract StringManipulationTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testToLowercase() public { + string memory original = "Hello World"; + string memory lowercased = vm.toLowercase(original); + assertEq("hello world", lowercased); + } + + function testToUppercase() public { + string memory original = "Hello World"; + string memory uppercased = vm.toUppercase(original); + assertEq("HELLO WORLD", uppercased); + } + + function testTrim() public { + string memory original = " Hello World "; + string memory trimmed = vm.trim(original); + assertEq("Hello World", trimmed); + } + + function testReplace() public { + string memory original = "Hello World"; + string memory replaced = vm.replace(original, "World", "Reth"); + assertEq("Hello Reth", replaced); + } + + function testSplit() public { + string memory original = "Hello,World,Reth"; + string[] memory splitResult = vm.split(original, ","); + assertEq(3, splitResult.length); + assertEq("Hello", splitResult[0]); + assertEq("World", splitResult[1]); + assertEq("Reth", splitResult[2]); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index ed1563aa2b76..bac97febb2f9 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -293,6 +293,7 @@ interface Vm { function rememberKey(uint256 privateKey) external returns (address keyAddr); function removeDir(string calldata path, bool recursive) external; function removeFile(string calldata path) external; + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); function resetNonce(address account) external; function resumeGasMetering() external; function revertTo(uint256 snapshotId) external returns (bool success); @@ -333,6 +334,7 @@ interface Vm { function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshot() external returns (uint256 snapshotId); + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; function startBroadcast(uint256 privateKey) external; @@ -349,14 +351,17 @@ interface Vm { function toBase64URL(string calldata data) external pure returns (string memory); function toBase64(bytes calldata data) external pure returns (string memory); function toBase64(string calldata data) external pure returns (string memory); + function toLowercase(string calldata input) external pure returns (string memory output); function toString(address value) external pure returns (string memory stringifiedValue); function toString(bytes calldata value) external pure returns (string memory stringifiedValue); function toString(bytes32 value) external pure returns (string memory stringifiedValue); function toString(bool value) external pure returns (string memory stringifiedValue); function toString(uint256 value) external pure returns (string memory stringifiedValue); function toString(int256 value) external pure returns (string memory stringifiedValue); + function toUppercase(string calldata input) external pure returns (string memory output); function transact(bytes32 txHash) external; function transact(uint256 forkId, bytes32 txHash) external; + function trim(string calldata input) external pure returns (string memory output); function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); function txGasPrice(uint256 newGasPrice) external; function unixTime() external returns (uint256 milliseconds);