diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index deda608395ae5..f78e1587f19c8 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5894,6 +5894,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "fromRlp", + "description": "RLP decodes an RLP payload into a list of bytes.", + "declaration": "function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data);", + "visibility": "external", + "mutability": "pure", + "signature": "fromRlp(bytes)", + "selector": "0x1e1d8b63", + "selectorBytes": [ + 30, + 29, + 139, + 99 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "fsMetadata", @@ -10822,6 +10842,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "toRlp", + "description": "RLP encodes a list of bytes into an RLP payload.", + "declaration": "function toRlp(bytes[] calldata data) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toRlp(bytes[])", + "selector": "0xa7ed3885", + "selectorBytes": [ + 167, + 237, + 56, + 133 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "toString_0", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index c3722aae73ab7..79ed9d41b325a 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2946,6 +2946,13 @@ interface Vm { /// Generates a ready-to-sign digest of human-readable typed data following the EIP-712 standard. #[cheatcode(group = Utilities)] function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest); + + /// RLP encodes a list of bytes into an RLP payload. + #[cheatcode(group = Utilities)] + function toRlp(bytes[] calldata data) external pure returns (bytes memory); + /// RLP decodes an RLP payload into a list of bytes. + #[cheatcode(group = Utilities)] + function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data); } } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 024a2badb8c71..89bece91777ce 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -4,6 +4,7 @@ use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::* use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, TypedData, eip712_parser::EncodeType}; use alloy_ens::namehash; use alloy_primitives::{B64, Bytes, U256, aliases::B32, keccak256, map::HashMap}; +use alloy_rlp::{Decodable, Encodable}; use alloy_sol_types::SolValue; use foundry_common::{TYPE_BINDING_PREFIX, fs}; use foundry_config::fs_permissions::FsAccessKind; @@ -470,3 +471,25 @@ fn get_struct_hash(primary: &str, type_def: &String, abi_encoded_data: &Bytes) - Ok(keccak256(&bytes_to_hash).to_vec()) } + +impl Cheatcode for toRlpCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + + let mut buf = Vec::new(); + data.encode(&mut buf); + + Ok(Bytes::from(buf).abi_encode()) + } +} + +impl Cheatcode for fromRlpCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { rlp } = self; + + let decoded: Vec = Vec::::decode(&mut rlp.as_ref()) + .map_err(|e| fmt_err!("Failed to decode RLP: {e}"))?; + + Ok(decoded.abi_encode()) + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 5959572375115..dbaf4b4e56ded 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -288,6 +288,7 @@ interface Vm { function ffi(string[] calldata commandInput) external returns (bytes memory result); function foundryVersionAtLeast(string calldata version) external view returns (bool); function foundryVersionCmp(string calldata version) external view returns (int256); + function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data); function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); function getArtifactPathByCode(bytes calldata code) external view returns (string memory path); function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path); @@ -534,6 +535,7 @@ interface Vm { 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 toRlp(bytes[] calldata data) external pure returns (bytes memory); 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); diff --git a/testdata/default/cheats/Rlp.t.sol b/testdata/default/cheats/Rlp.t.sol new file mode 100644 index 0000000000000..7c70f7cf48030 --- /dev/null +++ b/testdata/default/cheats/Rlp.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Rlp is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testToRlp() public { + bytes[] memory data = new bytes[](2); + data[0] = hex"01"; + data[1] = hex"02"; + + bytes memory rlp = vm.toRlp(data); + + // Assert the expected RLP encoding for [0x01, 0x02] + // 0xc2 = list with 2 bytes total length + // 0x01 = first byte + // 0x02 = second byte + assertEq(rlp, hex"c20102"); + } + + function testFromRlp() public { + // RLP encoded [0x01, 0x02] + bytes memory rlp = hex"c20102"; + + bytes[] memory decoded = vm.fromRlp(rlp); + assertEq(decoded.length, 2); + assertEq(decoded[0], hex"01"); + assertEq(decoded[1], hex"02"); + } + + function testRoundTrip() public { + bytes[] memory original = new bytes[](3); + original[0] = hex"deadbeef"; + original[1] = hex"cafebabe"; + original[2] = hex"01020304"; + + bytes memory rlp = vm.toRlp(original); + bytes[] memory decoded = vm.fromRlp(rlp); + + assertEq(decoded.length, original.length); + for (uint256 i = 0; i < original.length; i++) { + assertEq(decoded[i], original[i]); + } + } +}