Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions prdoc/pr_10911.prdoc
Original file line number Diff line number Diff line change
@@ -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
14 changes: 12 additions & 2 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,12 @@ where
}

fn code_hash(&self, address: &H160) -> H256 {
if let Some(code) = <AllPrecompiles<T>>::code(address.as_fixed_bytes()) {
if let Some(code) = <AllPrecompiles<T>>::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()
}

Expand All @@ -2232,7 +2237,12 @@ where
}

fn code_size(&self, address: &H160) -> u64 {
if let Some(code) = <AllPrecompiles<T>>::code(address.as_fixed_bytes()) {
if let Some(code) = <AllPrecompiles<T>>::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
}

Expand Down
13 changes: 13 additions & 0 deletions substrate/frame/revive/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,17 @@ pub trait MockHandler<T: pallet::Config> {
fn mock_delegated_caller(&self, _dest: H160, _input_data: &[u8]) -> Option<DelegateInfo<T>> {
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(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
}
}
12 changes: 12 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ impl Default for Origin<Test> {
}
}

/// 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<T: crate::pallet::Config> {
// Always return this caller if set.
Expand Down Expand Up @@ -566,6 +570,14 @@ impl<T: crate::pallet::Config> MockHandler<T> for MockHandlerImpl<T> {
fn mock_delegated_caller(&self, _dest: H160, input_data: &[u8]) -> Option<DelegateInfo<T>> {
self.mock_delegate_caller.get(&input_data.to_vec()).cloned()
}

fn mocked_code(&self, address: H160) -> Option<&'static [u8]> {
if self.mock_call.contains_key(&address) {
Some(&MOCK_CODE)
} else {
None
}
}
}

#[test]
Expand Down
85 changes: 82 additions & 3 deletions substrate/frame/revive/src/tests/sol/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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.
Expand Down Expand Up @@ -546,6 +546,85 @@ 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 _ = <Test as Config>::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]);

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();
Expand Down
Loading