diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 2a125df98c5f7..f2dde361978a4 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -7011,26 +7011,6 @@ "status": "stable", "safety": "unsafe" }, - { - "func": { - "id": "rpc", - "description": "Performs an Ethereum JSON-RPC request to the current fork URL.", - "declaration": "function rpc(string calldata method, string calldata params) external returns (bytes memory data);", - "visibility": "external", - "mutability": "", - "signature": "rpc(string,string)", - "selector": "0x1206c8a8", - "selectorBytes": [ - 18, - 6, - 200, - 168 - ] - }, - "group": "evm", - "status": "stable", - "safety": "safe" - }, { "func": { "id": "rpcUrl", @@ -7091,6 +7071,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "rpc_0", + "description": "Performs an Ethereum JSON-RPC request to the current fork URL.", + "declaration": "function rpc(string calldata method, string calldata params) external returns (bytes memory data);", + "visibility": "external", + "mutability": "", + "signature": "rpc(string,string)", + "selector": "0x1206c8a8", + "selectorBytes": [ + 18, + 6, + 200, + 168 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rpc_1", + "description": "Performs an Ethereum JSON-RPC request to the given endpoint.", + "declaration": "function rpc(string calldata urlOrAlias, string calldata method, string calldata params) external returns (bytes memory data);", + "visibility": "external", + "mutability": "", + "signature": "rpc(string,string,string)", + "selector": "0x0199a220", + "selectorBytes": [ + 1, + 153, + 162, + 32 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "selectFork", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index cd8aa08c509c6..62c25ac3d4bbb 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -611,6 +611,12 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function rpc(string calldata method, string calldata params) external returns (bytes memory data); + /// Performs an Ethereum JSON-RPC request to the given endpoint. + #[cheatcode(group = Evm, safety = Safe)] + function rpc(string calldata urlOrAlias, string calldata method, string calldata params) + external + returns (bytes memory data); + /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index a5a16065ed241..a8cc830009fec 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -233,29 +233,20 @@ impl Cheatcode for isPersistentCall { } } -impl Cheatcode for rpcCall { +impl Cheatcode for rpc_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; - let provider = ProviderBuilder::new(&url).build()?; - let params_json: serde_json::Value = serde_json::from_str(params)?; - let result = - foundry_common::block_on(provider.raw_request(method.clone().into(), params_json)) - .map_err(|err| fmt_err!("{method:?}: {err}"))?; - - let result_as_tokens = match crate::json::json_value_to_token(&result) - .map_err(|err| fmt_err!("failed to parse result: {err}"))? - { - DynSolValue::FixedBytes(bytes, size) => { - // converted fixed bytes to bytes to prevent evm encoding issues: - DynSolValue::Bytes(bytes.as_slice()[..size].to_vec()) - } - DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), - val => val, - }; + rpc_call(&url, method, params) + } +} - Ok(result_as_tokens.abi_encode()) +impl Cheatcode for rpc_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { urlOrAlias, method, params } = self; + let url = state.config.rpc_url(urlOrAlias)?; + rpc_call(&url, method, params) } } @@ -392,3 +383,26 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { fn persist_caller(ccx: &mut CheatsCtxt) { ccx.ecx.db.add_persistent_account(ccx.caller); } + +/// Performs an Ethereum JSON-RPC request to the given endpoint. +fn rpc_call(url: &str, method: &str, params: &str) -> Result { + let provider = ProviderBuilder::new(url).build()?; + let params_json: serde_json::Value = serde_json::from_str(params)?; + let result = + foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) + .map_err(|err| fmt_err!("{method:?}: {err}"))?; + + let result_as_tokens = match crate::json::json_value_to_token(&result) + .map_err(|err| fmt_err!("failed to parse result: {err}"))? + { + // Convert fixed bytes to bytes to prevent encoding issues. + // See: + DynSolValue::FixedBytes(bytes, size) => { + DynSolValue::Bytes(bytes.as_slice()[..size].to_vec()) + } + DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), + val => val, + }; + + Ok(result_as_tokens.abi_encode()) +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 4a0ec81c60790..b9ee72e1b5ba2 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -346,10 +346,11 @@ interface Vm { function rollFork(bytes32 txHash) external; function rollFork(uint256 forkId, uint256 blockNumber) external; function rollFork(uint256 forkId, bytes32 txHash) external; - function rpc(string calldata method, string calldata params) external returns (bytes memory data); function rpcUrl(string calldata rpcAlias) external view returns (string memory json); function rpcUrlStructs() external view returns (Rpc[] memory urls); function rpcUrls() external view returns (string[2][] memory urls); + function rpc(string calldata method, string calldata params) external returns (bytes memory data); + function rpc(string calldata urlOrAlias, string calldata method, string calldata params) external returns (bytes memory data); function selectFork(uint256 forkId) external; function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external returns (string memory json); function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external returns (string memory json); diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 4b40533347655..da382e90e399b 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -228,6 +228,12 @@ contract ForkTest is DSTest { bytes memory result = vm.rpc("eth_getBalance", file); assertEq(hex"10b7c11bcb51e6", result); } + + function testRpcWithUrl() public { + bytes memory result = vm.rpc("rpcAlias", "eth_blockNumber", "[]"); + uint256 decodedResult = vm.parseUint(vm.toString(result)); + assertGt(decodedResult, 20_000_000); + } } contract DummyContract {