From 708bd81e46f3a4ec3c7e9f4984f727da757cfd88 Mon Sep 17 00:00:00 2001 From: xermicus Date: Mon, 23 Sep 2024 11:35:43 +0200 Subject: [PATCH] add chain id to config and expose via syscall Signed-off-by: xermicus --- .../revive/fixtures/contracts/chain_id.rs | 37 ++++++++++ substrate/frame/revive/src/lib.rs | 25 ++++--- substrate/frame/revive/src/tests.rs | 19 ++++- substrate/frame/revive/src/wasm/runtime.rs | 70 ++++++++++++------- substrate/frame/revive/uapi/src/host.rs | 3 + .../frame/revive/uapi/src/host/riscv32.rs | 3 +- 6 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/chain_id.rs diff --git a/substrate/frame/revive/fixtures/contracts/chain_id.rs b/substrate/frame/revive/fixtures/contracts/chain_id.rs new file mode 100644 index 000000000000..ce7a0cc671ce --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_id.rs @@ -0,0 +1,37 @@ +// 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] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + call() +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + api::chain_id(&mut buf); + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bf9803690560..955c68586801 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -293,6 +293,13 @@ pub mod pallet { /// This value is usually higher than [`Self::RuntimeMemory`] to account for the fact /// that validators have to hold all storage items in PvF memory. type PVFMemory: Get; + + /// The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. + /// + /// This is a unique identifier assigned to each blockchain network, + /// preventing replay attacks. + #[pallet::constant] + type ChainId: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -303,7 +310,7 @@ pub mod pallet { traits::{ConstBool, ConstU32}, }; use frame_system::EnsureSigned; - use sp_core::parameter_types; + use sp_core::{parameter_types, ConstU64}; type AccountId = sp_runtime::AccountId32; type Balance = u64; @@ -365,6 +372,7 @@ pub mod pallet { type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + type ChainId = ConstU64<{ 0 }>; } } @@ -659,8 +667,8 @@ pub mod pallet { // We can use storage to store items using the available block ref_time with the // `set_storage` host function. - let max_storage_size: u32 = ((max_block_ref_time / - (>::weight(&RuntimeCosts::SetStorage { + let max_storage_size: u32 = ((max_block_ref_time + / (>::weight(&RuntimeCosts::SetStorage { new_bytes: max_payload_size, old_bytes: 0, }) @@ -682,8 +690,8 @@ pub mod pallet { // We can use storage to store events using the available block ref_time with the // `deposit_event` host function. The overhead of stored events, which is around 100B, // is not taken into account to simplify calculations, as it does not change much. - let max_events_size: u32 = ((max_block_ref_time / - (>::weight(&RuntimeCosts::DepositEvent { + let max_events_size: u32 = ((max_block_ref_time + / (>::weight(&RuntimeCosts::DepositEvent { num_topic: 0, len: max_payload_size, }) @@ -919,7 +927,7 @@ pub mod pallet { let contract = if let Some(contract) = contract { contract } else { - return Err(>::ContractNotFound.into()) + return Err(>::ContractNotFound.into()); }; >>::increment_refcount(code_hash)?; >>::decrement_refcount(contract.code_hash); @@ -1041,8 +1049,9 @@ where storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, - Code::Existing(code_hash) => - (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), + Code::Existing(code_hash) => { + (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()) + }, }; let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); let mut storage_meter = diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 167e8da201b8..2f4448109ea4 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -149,8 +149,8 @@ pub mod test_utils { let code_info_len = CodeInfo::::max_encoded_len() as u64; // Calculate deposit to be reserved. // We add 2 storage items: one for code, other for code_info - DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + - DepositPerItem::get().saturating_mul(2) + DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + + DepositPerItem::get().saturating_mul(2) } pub fn ensure_stored(code_hash: sp_core::H256) -> usize { // Assert that code_info is stored @@ -412,6 +412,7 @@ parameter_types! { pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + pub static ChainId: u64 = 384; } impl Convert> for Test { @@ -496,6 +497,7 @@ impl Config for Test { type InstantiateOrigin = EnsureAccount; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; + type ChainId = ChainId; } pub struct ExtBuilder { @@ -4310,4 +4312,17 @@ mod run_tests { assert_ok!(builder::call(addr).build()); }); } + + #[test] + fn chain_id_works() { + let (code, _) = compile_module("chain_id").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let chain_id = U256::from(::ChainId::get()); + let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); + assert_eq!(received.result.data, chain_id.encode()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 34cce533c14a..6d7754183641 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -468,22 +468,28 @@ impl Token for RuntimeCosts { Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), DebugMessage(len) => T::WeightInfo::seal_debug_message(len), - SetStorage { new_bytes, old_bytes } => - cost_storage!(write, seal_set_storage, new_bytes, old_bytes), + SetStorage { new_bytes, old_bytes } => { + cost_storage!(write, seal_set_storage, new_bytes, old_bytes) + }, ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), GetStorage(len) => cost_storage!(read, seal_get_storage, len), TakeStorage(len) => cost_storage!(write, seal_take_storage, len), - SetTransientStorage { new_bytes, old_bytes } => - cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes), - ClearTransientStorage(len) => - cost_storage!(write_transient, seal_clear_transient_storage, len), - ContainsTransientStorage(len) => - cost_storage!(read_transient, seal_contains_transient_storage, len), - GetTransientStorage(len) => - cost_storage!(read_transient, seal_get_transient_storage, len), - TakeTransientStorage(len) => - cost_storage!(write_transient, seal_take_transient_storage, len), + SetTransientStorage { new_bytes, old_bytes } => { + cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes) + }, + ClearTransientStorage(len) => { + cost_storage!(write_transient, seal_clear_transient_storage, len) + }, + ContainsTransientStorage(len) => { + cost_storage!(read_transient, seal_contains_transient_storage, len) + }, + GetTransientStorage(len) => { + cost_storage!(read_transient, seal_get_transient_storage, len) + }, + TakeTransientStorage(len) => { + cost_storage!(write_transient, seal_take_transient_storage, len) + }, Transfer => T::WeightInfo::seal_transfer(), CallBase => T::WeightInfo::seal_call(0, 0), DelegateCallBase => T::WeightInfo::seal_delegate_call(), @@ -563,15 +569,16 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { log::error!(target: LOG_TARGET, "polkavm execution error: {error}"); Some(Err(Error::::ExecutionFailed.into())) }, - Ok(Finished) => - Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Ok(Finished) => { + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })) + }, Ok(Trap) => Some(Err(Error::::ContractTrapped.into())), Ok(Segfault(_)) => Some(Err(Error::::ExecutionFailed.into())), Ok(NotEnoughGas) => Some(Err(Error::::OutOfGas.into())), Ok(Step) => None, Ok(Ecalli(idx)) => { let Some(syscall_symbol) = module.imports().get(idx) else { - return Some(Err(>::InvalidSyscall.into())) + return Some(Err(>::InvalidSyscall.into())); }; match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { Ok(None) => None, @@ -579,11 +586,12 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { instance.write_output(return_value); None }, - Err(TrapReason::Return(ReturnData { flags, data })) => + Err(TrapReason::Return(ReturnData { flags, data })) => { match ReturnFlags::from_bits(flags) { None => Some(Err(Error::::InvalidCallFlags.into())), Some(flags) => Some(Ok(ExecReturnValue { flags, data })), - }, + } + }, Err(TrapReason::Termination) => Some(Ok(Default::default())), Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())), } @@ -679,7 +687,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { create_token: impl FnOnce(u32) -> Option, ) -> Result<(), DispatchError> { if allow_skip && out_ptr == SENTINEL { - return Ok(()) + return Ok(()); } let len = memory.read_u32(out_len_ptr)?; @@ -703,7 +711,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { create_token: impl FnOnce(u32) -> Option, ) -> Result<(), DispatchError> { if allow_skip && out_ptr == SENTINEL { - return Ok(()) + return Ok(()); } let buf_len = buf.len() as u32; @@ -820,7 +828,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { let max_size = self.ext.max_value_size(); let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; if value_len > max_size { - return Err(Error::::ValueTooLarge.into()) + return Err(Error::::ValueTooLarge.into()); } let key = self.decode_key(memory, key_ptr, key_len)?; let value = Some(memory.read(value_ptr, value_len)?); @@ -1022,7 +1030,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { }, CallType::DelegateCall { code_hash_ptr } => { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { - return Err(Error::::InvalidCallFlags.into()) + return Err(Error::::InvalidCallFlags.into()); } let code_hash = memory.read_h256(code_hash_ptr)?; @@ -1037,7 +1045,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { return Err(TrapReason::Return(ReturnData { flags: return_value.flags.bits(), data: return_value.data, - })) + })); } } @@ -1536,6 +1544,19 @@ pub mod env { )?) } + /// Returns the chain ID. + /// See [`pallet_revive_uapi::HostFn::chain_id`]. + #[api_version(0)] + fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &as_bytes(U256::from(::ChainId::get())), + false, + already_charged, + )?) + } + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. #[api_version(0)] @@ -1719,8 +1740,9 @@ pub mod env { Environment::new(self, memory, id, input_ptr, input_len, output_ptr, output_len_ptr); let ret = match chain_extension.call(env)? { RetVal::Converging(val) => Ok(val), - RetVal::Diverging { flags, data } => - Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })), + RetVal::Diverging { flags, data } => { + Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })) + }, }; self.chain_extension = Some(chain_extension); ret diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 538de7ea251d..57a03332670f 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -71,6 +71,9 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the balance. fn balance_of(addr: &[u8; 20], output: &mut [u8; 32]); + /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. + fn chain_id(output: &mut [u8; 32]); + /// Stores the current block number of the current contract into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index 0bb0ede4543b..a60c338e8bd9 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -83,6 +83,7 @@ mod sys { pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn balance(out_ptr: *mut u8); pub fn balance_of(addr_ptr: *const u8, out_ptr: *mut u8); + pub fn chain_id(out_ptr: *mut u8); pub fn value_transferred(out_ptr: *mut u8); pub fn now(out_ptr: *mut u8); pub fn minimum_balance(out_ptr: *mut u8); @@ -447,7 +448,7 @@ impl HostFn for HostFnImpl { } impl_wrapper_for! { - [u8; 32] => block_number, balance, value_transferred, now, minimum_balance; + [u8; 32] => block_number, balance, value_transferred, now, minimum_balance, chain_id; [u8; 20] => address, caller; }