diff --git a/Cargo.lock b/Cargo.lock index 02bb79be5d20..0fde23ac095c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,6 +2928,7 @@ dependencies = [ "alloy-providers", "alloy-rpc-types", "alloy-sol-types", + "base64 0.21.7", "const-hex", "ethers-core", "ethers-signers", diff --git a/Cargo.toml b/Cargo.toml index 730355287dc3..21acb389c2b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,6 +178,7 @@ protobuf = "=3.2.0" rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } +base64 = "0.21" toml = "0.8" tracing = "0.1" tracing-subscriber = "0.3" diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index bb000f5c3198..7225111fed4d 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -34,5 +34,6 @@ itertools.workspace = true jsonpath_lib.workspace = true revm.workspace = true serde_json.workspace = true +base64.workspace = true tracing.workspace = true walkdir = "2" diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 8b69d7e257cc..fea491981baf 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -4533,6 +4533,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "toBase64", + "description": "Encodes a `bytes` value to base64 string", + "declaration": "function toBase64(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64(bytes)", + "selector": "0xa5cbfe65", + "selectorBytes": [ + 165, + 203, + 254, + 101 + ] + }, + "group": "base64", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64URL", + "description": "Encodes a `bytes` value to base64url string", + "declaration": "function toBase64URL(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64URL(bytes)", + "selector": "0xc8bd0e4a", + "selectorBytes": [ + 200, + 189, + 14, + 74 + ] + }, + "group": "base64", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "toString_0", diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index 7b68420ad63b..1231ec0458e6 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -298,6 +298,13 @@ "json" ] }, + { + "description": "Utility cheatcodes that deal with encoding and decoding Base64.\n\nExamples: `toBase64`, `toBase64URL`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "base64" + ] + }, { "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", "type": "string", diff --git a/crates/cheatcodes/spec/src/cheatcode.rs b/crates/cheatcodes/spec/src/cheatcode.rs index f7e0c87b306f..e6ed724629fe 100644 --- a/crates/cheatcodes/spec/src/cheatcode.rs +++ b/crates/cheatcodes/spec/src/cheatcode.rs @@ -103,6 +103,12 @@ pub enum Group { /// /// Safety: safe. Json, + /// Utility cheatcodes that deal with encoding and decoding Base64. + /// + /// Examples: `toBase64`, `toBase64URL`. + /// + /// Safety: safe. + Base64, /// Generic, uncategorized utilities. /// /// Examples: `toString`, `parse*`, `serialize*`. @@ -125,6 +131,7 @@ impl Group { Self::Environment | Self::String | Self::Json | + Self::Base64 | Self::Utilities => Some(Safety::Safe), } } @@ -140,6 +147,7 @@ impl Group { Self::Environment => "environment", Self::String => "string", Self::Json => "json", + Self::Base64 => "base64", Self::Utilities => "utilities", } } diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 1577e6189b84..819ddd41ceaf 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1228,6 +1228,16 @@ interface Vm { #[cheatcode(group = Json)] function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + // -------- Base64 -------- + + /// Encodes a `bytes` value to base64 string + #[cheatcode(group = Base64)] + function toBase64(bytes calldata data) external pure returns (string memory); + + /// Encodes a `bytes` value to base64url string + #[cheatcode(group = Base64)] + function toBase64URL(bytes calldata data) external pure returns (string memory); + // -------- Key Management -------- /// Derives a private key from the name, labels the account with that name, and returns the wallet. diff --git a/crates/cheatcodes/src/base64.rs b/crates/cheatcodes/src/base64.rs new file mode 100644 index 000000000000..d9d0bc86f76c --- /dev/null +++ b/crates/cheatcodes/src/base64.rs @@ -0,0 +1,17 @@ +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_sol_types::SolValue; +use base64::prelude::*; + +impl Cheatcode for toBase64Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_STANDARD.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64URLCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + } +} diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index b7a401c074a8..856467f2990d 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -26,6 +26,7 @@ pub use config::CheatsConfig; mod inspector; pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; +mod base64; mod env; mod evm; mod fs; diff --git a/testdata/cheats/Base64.t.sol b/testdata/cheats/Base64.t.sol new file mode 100644 index 000000000000..88b792cb2991 --- /dev/null +++ b/testdata/cheats/Base64.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "./Vm.sol"; +import "../logs/console.sol"; + +contract Base64Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_toBase64() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u/w=="; + string memory actual = vm.toBase64(input); + assertEq(actual, expected); + } + + function test_toBase64URL() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u_w=="; + string memory actual = vm.toBase64URL(input); + assertEq(actual, expected); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index cc9a1aa8ab22..86afd8d06d2b 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -224,6 +224,8 @@ interface Vm { function stopMappingRecording() external; function stopPrank() external; function store(address target, bytes32 slot, bytes32 value) external; + function toBase64(bytes calldata data) external pure returns (string memory); + function toBase64URL(bytes calldata data) external pure returns (string 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);