diff --git a/runtime/devnet/Cargo.toml b/runtime/devnet/Cargo.toml index 0780434d..b32fe4a3 100644 --- a/runtime/devnet/Cargo.toml +++ b/runtime/devnet/Cargo.toml @@ -95,7 +95,7 @@ hex = "0.4.3" rand = "0.8.5" [features] -default = ["std"] +default = ["std", "pop-runtime-extensions/pop-devnet"] std = [ "codec/std", "cumulus-pallet-aura-ext/std", @@ -142,7 +142,6 @@ std = [ "polkadot-runtime-common/std", "pop-primitives/std", "pop-runtime-extensions/std", - "pop-runtime-extensions/pop-devnet", "scale-info/std", "sp-api/std", "sp-io/std", diff --git a/runtime/extensions/Cargo.toml b/runtime/extensions/Cargo.toml index 29782a60..e9101ca6 100644 --- a/runtime/extensions/Cargo.toml +++ b/runtime/extensions/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true repository.workspace = true edition.workspace = true publish = false +resolver = "2" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/extensions/src/config/mod.rs b/runtime/extensions/src/config/mod.rs index 69dd1972..b42fd9eb 100644 --- a/runtime/extensions/src/config/mod.rs +++ b/runtime/extensions/src/config/mod.rs @@ -1,4 +1,2 @@ -#[cfg(feature = "pop-devnet")] pub(crate) mod devnet; -#[cfg(feature = "pop-testnet")] pub(crate) mod testnet; diff --git a/runtime/extensions/src/lib.rs b/runtime/extensions/src/lib.rs index 76b86852..4b7545c9 100644 --- a/runtime/extensions/src/lib.rs +++ b/runtime/extensions/src/lib.rs @@ -12,6 +12,10 @@ use sp_core::crypto::UncheckedFrom; use sp_runtime::{traits::Dispatchable, DispatchError}; use sp_std::vec::Vec; +#[cfg(all(feature = "pop-devnet", feature = "pop-testnet"))] +compile_error!( + "Feature \"pop-devnet\" and feature \"pop-testnet\" cannot be enabled at the same time." +); // Conditionally use different implementations of `PopApiExtensionConfig` based on the active feature. cfg_if::cfg_if! { if #[cfg(feature = "pop-devnet")] { diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index 217c5dc6..f318350f 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -93,7 +93,7 @@ env_logger = "0.11.2" hex = "0.4.3" [features] -default = ["std"] +default = ["std", "pop-runtime-extensions/pop-testnet"] std = [ "codec/std", "cumulus-pallet-aura-ext/std", @@ -139,7 +139,6 @@ std = [ "polkadot-runtime-common/std", "pop-primitives/std", "pop-runtime-extensions/std", - "pop-runtime-extensions/pop-testnet", "scale-info/std", "sp-api/std", "sp-io/std", diff --git a/runtime/testnet/src/config/contracts.rs b/runtime/testnet/src/config/contracts.rs index 36d62f7f..53ccf2cb 100644 --- a/runtime/testnet/src/config/contracts.rs +++ b/runtime/testnet/src/config/contracts.rs @@ -1,6 +1,6 @@ use crate::{ - deposit, extensions, Balance, Balances, BalancesCall, Perbill, Runtime, RuntimeCall, - RuntimeEvent, RuntimeHoldReason, Timestamp, + deposit, Balance, Balances, BalancesCall, Perbill, Runtime, RuntimeCall, RuntimeEvent, + RuntimeHoldReason, Timestamp, }; use frame_support::{ parameter_types, @@ -63,7 +63,7 @@ impl pallet_contracts::Config for Runtime { type CallStack = [pallet_contracts::Frame; 23]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = extensions::PopApiExtension; + type ChainExtension = pop_runtime_extensions::PopApiExtension; type Schedule = Schedule; type AddressGenerator = pallet_contracts::DefaultAddressGenerator; // This node is geared towards development and testing of contracts. diff --git a/runtime/testnet/src/config/extension.rs b/runtime/testnet/src/config/extension.rs new file mode 100644 index 00000000..a8360265 --- /dev/null +++ b/runtime/testnet/src/config/extension.rs @@ -0,0 +1,112 @@ +use crate::{Runtime, RuntimeCall}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::OriginTrait; +use frame_support::{ensure, traits::Contains}; +use frame_system::RawOrigin; +use pallet_contracts::chain_extension::{BufInBufOutState, Environment, Ext}; +use pop_runtime_extensions::{ + constants::{DECODING_FAILED_ERROR, LOG_TARGET, UNKNOWN_CALL_ERROR}, + dispatch_call, CallDispatchHandler, PopApiExtensionConfig, StateReadHandler, +}; +use sp_core::Get; +use sp_runtime::DispatchError; + +/// A query of runtime state. +#[derive(Encode, Decode, Debug, MaxEncodedLen)] +pub enum RuntimeRead {} + +/// A type to identify allowed calls to the Runtime from the API. +pub struct AllowedApiCalls; + +impl Contains for AllowedApiCalls { + /// Allowed runtime calls from the API. + fn contains(_: &RuntimeCall) -> bool { + false + } +} + +impl Contains for AllowedApiCalls { + /// Allowed state queries from the API. + fn contains(_: &RuntimeRead) -> bool { + false + } +} + +/// Wrapper to enable versioning of runtime state reads. +#[derive(Decode, Debug)] +enum VersionedStateRead { + /// Version zero of state reads. + #[codec(index = 0)] + V0(RuntimeRead), +} + +/// Wrapper to enable versioning of runtime calls. +#[derive(Decode, Debug)] +enum VersionedDispatch { + /// Version zero of dispatch calls. + #[codec(index = 0)] + V0(T::RuntimeCall), +} + +pub struct ContractExecutionContext; + +impl CallDispatchHandler for ContractExecutionContext { + fn handle_params( + env: &mut Environment, + params: Vec, + ) -> Result<(), DispatchError> + where + E: Ext, + T: PopApiExtensionConfig, + { + const LOG_PREFIX: &str = " dispatch |"; + + let call = + >::decode(&mut ¶ms[..]).map_err(|_| DECODING_FAILED_ERROR)?; + + // Contract is the origin by default. + let mut origin: T::RuntimeOrigin = RawOrigin::Signed(env.ext().address().clone()).into(); + match call { + VersionedDispatch::V0(call) => { + origin.add_filter(T::AllowedDispatchCalls::contains); + dispatch_call::(env, call, origin, LOG_PREFIX) + }, + } + } +} + +impl StateReadHandler for ContractExecutionContext { + fn handle_params( + env: &mut Environment, + params: Vec, + ) -> Result<(), DispatchError> + where + E: Ext, + T: PopApiExtensionConfig, + { + const LOG_PREFIX: &str = " read_state |"; + + let read = + ::decode(&mut ¶ms[..]).map_err(|_| DECODING_FAILED_ERROR)?; + + // Charge weight for doing one storage read. + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + let result = match read { + VersionedStateRead::V0(read) => { + ensure!(AllowedApiCalls::contains(&read), UNKNOWN_CALL_ERROR); + vec![0u8] + }, + }; + log::trace!( + target:LOG_TARGET, + "{} result: {:?}.", LOG_PREFIX, result + ); + env.write(&result, false, None) + } +} + +impl PopApiExtensionConfig for Runtime { + type StateReadHandler = ContractExecutionContext; + type CallDispatchHandler = ContractExecutionContext; + type AllowedDispatchCalls = AllowedApiCalls; +} diff --git a/runtime/testnet/src/config/mod.rs b/runtime/testnet/src/config/mod.rs index a3a64c92..4452fa53 100644 --- a/runtime/testnet/src/config/mod.rs +++ b/runtime/testnet/src/config/mod.rs @@ -1,5 +1,6 @@ mod assets; mod contracts; +mod extension; mod proxy; // Public due to integration tests crate. pub mod xcm; diff --git a/runtime/testnet/src/extensions.rs b/runtime/testnet/src/extensions.rs deleted file mode 100644 index a6e309f9..00000000 --- a/runtime/testnet/src/extensions.rs +++ /dev/null @@ -1,195 +0,0 @@ -use frame_support::traits::{Contains, OriginTrait}; -use frame_support::{ - dispatch::{GetDispatchInfo, RawOrigin}, - pallet_prelude::*, -}; -use pallet_contracts::chain_extension::{ - BufInBufOutState, ChainExtension, ChargedAmount, Environment, Ext, InitState, RetVal, -}; -use sp_core::crypto::UncheckedFrom; -use sp_runtime::{traits::Dispatchable, DispatchError}; -use sp_std::vec::Vec; - -use crate::{AccountId, AllowedApiCalls, RuntimeCall, RuntimeOrigin}; - -const LOG_TARGET: &str = "pop-api::extension"; - -type ContractSchedule = ::Schedule; - -#[derive(Default)] -pub struct PopApiExtension; - -impl ChainExtension for PopApiExtension -where - T: pallet_contracts::Config - + frame_system::Config< - RuntimeOrigin = RuntimeOrigin, - AccountId = AccountId, - RuntimeCall = RuntimeCall, - >, - T::AccountId: UncheckedFrom + AsRef<[u8]>, -{ - fn call(&mut self, env: Environment) -> Result - where - E: Ext, - { - log::debug!(target:LOG_TARGET, " extension called "); - match v0::FuncId::try_from(env.func_id())? { - v0::FuncId::Dispatch => { - match dispatch::(env) { - Ok(()) => Ok(RetVal::Converging(0)), - Err(DispatchError::Module(error)) => { - // encode status code = pallet index in runtime + error index, allowing for - // 999 errors - Ok(RetVal::Converging( - (error.index as u32 * 1_000) + u32::from_le_bytes(error.error), - )) - }, - Err(e) => Err(e), - } - }, - v0::FuncId::ReadState => { - read_state::(env)?; - Ok(RetVal::Converging(0)) - }, - } - } -} - -pub mod v0 { - #[derive(Debug)] - pub enum FuncId { - Dispatch, - ReadState, - } -} - -impl TryFrom for v0::FuncId { - type Error = DispatchError; - - fn try_from(func_id: u16) -> Result { - let id = match func_id { - 0x0 => Self::Dispatch, - 0x1 => Self::ReadState, - _ => { - log::error!("called an unregistered `func_id`: {:}", func_id); - return Err(DispatchError::Other("unimplemented func_id")); - }, - }; - - Ok(id) - } -} - -fn dispatch_call( - env: &mut Environment, - call: RuntimeCall, - mut origin: RuntimeOrigin, - log_prefix: &str, -) -> Result<(), DispatchError> -where - E: Ext, -{ - let charged_dispatch_weight = env.charge_weight(call.get_dispatch_info().weight)?; - - log::debug!(target:LOG_TARGET, "{} inputted RuntimeCall: {:?}", log_prefix, call); - - origin.add_filter(AllowedApiCalls::contains); - - match call.dispatch(origin) { - Ok(info) => { - log::debug!(target:LOG_TARGET, "{} success, actual weight: {:?}", log_prefix, info.actual_weight); - - // refund weight if the actual weight is less than the charged weight - if let Some(actual_weight) = info.actual_weight { - env.adjust_weight(charged_dispatch_weight, actual_weight); - } - - Ok(()) - }, - Err(err) => { - log::debug!(target:LOG_TARGET, "{} failed: error: {:?}", log_prefix, err.error); - Err(err.error) - }, - } -} - -fn charge_overhead_weight( - env: &mut Environment, - len: u32, - log_prefix: &str, -) -> Result -where - T: pallet_contracts::Config, - E: Ext, -{ - let contract_host_weight = ContractSchedule::::get().host_fn_weights; - - // calculate weight for reading bytes of `len` - // reference: https://github.com/paritytech/polkadot-sdk/blob/117a9433dac88d5ac00c058c9b39c511d47749d2/substrate/frame/contracts/src/wasm/runtime.rs#L267 - let base_weight: Weight = contract_host_weight.return_per_byte.saturating_mul(len.into()); - - // debug_message weight is a good approximation of the additional overhead of going - // from contract layer to substrate layer. - // reference: https://github.com/paritytech/ink-examples/blob/b8d2caa52cf4691e0ddd7c919e4462311deb5ad0/psp22-extension/runtime/psp22-extension-example.rs#L236 - let overhead = contract_host_weight.debug_message; - - let charged_weight = env.charge_weight(base_weight.saturating_add(overhead))?; - log::debug!(target: LOG_TARGET, "{} charged weight: {:?}", log_prefix, charged_weight); - - Ok(charged_weight) -} - -fn dispatch(env: Environment) -> Result<(), DispatchError> -where - T: pallet_contracts::Config, - RuntimeOrigin: From>, - E: Ext, -{ - const LOG_PREFIX: &str = " dispatch |"; - - let mut env = env.buf_in_buf_out(); - let len = env.in_len(); - - charge_overhead_weight::(&mut env, len, LOG_PREFIX)?; - - // read the input as RuntimeCall - let call: RuntimeCall = env.read_as_unbounded(len)?; - - // contract is the origin by default - let origin: RuntimeOrigin = RawOrigin::Signed(env.ext().address().clone()).into(); - - dispatch_call::(&mut env, call, origin, LOG_PREFIX) -} - -fn read_state(env: Environment) -> Result<(), DispatchError> -where - T: pallet_contracts::Config, - E: Ext, -{ - const LOG_PREFIX: &str = " read_state |"; - - let mut env = env.buf_in_buf_out(); - - // To be conservative, we charge the weight for reading the input bytes of a fixed-size type. - let base_weight: Weight = ContractSchedule::::get() - .host_fn_weights - .return_per_byte - .saturating_mul(env.in_len().into()); - let charged_weight = env.charge_weight(base_weight)?; - - log::debug!(target:LOG_TARGET, "{} charged weight: {:?}", LOG_PREFIX, charged_weight); - - // TODO: always returning an empty vec. Chainextension will be refactored into one for both - // runtimes before pop api implementation gets merged into main. - let result = Vec::::default().encode(); - - log::trace!( - target:LOG_TARGET, - "{} result: {:?}.", LOG_PREFIX, result - ); - env.write(&result, false, None).map_err(|e| { - log::trace!(target: LOG_TARGET, "{:?}", e); - DispatchError::Other("unable to write results to contract memory") - }) -} diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index 5573ef18..cf6a149f 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -7,7 +7,6 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod config; -mod extensions; mod weights; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;