diff --git a/prdoc/pr_6088.prdoc b/prdoc/pr_6088.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..93e435bbd458aa0caad21d5c3ac7a08412350dbe --- /dev/null +++ b/prdoc/pr_6088.prdoc @@ -0,0 +1,14 @@ +title: "[pallet-revive] EXTCODEHASH to match EIP-1052" + +doc: + - audience: Runtime Dev + description: | + Update `ext_code_hash` to match [EIP-1052](https://eips.ethereum.org/EIPS/eip-1052) specs. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/fixtures/contracts/code_hash.rs b/substrate/frame/revive/fixtures/contracts/code_hash.rs new file mode 100644 index 0000000000000000000000000000000000000000..b598a485a8c72a6805c844b2563001dccee964ed --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/code_hash.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + expected_code_hash: &[u8; 32], + ); + + let mut code_hash = [0u8; 32]; + api::code_hash(address, &mut code_hash); + + assert!(&code_hash == expected_code_hash); +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index fffc3e4f48376efed32b97a6546f0cf3bfab2c1c..07dbd096339bf526590edca30c855b8b477f8153 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -66,6 +66,10 @@ type VarSizedKey = BoundedVec<u8, ConstU32<{ limits::STORAGE_KEY_BYTES }>>; const FRAME_ALWAYS_EXISTS_ON_INSTANTIATE: &str = "The return value is only `None` if no contract exists at the specified address. This cannot happen on instantiate or delegate; qed"; +/// Code hash of existing account without code (keccak256 hash of empty data). +pub const EMPTY_CODE_HASH: H256 = + H256(sp_core::hex2array!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + /// Combined key type for both fixed and variable sized storage keys. pub enum Key { /// Variant for fixed sized keys. @@ -272,9 +276,8 @@ pub trait Ext: sealing::Sealed { fn is_contract(&self, address: &H160) -> bool; /// Returns the code hash of the contract for the given `address`. - /// - /// Returns `None` if the `address` does not belong to a contract. - fn code_hash(&self, address: &H160) -> Option<H256>; + /// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`. + fn code_hash(&self, address: &H160) -> H256; /// Returns the code hash of the contract being executed. fn own_code_hash(&mut self) -> &H256; @@ -1536,8 +1539,15 @@ where ContractInfoOf::<T>::contains_key(&address) } - fn code_hash(&self, address: &H160) -> Option<H256> { - <ContractInfoOf<T>>::get(&address).map(|contract| contract.code_hash) + fn code_hash(&self, address: &H160) -> H256 { + <ContractInfoOf<T>>::get(&address) + .map(|contract| contract.code_hash) + .unwrap_or_else(|| { + if System::<T>::account_exists(&T::AddressMapper::to_account_id(address)) { + return EMPTY_CODE_HASH; + } + H256::zero() + }) } fn own_code_hash(&mut self) -> &H256 { @@ -1817,9 +1827,10 @@ mod tests { }; use assert_matches::assert_matches; use frame_support::{assert_err, assert_ok, parameter_types}; - use frame_system::{EventRecord, Phase}; + use frame_system::{AccountInfo, EventRecord, Phase}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; + use sp_io::hashing::keccak_256; use sp_runtime::{traits::Hash, DispatchError}; use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc}; @@ -1870,8 +1881,8 @@ mod tests { f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, ) -> H256 { Loader::mutate(|loader| { - // Generate code hashes as monotonically increasing values. - let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter); + // Generate code hashes from contract index value. + let hash = H256(keccak_256(&loader.counter.to_le_bytes())); loader.counter += 1; loader.map.insert( hash, @@ -2386,16 +2397,25 @@ mod tests { #[test] fn code_hash_returns_proper_values() { - let code_bob = MockLoader::insert(Call, |ctx, _| { - // ALICE is not a contract and hence they do not have a code_hash - assert!(ctx.ext.code_hash(&ALICE_ADDR).is_none()); - // BOB is a contract and hence it has a code_hash - assert!(ctx.ext.code_hash(&BOB_ADDR).is_some()); + let bob_code_hash = MockLoader::insert(Call, |ctx, _| { + // ALICE is not a contract but account exists so it returns hash of empty data + assert_eq!(ctx.ext.code_hash(&ALICE_ADDR), EMPTY_CODE_HASH); + // BOB is a contract (this function) and hence it has a code_hash. + // `MockLoader` uses contract index to generate the code hash. + assert_eq!(ctx.ext.code_hash(&BOB_ADDR), H256(keccak_256(&0u64.to_le_bytes()))); + // [0xff;20] doesn't exist and returns hash zero + assert!(ctx.ext.code_hash(&H160([0xff; 20])).is_zero()); + exec_success() }); ExtBuilder::default().build().execute_with(|| { - place_contract(&BOB, code_bob); + // add alice account info to test case EOA code hash + frame_system::Account::<Test>::insert( + <Test as Config>::AddressMapper::to_account_id(&ALICE_ADDR), + AccountInfo { consumers: 1, providers: 1, ..Default::default() }, + ); + place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); // ALICE (not contract) -> BOB (contract) @@ -2415,7 +2435,7 @@ mod tests { #[test] fn own_code_hash_returns_proper_values() { let bob_ch = MockLoader::insert(Call, |ctx, _| { - let code_hash = ctx.ext.code_hash(&BOB_ADDR).unwrap(); + let code_hash = ctx.ext.code_hash(&BOB_ADDR); assert_eq!(*ctx.ext.own_code_hash(), code_hash); exec_success() }); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 4816e65f8f5c3951c91775fbd1726e764ea51ec1..e637c5f991c6187a36c82898edf0c392bc4e2585 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4442,4 +4442,46 @@ mod run_tests { ); }); } + + #[test] + fn code_hash_works() { + let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); + let (dummy_code, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + let Contract { addr: dummy_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); + + // code hash of dummy contract + assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build()); + // code has of itself + assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build()); + + // EOA doesn't exists + assert_err!( + builder::bare_call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build() + .result, + Error::<Test>::ContractTrapped + ); + // non-existing will return zero + assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build()); + + // create EOA + let _ = <Test as Config>::Currency::set_balance( + &<Test as Config>::AddressMapper::to_account_id(&BOB_ADDR), + 1_000_000, + ); + + // EOA returns empty code hash + assert_ok!(builder::call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 245c91278a7f80343f592a8f7dec152ae12094db..36cd03e9dd698d39b8081452278bd4d5c1ab86dd 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1406,27 +1406,17 @@ pub mod env { /// Retrieve the code hash for a specified contract address. /// See [`pallet_revive_uapi::HostFn::code_hash`]. #[api_version(0)] - fn code_hash( - &mut self, - memory: &mut M, - addr_ptr: u32, - out_ptr: u32, - ) -> Result<ReturnErrorCode, TrapReason> { + fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::CodeHash)?; let mut address = H160::zero(); memory.read_into_buf(addr_ptr, address.as_bytes_mut())?; - if let Some(value) = self.ext.code_hash(&address) { - self.write_fixed_sandbox_output( - memory, - out_ptr, - &value.as_bytes(), - false, - already_charged, - )?; - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::KeyNotFound) - } + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.code_hash(&address).as_bytes(), + false, + already_charged, + )?) } /// Retrieve the code hash of the currently executing contract. diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 2106b8fb49b7929975cbebba112038ced6c0bfbe..2663d7c2cf0cc2876ac623d98f9391973cfe2cf3 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -245,10 +245,12 @@ pub trait HostFn: private::Sealed { /// - `addr`: The address of the contract. /// - `output`: A reference to the output data buffer to write the code hash. /// - /// # Errors + /// # Note /// - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]) -> Result; + /// If `addr` is not a contract but the account exists then the hash of empty data + /// `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` is written, + /// otherwise `zero`. + fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]); /// Checks whether there is a value stored under the given key. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 866b0ee8dd1762ad8fafcf820ecce0410a3cb038..c2508198c935855d79fd111db5ac320f0b43b175 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -74,7 +74,7 @@ mod sys { pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); pub fn caller(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; - pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode; + pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); pub fn own_code_hash(out_ptr: *mut u8); pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; @@ -528,9 +528,8 @@ impl HostFn for HostFnImpl { ret_val.into() } - fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) -> Result { - let ret_val = unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) }; - ret_val.into() + fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } } fn own_code_hash(output: &mut [u8; 32]) {