From f0e858be926cfceaf7eeafdf74a1be535b88554e Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 27 Jan 2026 13:31:26 +0100 Subject: [PATCH 1/5] Fix mock calls in pallet-revive --- substrate/frame/revive/src/exec.rs | 14 +++- substrate/frame/revive/src/mock.rs | 13 +++ substrate/frame/revive/src/tests.rs | 10 +++ .../frame/revive/src/tests/sol/contract.rs | 84 ++++++++++++++++++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index f4ec66eb3bc17..e5d09543b4659 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -2217,7 +2217,12 @@ where } fn code_hash(&self, address: &H160) -> H256 { - if let Some(code) = >::code(address.as_fixed_bytes()) { + if let Some(code) = >::code(address.as_fixed_bytes()).or_else(|| { + self.exec_config + .mock_handler + .as_ref() + .and_then(|handler| handler.mocked_code(*address)) + }) { return sp_io::hashing::keccak_256(code).into() } @@ -2232,7 +2237,12 @@ where } fn code_size(&self, address: &H160) -> u64 { - if let Some(code) = >::code(address.as_fixed_bytes()) { + if let Some(code) = >::code(address.as_fixed_bytes()).or_else(|| { + self.exec_config + .mock_handler + .as_ref() + .and_then(|handler| handler.mocked_code(*address)) + }) { return code.len() as u64 } diff --git a/substrate/frame/revive/src/mock.rs b/substrate/frame/revive/src/mock.rs index c8adddcb4f2c4..41986b2953d12 100644 --- a/substrate/frame/revive/src/mock.rs +++ b/substrate/frame/revive/src/mock.rs @@ -63,4 +63,17 @@ pub trait MockHandler { fn mock_delegated_caller(&self, _dest: H160, _input_data: &[u8]) -> Option> { None } + + /// Returns dummy code for mocked addresses. + /// + /// This method serves two purposes: + /// 1. Indicates whether an address has mocked calls (Some = mocked, None = not mocked) + /// 2. Provides the dummy bytecode for `EXTCODESIZE` and `EXTCODEHASH` opcodes + /// + /// # Returns + /// - `Some(&MOCK_CODE)` if the address has mocked calls + /// - `None` if the address is not mocked + fn mocked_code(&self, _address: H160) -> Option<&'static [u8]> { + None + } } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 614a8a25e8978..cade75e392322 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -566,6 +566,16 @@ impl MockHandler for MockHandlerImpl { fn mock_delegated_caller(&self, _dest: H160, input_data: &[u8]) -> Option> { self.mock_delegate_caller.get(&input_data.to_vec()).cloned() } + + fn mocked_code(&self, address: H160) -> Option<&'static [u8]> { + // Return dummy code if address has mocked calls + if self.mock_call.contains_key(&address) { + // Same bytecode as precompiles: PUSH1 0 PUSH1 0 REVERT + Some(&[0x60, 0x00, 0x60, 0x00, 0xfd]) + } else { + None + } + } } #[test] diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 070aa1bc48c77..333c54c103cff 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -37,10 +37,10 @@ use frame_support::{ traits::fungible::{Balanced, Mutate}, }; use itertools::Itertools; -use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType}; +use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType, Host}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; -use sp_core::H160; +use sp_core::{H160, H256}; use test_case::test_case; /// Tests that the `CALL` opcode works as expected by having one contract call another. @@ -546,6 +546,86 @@ fn mock_delegatecall_hook_works(caller_type: FixtureType, callee_type: FixtureTy }); } +#[test] +fn mocked_code_works() { + let (host_code, _) = compile_module_with_type("Host", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr: host_addr, .. } = + builder::bare_instantiate(Code::Upload(host_code)).build_and_unwrap_contract(); + + let mocked_addr = H160::from_slice(&[0x42; 20]); + + const MOCK_CODE: [u8; 5] = [0x60, 0x00, 0x60, 0x00, 0xfd]; + let expected_size = MOCK_CODE.len() as u64; + let expected_hash = sp_io::hashing::keccak_256(&MOCK_CODE); + + // Test EXTCODESIZE with mocked address + let result = builder::bare_call(host_addr) + .data(Host::extcodesizeOpCall { account: mocked_addr.0.into() }.abi_encode()) + .exec_config(ExecConfig { + bump_nonce: false, + collect_deposit_from_hold: None, + effective_gas_price: None, + is_dry_run: None, + mock_handler: Some(Box::new(MockHandlerImpl { + mock_caller: None, + mock_call: iter::once(( + mocked_addr, + ExecReturnValue { flags: Default::default(), data: vec![] }, + )) + .collect(), + mock_delegate_caller: Default::default(), + })), + }) + .build_and_unwrap_result(); + + let size = Host::extcodesizeOpCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!( + size, expected_size, + "EXTCODESIZE should return {} for mocked address", + expected_size + ); + + // Test EXTCODEHASH with mocked address + let result = builder::bare_call(host_addr) + .data(Host::extcodehashOpCall { account: mocked_addr.0.into() }.abi_encode()) + .exec_config(ExecConfig { + bump_nonce: false, + collect_deposit_from_hold: None, + effective_gas_price: None, + is_dry_run: None, + mock_handler: Some(Box::new(MockHandlerImpl { + mock_caller: None, + mock_call: iter::once(( + mocked_addr, + ExecReturnValue { flags: Default::default(), data: vec![] }, + )) + .collect(), + mock_delegate_caller: Default::default(), + })), + }) + .build_and_unwrap_result(); + + let hash = Host::extcodehashOpCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!( + H256::from_slice(hash.as_slice()), + H256::from_slice(&expected_hash), + "EXTCODEHASH should return keccak256(MOCK_CODE) for mocked address" + ); + + // Verify that without mock handler, the same address returns 0 for code size + let result = builder::bare_call(host_addr) + .data(Host::extcodesizeOpCall { account: mocked_addr.0.into() }.abi_encode()) + .build_and_unwrap_result(); + + let size = Host::extcodesizeOpCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!(size, 0, "EXTCODESIZE should return 0 for unmocked address without code"); + }); +} + #[test] fn create_works() { let (caller_code, _) = compile_module_with_type("Caller", FixtureType::Solc).unwrap(); From b12f733147ec38310e4ceb14feafffbceb981362 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 27 Jan 2026 13:57:32 +0100 Subject: [PATCH 2/5] Cleanup --- substrate/frame/revive/src/tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index cade75e392322..f726fc9aa0dc0 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -568,9 +568,7 @@ impl MockHandler for MockHandlerImpl { } fn mocked_code(&self, address: H160) -> Option<&'static [u8]> { - // Return dummy code if address has mocked calls if self.mock_call.contains_key(&address) { - // Same bytecode as precompiles: PUSH1 0 PUSH1 0 REVERT Some(&[0x60, 0x00, 0x60, 0x00, 0xfd]) } else { None From 31c1b171d0aeaa095edf7c57f54e562e83f4c678 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 27 Jan 2026 15:06:01 +0100 Subject: [PATCH 3/5] Add prdoc --- prdoc/pr_10911.prdoc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 prdoc/pr_10911.prdoc diff --git a/prdoc/pr_10911.prdoc b/prdoc/pr_10911.prdoc new file mode 100644 index 0000000000000..928927dea496e --- /dev/null +++ b/prdoc/pr_10911.prdoc @@ -0,0 +1,10 @@ +title: '[pallet-revive] Fix EXTCODESIZE and EXTCODEHASH for mocked addresses' +doc: +- audience: Runtime Dev + description: Fixes EXTCODESIZE and EXTCODEHASH opcodes for mocked addresses. + Previously, these opcodes did not check the mock handler, causing them to + return values indicating no code exists at mocked addresses. Fixed by adding + `mocked_code` method to `MockHandler` trait to provide dummy bytecode. +crates: +- name: pallet-revive + bump: major From 3724cdf557e9776f33b4286284d13602c2482f86 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 28 Jan 2026 10:21:05 +0100 Subject: [PATCH 4/5] Cleanup --- substrate/frame/revive/src/tests.rs | 6 +++++- substrate/frame/revive/src/tests/sol/contract.rs | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index f726fc9aa0dc0..693459eb47690 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -537,6 +537,10 @@ impl Default for Origin { } } +/// Dummy EVM bytecode for mocked addresses. +/// This is minimal EVM bytecode (PUSH1 0, PUSH1 0, REVERT) that immediately reverts. +pub const MOCK_CODE: [u8; 5] = [0x60, 0x00, 0x60, 0x00, 0xfd]; + /// A mock handler implementation for testing purposes. pub struct MockHandlerImpl { // Always return this caller if set. @@ -569,7 +573,7 @@ impl MockHandler for MockHandlerImpl { fn mocked_code(&self, address: H160) -> Option<&'static [u8]> { if self.mock_call.contains_key(&address) { - Some(&[0x60, 0x00, 0x60, 0x00, 0xfd]) + Some(&MOCK_CODE) } else { None } diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 333c54c103cff..a21dd0f0ece51 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -24,7 +24,7 @@ use crate::{ evm::{decode_revert_reason, fees::InfoT}, metering::TransactionLimits, test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, BOB_ADDR, WEIGHT_LIMIT}, - tests::{builder, ExtBuilder, MockHandlerImpl, Test}, + tests::{builder, ExtBuilder, MockHandlerImpl, Test, MOCK_CODE}, BalanceOf, Code, Config, DelegateInfo, DispatchError, Error, ExecConfig, ExecOrigin, ExecReturnValue, Weight, }; @@ -558,7 +558,6 @@ fn mocked_code_works() { let mocked_addr = H160::from_slice(&[0x42; 20]); - const MOCK_CODE: [u8; 5] = [0x60, 0x00, 0x60, 0x00, 0xfd]; let expected_size = MOCK_CODE.len() as u64; let expected_hash = sp_io::hashing::keccak_256(&MOCK_CODE); From a0d179d84006b99236bd0f34895ca71b1645e513 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 28 Jan 2026 10:27:38 +0100 Subject: [PATCH 5/5] Update TRait description --- substrate/frame/revive/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/mock.rs b/substrate/frame/revive/src/mock.rs index 41986b2953d12..7a6d847d2940c 100644 --- a/substrate/frame/revive/src/mock.rs +++ b/substrate/frame/revive/src/mock.rs @@ -71,7 +71,7 @@ pub trait MockHandler { /// 2. Provides the dummy bytecode for `EXTCODESIZE` and `EXTCODEHASH` opcodes /// /// # Returns - /// - `Some(&MOCK_CODE)` if the address has mocked calls + /// - `Some(bytecode)` containing dummy bytecode if the address has mocked calls /// - `None` if the address is not mocked fn mocked_code(&self, _address: H160) -> Option<&'static [u8]> { None