diff --git a/Cargo.lock b/Cargo.lock index 47fad18c364f0..14fd4778c639a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4813,6 +4813,8 @@ version = "3.0.0" dependencies = [ "bitflags", "parity-scale-codec", + "serde", + "sp-core", "sp-runtime", "sp-std", ] diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 1d9f88c8c9142..885ecdd42f111 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -115,7 +115,7 @@ pub fn create_full( C: ProvideRuntimeApi + HeaderBackend + AuxStore + HeaderMetadata + Sync + Send + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi, - C::Api: pallet_contracts_rpc::ContractsRuntimeApi, + C::Api: pallet_contracts_rpc::ContractsRuntimeApi, C::Api: pallet_mmr_rpc::MmrRuntimeApi::Hash>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BabeApi, diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 956675175a746..aaf470ed33764 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1340,7 +1340,9 @@ impl_runtime_apis! { } } - impl pallet_contracts_rpc_runtime_api::ContractsApi + impl pallet_contracts_rpc_runtime_api::ContractsApi< + Block, AccountId, Balance, BlockNumber, Hash, + > for Runtime { fn call( @@ -1353,6 +1355,18 @@ impl_runtime_apis! { Contracts::bare_call(origin, dest, value, gas_limit, input_data) } + fn instantiate( + origin: AccountId, + endowment: Balance, + gas_limit: u64, + code: pallet_contracts_primitives::Code, + data: Vec, + salt: Vec, + ) -> pallet_contracts_primitives::ContractInstantiateResult + { + Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true) + } + fn get_storage( address: AccountId, key: [u8; 32], diff --git a/frame/contracts/CHANGELOG.md b/frame/contracts/CHANGELOG.md index efc3eb93c5701..9660d903bfe8d 100644 --- a/frame/contracts/CHANGELOG.md +++ b/frame/contracts/CHANGELOG.md @@ -20,6 +20,8 @@ In other words: Upgrading this pallet will not break pre-existing contracts. ### Added +- Add new `instantiate` RPC that allows clients to dry-run contract instantiation. + - Make storage and fields of `Schedule` private to the crate. [1](https://github.com/paritytech/substrate/pull/8359) diff --git a/frame/contracts/common/Cargo.toml b/frame/contracts/common/Cargo.toml index 050e18fc44d14..375f760b0a5cf 100644 --- a/frame/contracts/common/Cargo.toml +++ b/frame/contracts/common/Cargo.toml @@ -16,13 +16,17 @@ targets = ["x86_64-unknown-linux-gnu"] # This crate should not rely on any of the frame primitives. bitflags = "1.0" codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +sp-core = { version = "3.0.0", path = "../../../primitives/core", default-features = false } sp-std = { version = "3.0.0", default-features = false, path = "../../../primitives/std" } sp-runtime = { version = "3.0.0", default-features = false, path = "../../../primitives/runtime" } +serde = { version = "1", features = ["derive"], optional = true } [features] default = ["std"] std = [ "codec/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", + "serde", ] diff --git a/frame/contracts/common/src/lib.rs b/frame/contracts/common/src/lib.rs index 2b325d63d628d..17d4bec06b7cf 100644 --- a/frame/contracts/common/src/lib.rs +++ b/frame/contracts/common/src/lib.rs @@ -21,18 +21,45 @@ use bitflags::bitflags; use codec::{Decode, Encode}; +use sp_core::Bytes; use sp_runtime::{DispatchError, RuntimeDebug}; use sp_std::prelude::*; -/// Result type of a `bare_call` call. +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; + +/// Result type of a `bare_call` or `bare_instantiate` call. /// -/// The result of a contract execution along with a gas consumed. +/// It contains the execution result together with some auxiliary information. #[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] -pub struct ContractExecResult { - pub exec_result: ExecResult, +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct ContractResult { + /// How much gas was consumed during execution. pub gas_consumed: u64, + /// An optional debug message. This message is only non-empty when explicitly requested + /// by the code that calls into the contract. + /// + /// The contained bytes are valid UTF-8. This is not declared as `String` because + /// this type is not allowed within the runtime. A client should decode them in order + /// to present the message to its users. + /// + /// # Note + /// + /// The debug message is never generated during on-chain execution. It is reserved for + /// RPC calls. + pub debug_message: Bytes, + /// The execution result of the wasm code. + pub result: T, } +/// Result type of a `bare_call` call. +pub type ContractExecResult = ContractResult>; + +/// Result type of a `bare_instantiate` call. +pub type ContractInstantiateResult = + ContractResult, DispatchError>>; + /// Result type of a `get_storage` call. pub type GetStorageResult = Result>, ContractAccessError>; @@ -50,6 +77,8 @@ pub enum ContractAccessError { } #[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] pub enum RentProjection { /// Eviction is projected to happen at the specified block number. EvictionAt(BlockNumber), @@ -62,6 +91,8 @@ pub enum RentProjection { bitflags! { /// Flags used by a contract to customize exit behaviour. #[derive(Encode, Decode)] + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "std", serde(rename_all = "camelCase", transparent))] pub struct ReturnFlags: u32 { /// If this bit is set all changes made by the contract execution are rolled back. const REVERT = 0x0000_0001; @@ -70,11 +101,13 @@ bitflags! { /// Output of a contract call or instantiation which ran to completion. #[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] pub struct ExecReturnValue { /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. pub flags: ReturnFlags, /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. - pub data: Vec, + pub data: Bytes, } impl ExecReturnValue { @@ -84,40 +117,32 @@ impl ExecReturnValue { } } -/// Origin of the error. -/// -/// Call or instantiate both called into other contracts and pass through errors happening -/// in those to the caller. This enum is for the caller to distinguish whether the error -/// happened during the execution of the callee or in the current execution context. +/// The result of a successful contract instantiation. #[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub enum ErrorOrigin { - /// Caller error origin. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub struct InstantiateReturnValue { + /// The output of the called constructor. + pub result: ExecReturnValue, + /// The account id of the new contract. + pub account_id: AccountId, + /// Information about when and if the new project will be evicted. /// - /// The error happened in the current exeuction context rather than in the one - /// of the contract that is called into. - Caller, - /// The error happened during execution of the called contract. - Callee, -} - -/// Error returned by contract exection. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub struct ExecError { - /// The reason why the execution failed. - pub error: DispatchError, - /// Origin of the error. - pub origin: ErrorOrigin, + /// # Note + /// + /// `None` if `bare_instantiate` was called with + /// `compute_projection` set to false. From the perspective of an RPC this means that + /// the runtime API did not request this value and this feature is therefore unsupported. + pub rent_projection: Option>, } -impl> From for ExecError { - fn from(error: T) -> Self { - Self { - error: error.into(), - origin: ErrorOrigin::Caller, - } - } +/// Reference to an existing code hash or a new wasm module. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +pub enum Code { + /// A wasm module as raw bytes. + Upload(Bytes), + /// The code hash of an on-chain wasm blob. + Existing(Hash), } - -/// The result that is returned from contract execution. It either contains the output -/// buffer or an error describing the reason for failure. -pub type ExecResult = Result; diff --git a/frame/contracts/rpc/runtime-api/src/lib.rs b/frame/contracts/rpc/runtime-api/src/lib.rs index 6f0399586fa22..943931ec0c846 100644 --- a/frame/contracts/rpc/runtime-api/src/lib.rs +++ b/frame/contracts/rpc/runtime-api/src/lib.rs @@ -25,18 +25,21 @@ use codec::Codec; use sp_std::vec::Vec; -use pallet_contracts_primitives::{ContractExecResult, GetStorageResult, RentProjectionResult}; +use pallet_contracts_primitives::{ + ContractExecResult, GetStorageResult, RentProjectionResult, Code, ContractInstantiateResult, +}; sp_api::decl_runtime_apis! { /// The API to interact with contracts without using executive. - pub trait ContractsApi where + pub trait ContractsApi where AccountId: Codec, Balance: Codec, BlockNumber: Codec, + Hash: Codec, { /// Perform a call from a specified account to a given contract. /// - /// See the contracts' `call` dispatchable function for more details. + /// See [`pallet_contracts::Pallet::call`]. fn call( origin: AccountId, dest: AccountId, @@ -45,6 +48,18 @@ sp_api::decl_runtime_apis! { input_data: Vec, ) -> ContractExecResult; + /// Instantiate a new contract. + /// + /// See [`pallet_contracts::Pallet::instantiate`]. + fn instantiate( + origin: AccountId, + endowment: Balance, + gas_limit: u64, + code: Code, + data: Vec, + salt: Vec, + ) -> ContractInstantiateResult; + /// Query a given storage key in a given contract. /// /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs index e0a056906f743..dd9ec164a984b 100644 --- a/frame/contracts/rpc/src/lib.rs +++ b/frame/contracts/rpc/src/lib.rs @@ -27,14 +27,13 @@ use serde::{Deserialize, Serialize}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::{Bytes, H256}; -use sp_rpc::number; +use sp_rpc::number::NumberOrHex; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT}, - DispatchError, }; use std::convert::{TryFrom, TryInto}; -use pallet_contracts_primitives::ContractExecResult; +use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult}; pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi; @@ -42,6 +41,8 @@ const RUNTIME_ERROR: i64 = 1; const CONTRACT_DOESNT_EXIST: i64 = 2; const CONTRACT_IS_A_TOMBSTONE: i64 = 3; +pub type Weight = u64; + /// A rough estimate of how much gas a decent hardware consumes per second, /// using native execution. /// This value is used to set the upper bound for maximal contract calls to @@ -50,7 +51,11 @@ const CONTRACT_IS_A_TOMBSTONE: i64 = 3; /// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which /// determined runtime weights: /// https://github.com/paritytech/substrate/pull/5446 -const GAS_PER_SECOND: u64 = 1_000_000_000_000; +const GAS_PER_SECOND: Weight = 1_000_000_000_000; + +/// The maximum amount of weight that the call and instantiate rpcs are allowed to consume. +/// This puts a ceiling on the weight limit that is supplied to the rpc as an argument. +const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND; /// A private newtype for converting `ContractAccessError` into an RPC error. struct ContractAccessError(pallet_contracts_primitives::ContractAccessError); @@ -79,59 +84,27 @@ impl From for Error { pub struct CallRequest { origin: AccountId, dest: AccountId, - value: number::NumberOrHex, - gas_limit: number::NumberOrHex, + value: NumberOrHex, + gas_limit: NumberOrHex, input_data: Bytes, } +/// A struct that encodes RPC parameters required to instantiate a new smart-contract. #[derive(Serialize, Deserialize)] -#[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] -struct RpcContractExecSuccess { - /// The return flags. See `pallet_contracts_primitives::ReturnFlags`. - flags: u32, - /// Data as returned by the contract. - data: Bytes, -} - -/// An RPC serializable result of contract execution -#[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] -#[serde(rename_all = "camelCase")] -pub struct RpcContractExecResult { - /// How much gas was consumed by the call. In case of an error this is the amount - /// that was used up until the error occurred. - gas_consumed: u64, - /// Additional dynamic human readable error information for debugging. An empty string - /// indicates that no additional information is available. - debug_message: String, - /// Indicates whether the contract execution was successful or not. - result: std::result::Result, -} - -impl From for RpcContractExecResult { - fn from(r: ContractExecResult) -> Self { - match r.exec_result { - Ok(val) => RpcContractExecResult { - gas_consumed: r.gas_consumed, - debug_message: String::new(), - result: Ok(RpcContractExecSuccess { - flags: val.flags.bits(), - data: val.data.into(), - }), - }, - Err(err) => RpcContractExecResult { - gas_consumed: r.gas_consumed, - debug_message: String::new(), - result: Err(err.error), - }, - } - } +pub struct InstantiateRequest { + origin: AccountId, + endowment: NumberOrHex, + gas_limit: NumberOrHex, + code: Code, + data: Bytes, + salt: Bytes, } /// Contracts RPC methods. #[rpc] -pub trait ContractsApi { +pub trait ContractsApi { /// Executes a call to a contract. /// /// This call is performed locally without submitting any transactions. Thus executing this @@ -143,7 +116,20 @@ pub trait ContractsApi { &self, call_request: CallRequest, at: Option, - ) -> Result; + ) -> Result; + + /// Instantiate a new contract. + /// + /// This call is performed locally without submitting any transactions. Thus the contract + /// is not actually created. + /// + /// This method is useful for UIs to dry-run contract instantiations. + #[rpc(name = "contracts_instantiate")] + fn instantiate( + &self, + instantiate_request: InstantiateRequest, + at: Option, + ) -> Result>; /// Returns the value under a specified storage `key` in a contract given by `address` param, /// or `None` if it is not set. @@ -184,12 +170,13 @@ impl Contracts { } } } -impl +impl ContractsApi< ::Hash, <::Header as HeaderT>::Number, AccountId, Balance, + Hash, > for Contracts where Block: BlockT, @@ -199,15 +186,17 @@ where AccountId, Balance, <::Header as HeaderT>::Number, + Hash, >, AccountId: Codec, - Balance: Codec + TryFrom, + Balance: Codec + TryFrom, + Hash: Codec, { fn call( &self, call_request: CallRequest, at: Option<::Hash>, - ) -> Result { + ) -> Result { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -221,37 +210,45 @@ where input_data, } = call_request; - // Make sure that value fits into the balance type. - let value: Balance = value.try_into().map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("{:?} doesn't fit into the balance type", value), - data: None, - })?; - - // Make sure that gas_limit fits into 64 bits. - let gas_limit: u64 = gas_limit.try_into().map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("{:?} doesn't fit in 64 bit unsigned value", gas_limit), - data: None, - })?; - - let max_gas_limit = 5 * GAS_PER_SECOND; - if gas_limit > max_gas_limit { - return Err(Error { - code: ErrorCode::InvalidParams, - message: format!( - "Requested gas limit is greater than maximum allowed: {} > {}", - gas_limit, max_gas_limit - ), - data: None, - }); - } + let value: Balance = decode_hex(value, "balance")?; + let gas_limit: Weight = decode_hex(gas_limit, "weight")?; + limit_gas(gas_limit)?; let exec_result = api .call(&at, origin, dest, value, gas_limit, input_data.to_vec()) .map_err(runtime_error_into_rpc_err)?; - Ok(exec_result.into()) + Ok(exec_result) + } + + fn instantiate( + &self, + instantiate_request: InstantiateRequest, + at: Option<::Hash>, + ) -> Result::Header as HeaderT>::Number>> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); + + let InstantiateRequest { + origin, + endowment, + gas_limit, + code, + data, + salt, + } = instantiate_request; + + let endowment: Balance = decode_hex(endowment, "balance")?; + let gas_limit: Weight = decode_hex(gas_limit, "weight")?; + limit_gas(gas_limit)?; + + let exec_result = api + .instantiate(&at, origin, endowment, gas_limit, code, data.to_vec(), salt.to_vec()) + .map_err(runtime_error_into_rpc_err)?; + + Ok(exec_result) } fn get_storage( @@ -300,16 +297,43 @@ where fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error { Error { code: ErrorCode::ServerError(RUNTIME_ERROR), - message: "Runtime trapped".into(), + message: "Runtime error".into(), data: Some(format!("{:?}", err).into()), } } +fn decode_hex>(from: H, name: &str) -> Result { + from.try_into().map_err(|_| Error { + code: ErrorCode::InvalidParams, + message: format!("{:?} does not fit into the {} type", from, name), + data: None, + }) +} + +fn limit_gas(gas_limit: Weight) -> Result<()> { + if gas_limit > GAS_LIMIT { + Err(Error { + code: ErrorCode::InvalidParams, + message: format!( + "Requested gas limit is greater than maximum allowed: {} > {}", + gas_limit, GAS_LIMIT + ), + data: None, + }) + } else { + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; use sp_core::U256; + fn trim(json: &str) -> String { + json.chars().filter(|c| !c.is_whitespace()).collect() + } + #[test] fn call_request_should_serialize_deserialize_properly() { type Req = CallRequest; @@ -327,13 +351,84 @@ mod tests { } #[test] - fn result_should_serialize_deserialize_properly() { + fn instantiate_request_should_serialize_deserialize_properly() { + type Req = InstantiateRequest; + let req: Req = serde_json::from_str(r#" + { + "origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + "endowment": "0x88", + "gasLimit": 42, + "code": { "existing": "0x1122" }, + "data": "0x4299", + "salt": "0x9988" + } + "#).unwrap(); + + assert_eq!(req.origin, "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"); + assert_eq!(req.endowment.into_u256(), 0x88.into()); + assert_eq!(req.gas_limit.into_u256(), 42.into()); + assert_eq!(&*req.data, [0x42, 0x99].as_ref()); + assert_eq!(&*req.salt, [0x99, 0x88].as_ref()); + let code = match req.code { + Code::Existing(hash) => hash, + _ => panic!("json encoded an existing hash"), + }; + assert_eq!(&code, "0x1122"); + } + + #[test] + fn call_result_should_serialize_deserialize_properly() { + fn test(expected: &str) { + let res: ContractExecResult = serde_json::from_str(expected).unwrap(); + let actual = serde_json::to_string(&res).unwrap(); + assert_eq!(actual, trim(expected).as_str()); + } + test(r#"{ + "gasConsumed": 5000, + "debugMessage": "0x68656c704f6b", + "result": { + "Ok": { + "flags": 5, + "data": "0x1234" + } + } + }"#); + test(r#"{ + "gasConsumed": 3400, + "debugMessage": "0x68656c70457272", + "result": { + "Err": "BadOrigin" + } + }"#); + } + + #[test] + fn instantiate_result_should_serialize_deserialize_properly() { fn test(expected: &str) { - let res: RpcContractExecResult = serde_json::from_str(expected).unwrap(); + let res: ContractInstantiateResult = serde_json::from_str(expected).unwrap(); let actual = serde_json::to_string(&res).unwrap(); - assert_eq!(actual, expected); + assert_eq!(actual, trim(expected).as_str()); } - test(r#"{"gasConsumed":5000,"debugMessage":"helpOk","result":{"Ok":{"flags":5,"data":"0x1234"}}}"#); - test(r#"{"gasConsumed":3400,"debugMessage":"helpErr","result":{"Err":"BadOrigin"}}"#); + test(r#"{ + "gasConsumed": 5000, + "debugMessage": "0x68656c704f6b", + "result": { + "Ok": { + "result": { + "flags": 5, + "data": "0x1234" + }, + "accountId": "5CiPP", + "rentProjection": null + } + } + }"#); + test(r#"{ + "gasConsumed": 3400, + "debugMessage": "0x68656c70457272", + "result": { + "Err": "BadOrigin" + } + }"#); } } diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 4649c12018b74..be471ed0c72ea 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -32,17 +32,52 @@ use frame_support::{ weights::Weight, ensure, }; -use pallet_contracts_primitives::{ErrorOrigin, ExecError, ExecReturnValue, ExecResult, ReturnFlags}; +use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type BlockNumberOf = ::BlockNumber; pub type StorageKey = [u8; 32]; +pub type ExecResult = Result; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; +/// Origin of the error. +/// +/// Call or instantiate both called into other contracts and pass through errors happening +/// in those to the caller. This enum is for the caller to distinguish whether the error +/// happened during the execution of the callee or in the current execution context. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum ErrorOrigin { + /// Caller error origin. + /// + /// The error happened in the current exeuction context rather than in the one + /// of the contract that is called into. + Caller, + /// The error happened during execution of the called contract. + Callee, +} + +/// Error returned by contract exection. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct ExecError { + /// The reason why the execution failed. + pub error: DispatchError, + /// Origin of the error. + pub origin: ErrorOrigin, +} + +impl> From for ExecError { + fn from(error: T) -> Self { + Self { + error: error.into(), + origin: ErrorOrigin::Caller, + } + } +} + /// Information needed for rent calculations that can be requested by a contract. #[derive(codec::Encode)] #[cfg_attr(test, derive(Debug, PartialEq))] @@ -945,6 +980,7 @@ mod tests { exec::ExportedFunction::*, Error, Weight, CurrentSchedule, }; + use sp_core::Bytes; use frame_support::assert_noop; use sp_runtime::DispatchError; use assert_matches::assert_matches; @@ -1123,7 +1159,7 @@ mod tests { } fn exec_success() -> ExecResult { - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) } #[test] @@ -1186,7 +1222,7 @@ mod tests { let return_ch = MockLoader::insert( Call, - |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) + |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(Vec::new()) }) ); ExtBuilder::default().build().execute_with(|| { @@ -1246,7 +1282,7 @@ mod tests { let dest = BOB; let return_ch = MockLoader::insert( Call, - |_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) + |_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![1, 2, 3, 4]) }) ); ExtBuilder::default().build().execute_with(|| { @@ -1263,7 +1299,7 @@ mod tests { let output = result.unwrap(); assert!(output.0.is_success()); - assert_eq!(output.0.data, vec![1, 2, 3, 4]); + assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4])); }); } @@ -1275,7 +1311,7 @@ mod tests { let dest = BOB; let return_ch = MockLoader::insert( Call, - |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) + |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(vec![1, 2, 3, 4]) }) ); ExtBuilder::default().build().execute_with(|| { @@ -1292,7 +1328,7 @@ mod tests { let output = result.unwrap(); assert!(!output.0.is_success()); - assert_eq!(output.0.data, vec![1, 2, 3, 4]); + assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4])); }); } @@ -1512,7 +1548,7 @@ mod tests { fn instantiation_work_with_success_output() { let dummy_ch = MockLoader::insert( Constructor, - |_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) + |_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(vec![80, 65, 83, 83]) }) ); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { @@ -1532,7 +1568,7 @@ mod tests { vec![], &[], ), - Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address + Ok((address, ref output)) if output.data == Bytes(vec![80, 65, 83, 83]) => address ); // Check that the newly created account has the expected code hash and @@ -1548,7 +1584,7 @@ mod tests { fn instantiation_fails_with_failing_output() { let dummy_ch = MockLoader::insert( Constructor, - |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) + |_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Bytes(vec![70, 65, 73, 76]) }) ); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { @@ -1568,7 +1604,7 @@ mod tests { vec![], &[], ), - Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address + Ok((address, ref output)) if output.data == Bytes(vec![70, 65, 73, 76]) => address ); // Check that the account has not been created. diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index 80e608b217bd3..21c1afbcd6962 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Config, Error}; +use crate::{Config, Error, exec::ExecError}; use sp_std::marker::PhantomData; use sp_runtime::traits::Zero; use frame_support::{ @@ -24,7 +24,6 @@ use frame_support::{ }, weights::Weight, }; -use pallet_contracts_primitives::ExecError; use sp_core::crypto::UncheckedFrom; #[cfg(test)] diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 46947ea9e1aef..2aa6b8f2ec7b9 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -112,7 +112,7 @@ use crate::{ weights::WeightInfo, wasm::PrefabWasmModule, }; -use sp_core::crypto::UncheckedFrom; +use sp_core::{Bytes, crypto::UncheckedFrom}; use sp_std::prelude::*; use sp_runtime::{ traits::{ @@ -127,6 +127,7 @@ use frame_support::{ use frame_system::Pallet as System; use pallet_contracts_primitives::{ RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult, + ContractInstantiateResult, Code, InstantiateReturnValue, }; type CodeHash = ::Hash; @@ -666,8 +667,8 @@ where { /// Perform a call to a specified contract. /// - /// This function is similar to `Self::call`, but doesn't perform any address lookups and better - /// suitable for calling directly from Rust. + /// This function is similar to [`Self::call`], but doesn't perform any address lookups + /// and better suitable for calling directly from Rust. /// /// It returns the execution result and the amount of used weight. pub fn bare_call( @@ -683,8 +684,65 @@ where let result = ctx.call(dest, value, &mut gas_meter, input_data); let gas_consumed = gas_meter.gas_spent(); ContractExecResult { - exec_result: result.map(|r| r.0).map_err(|r| r.0), + result: result.map(|r| r.0).map_err(|r| r.0.error), gas_consumed, + debug_message: Bytes(Vec::new()), + } + } + + /// Instantiate a new contract. + /// + /// This function is similar to [`Self::instantiate`], but doesn't perform any address lookups + /// and better suitable for calling directly from Rust. + /// + /// It returns the execution result, account id and the amount of used weight. + /// + /// If `compute_projection` is set to `true` the result also contains the rent projection. + /// This is optional because some non trivial and stateful work is performed to compute + /// the projection. See [`Self::rent_projection`]. + pub fn bare_instantiate( + origin: T::AccountId, + endowment: BalanceOf, + gas_limit: Weight, + code: Code>, + data: Vec, + salt: Vec, + compute_projection: bool, + ) -> ContractInstantiateResult { + let mut gas_meter = GasMeter::new(gas_limit); + let schedule = >::get(); + let mut ctx = ExecutionContext::>::top_level(origin, &schedule); + let executable = match code { + Code::Upload(Bytes(binary)) => PrefabWasmModule::from_code(binary, &schedule), + Code::Existing(hash) => PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter), + }; + let executable = match executable { + Ok(executable) => executable, + Err(error) => return ContractInstantiateResult { + result: Err(error.into()), + gas_consumed: gas_meter.gas_spent(), + debug_message: Bytes(Vec::new()), + } + }; + let result = ctx.instantiate(endowment, &mut gas_meter, executable, data, &salt) + .and_then(|(account_id, result)| { + let rent_projection = if compute_projection { + Some(Rent::>::compute_projection(&account_id) + .map_err(|_| >::NewContractNotFunded)?) + } else { + None + }; + + Ok(InstantiateReturnValue { + result, + account_id, + rent_projection, + }) + }); + ContractInstantiateResult { + result: result.map_err(|e| e.error), + gas_consumed: gas_meter.gas_spent(), + debug_message: Bytes(Vec::new()), } } diff --git a/frame/contracts/src/rent.rs b/frame/contracts/src/rent.rs index 8605451ad1ee7..321fe151c300f 100644 --- a/frame/contracts/src/rent.rs +++ b/frame/contracts/src/rent.rs @@ -388,7 +388,7 @@ where None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone), Some(ContractInfo::Alive(contract)) => contract, }; - let module = PrefabWasmModule::from_storage_noinstr(alive_contract_info.code_hash) + let module = >::from_storage_noinstr(alive_contract_info.code_hash) .map_err(|_| IsTombstone)?; let code_size = module.occupied_storage(); let current_block_number = >::block_number(); @@ -399,8 +399,11 @@ where &alive_contract_info, code_size, ); + + // We skip the eviction in case one is in order. + // Evictions should only be performed by [`try_eviction`]. let new_contract_info = Self::enact_verdict( - account, alive_contract_info, current_block_number, verdict, Some(module), + account, alive_contract_info, current_block_number, verdict, None, ); // Check what happened after enaction of the verdict. diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 802118bfb069f..b72ef652ce260 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -30,6 +30,7 @@ use crate::{ }; use assert_matches::assert_matches; use codec::Encode; +use sp_core::Bytes; use sp_runtime::{ traits::{BlakeTwo256, Hash, IdentityLookup, Convert}, testing::{Header, H256}, @@ -1886,7 +1887,7 @@ fn crypto_hashes() { 0, GAS_LIMIT, params, - ).exec_result.unwrap(); + ).result.unwrap(); assert!(result.is_success()); let expected = hash_fn(input.as_ref()); assert_eq!(&result.data[..*expected_size], &*expected); @@ -1921,7 +1922,7 @@ fn transfer_return_code() { 0, GAS_LIMIT, vec![], - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); // Contract has enough total balance in order to not go below the subsistence @@ -1935,7 +1936,7 @@ fn transfer_return_code() { 0, GAS_LIMIT, vec![], - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); }); } @@ -1969,7 +1970,7 @@ fn call_return_code() { 0, GAS_LIMIT, AsRef::<[u8]>::as_ref(&DJANGO).to_vec(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::NotCallable); assert_ok!( @@ -1992,7 +1993,7 @@ fn call_return_code() { 0, GAS_LIMIT, AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); // Contract has enough total balance in order to not go below the subsistence @@ -2006,7 +2007,7 @@ fn call_return_code() { 0, GAS_LIMIT, AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but callee reverts because "1" is passed. @@ -2017,7 +2018,7 @@ fn call_return_code() { 0, GAS_LIMIT, AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&1u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::CalleeReverted); // Contract has enough balance but callee traps because "2" is passed. @@ -2027,7 +2028,7 @@ fn call_return_code() { 0, GAS_LIMIT, AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&2u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); }); @@ -2074,7 +2075,7 @@ fn instantiate_return_code() { 0, GAS_LIMIT, callee_hash.clone(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold); // Contract has enough total balance in order to not go below the subsistence @@ -2088,7 +2089,7 @@ fn instantiate_return_code() { 0, GAS_LIMIT, callee_hash.clone(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but the passed code hash is invalid @@ -2099,7 +2100,7 @@ fn instantiate_return_code() { 0, GAS_LIMIT, vec![0; 33], - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. @@ -2109,7 +2110,7 @@ fn instantiate_return_code() { 0, GAS_LIMIT, callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::CalleeReverted); // Contract has enough balance but callee traps because "2" is passed. @@ -2119,7 +2120,7 @@ fn instantiate_return_code() { 0, GAS_LIMIT, callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(), - ).exec_result.unwrap(); + ).result.unwrap(); assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); }); @@ -2209,7 +2210,7 @@ fn chain_extension_works() { ); let gas_consumed = result.gas_consumed; assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]); - assert_eq!(result.exec_result.unwrap().data, vec![0, 99]); + assert_eq!(result.result.unwrap().data, Bytes(vec![0, 99])); // 1 = treat inputs as integer primitives and store the supplied integers Contracts::bare_call( @@ -2218,7 +2219,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, vec![1], - ).exec_result.unwrap(); + ).result.unwrap(); // those values passed in the fixture assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12)); @@ -2230,7 +2231,7 @@ fn chain_extension_works() { GAS_LIMIT, vec![2, 42], ); - assert_ok!(result.exec_result); + assert_ok!(result.result); assert_eq!(result.gas_consumed, gas_consumed + 42); // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer @@ -2240,9 +2241,9 @@ fn chain_extension_works() { 0, GAS_LIMIT, vec![3], - ).exec_result.unwrap(); + ).result.unwrap(); assert_eq!(result.flags, ReturnFlags::REVERT); - assert_eq!(result.data, vec![42, 99]); + assert_eq!(result.data, Bytes(vec![42, 99])); }); } @@ -2767,7 +2768,7 @@ fn reinstrument_does_charge() { GAS_LIMIT, zero.clone(), ); - assert!(result0.exec_result.unwrap().is_success()); + assert!(result0.result.unwrap().is_success()); let result1 = Contracts::bare_call( ALICE, @@ -2776,7 +2777,7 @@ fn reinstrument_does_charge() { GAS_LIMIT, zero.clone(), ); - assert!(result1.exec_result.unwrap().is_success()); + assert!(result1.result.unwrap().is_success()); // They should match because both where called with the same schedule. assert_eq!(result0.gas_consumed, result1.gas_consumed); @@ -2794,7 +2795,7 @@ fn reinstrument_does_charge() { GAS_LIMIT, zero.clone(), ); - assert!(result2.exec_result.unwrap().is_success()); + assert!(result2.result.unwrap().is_success()); assert!(result2.gas_consumed > result1.gas_consumed); assert_eq!( result2.gas_consumed, diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 3f92320b94b77..969336b59fa39 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -27,14 +27,13 @@ mod runtime; use crate::{ CodeHash, Schedule, Config, wasm::env_def::FunctionImplProvider, - exec::{Ext, Executable, ExportedFunction}, + exec::{Ext, Executable, ExportedFunction, ExecResult}, gas::GasMeter, }; use sp_std::prelude::*; use sp_core::crypto::UncheckedFrom; use codec::{Encode, Decode}; use frame_support::dispatch::DispatchError; -use pallet_contracts_primitives::ExecResult; pub use self::runtime::{ReturnCode, Runtime, RuntimeToken}; #[cfg(feature = "runtime-benchmarks")] pub use self::code_cache::reinstrument; @@ -246,17 +245,20 @@ mod tests { use super::*; use crate::{ CodeHash, BalanceOf, Error, Pallet as Contracts, - exec::{Ext, StorageKey, AccountIdOf, Executable, SeedOf, BlockNumberOf, RentParams}, + exec::{ + Ext, StorageKey, AccountIdOf, Executable, SeedOf, BlockNumberOf, + RentParams, ExecError, ErrorOrigin, + }, gas::GasMeter, tests::{Test, Call, ALICE, BOB}, }; use std::collections::HashMap; - use sp_core::H256; + use sp_core::{Bytes, H256}; use hex_literal::hex; use sp_runtime::DispatchError; use frame_support::{assert_ok, dispatch::DispatchResult, weights::Weight}; use assert_matches::assert_matches; - use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags, ExecError, ErrorOrigin}; + use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pretty_assertions::assert_eq; const GAS_LIMIT: Weight = 10_000_000_000; @@ -336,7 +338,7 @@ mod tests { Contracts::::contract_address(&ALICE, &code_hash, salt), ExecReturnValue { flags: ReturnFlags::empty(), - data: Vec::new(), + data: Bytes(Vec::new()), }, 0, )) @@ -367,7 +369,7 @@ mod tests { }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. - Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }, 0)) + Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }, 0)) } fn terminate( &mut self, @@ -946,7 +948,10 @@ mod tests { &mut GasMeter::new(GAS_LIMIT), ).unwrap(); - assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: [0x22; 32].to_vec() }); + assert_eq!(output, ExecReturnValue { + flags: ReturnFlags::empty(), + data: Bytes([0x22; 32].to_vec()) + }); } /// calls `seal_caller` and compares the result with the constant 42. @@ -1209,7 +1214,7 @@ mod tests { &mut gas_meter, ).unwrap(); - let gas_left = Weight::decode(&mut output.data.as_slice()).unwrap(); + let gas_left = Weight::decode(&mut &*output.data).unwrap(); assert!(gas_left < GAS_LIMIT, "gas_left must be less than initial"); assert!(gas_left > gas_meter.gas_left(), "gas_left must be greater than final"); } @@ -1299,7 +1304,13 @@ mod tests { &mut GasMeter::new(GAS_LIMIT), ).unwrap(); - assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }); + assert_eq!( + output, + ExecReturnValue { + flags: ReturnFlags::empty(), + data: Bytes(vec![1, 2, 3, 4]) + } + ); } const CODE_TIMESTAMP_NOW: &str = r#" @@ -1526,7 +1537,10 @@ mod tests { output, ExecReturnValue { flags: ReturnFlags::empty(), - data: hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F").to_vec(), + data: Bytes( + hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F") + .to_vec() + ), }, ); } @@ -1601,10 +1615,10 @@ mod tests { output, ExecReturnValue { flags: ReturnFlags::empty(), - data: ( + data: Bytes(( hex!("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"), 42u64, - ).encode(), + ).encode()), }, ); } @@ -1837,7 +1851,10 @@ mod tests { &mut GasMeter::new(GAS_LIMIT), ).unwrap(); - assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: hex!("445566778899").to_vec() }); + assert_eq!(output, ExecReturnValue { + flags: ReturnFlags::empty(), + data: Bytes(hex!("445566778899").to_vec()), + }); assert!(output.is_success()); } @@ -1850,7 +1867,10 @@ mod tests { &mut GasMeter::new(GAS_LIMIT), ).unwrap(); - assert_eq!(output, ExecReturnValue { flags: ReturnFlags::REVERT, data: hex!("5566778899").to_vec() }); + assert_eq!(output, ExecReturnValue { + flags: ReturnFlags::REVERT, + data: Bytes(hex!("5566778899").to_vec()), + }); assert!(!output.is_success()); } @@ -1962,7 +1982,7 @@ mod tests { MockExt::default(), &mut GasMeter::new(GAS_LIMIT), ).unwrap(); - let rent_params = >::default().encode(); + let rent_params = Bytes(>::default().encode()); assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params }); } } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index f3757e4c2b10d..bed56f409d579 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -19,7 +19,7 @@ use crate::{ Config, CodeHash, BalanceOf, Error, - exec::{Ext, StorageKey, TopicOf}, + exec::{Ext, StorageKey, TopicOf, ExecResult, ExecError}, gas::{GasMeter, Token, ChargedAmount}, wasm::env_def::ConvertibleToWasm, schedule::HostFnWeights, @@ -29,14 +29,14 @@ use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weigh use sp_std::prelude::*; use codec::{Decode, DecodeAll, Encode}; use sp_runtime::traits::SaturatedConversion; -use sp_core::crypto::UncheckedFrom; +use sp_core::{Bytes, crypto::UncheckedFrom}; use sp_io::hashing::{ keccak_256, blake2_256, blake2_128, sha2_256, }; -use pallet_contracts_primitives::{ExecResult, ExecReturnValue, ReturnFlags, ExecError}; +use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; /// Every error that can be returned to a contract when it calls any of the host functions. /// @@ -347,19 +347,19 @@ where )?; Ok(ExecReturnValue { flags, - data, + data: Bytes(data), }) }, TrapReason::Termination => { Ok(ExecReturnValue { flags: ReturnFlags::empty(), - data: Vec::new(), + data: Bytes(Vec::new()), }) }, TrapReason::Restoration => { Ok(ExecReturnValue { flags: ReturnFlags::empty(), - data: Vec::new(), + data: Bytes(Vec::new()), }) }, TrapReason::SupervisorError(error) => Err(error)?, @@ -370,7 +370,7 @@ where match sandbox_result { // No traps were generated. Proceed normally. Ok(_) => { - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) } // `Error::Module` is returned only if instantiation or linking failed (i.e. // wasm binary tried to import a function that is not provided by the host). @@ -596,7 +596,7 @@ where /// Fallible conversion of a `ExecResult` to `ReturnCode`. fn exec_into_return_code(from: ExecResult) -> Result { - use pallet_contracts_primitives::ErrorOrigin::Callee; + use crate::exec::ErrorOrigin::Callee; let ExecError { error, origin } = match from { Ok(retval) => return Ok(retval.into()), diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index c72f38ea0827e..1f1b88fe2f1c7 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -148,6 +148,12 @@ impl Deref for Bytes { fn deref(&self) -> &[u8] { &self.0[..] } } +impl codec::WrapperTypeEncode for Bytes {} + +impl codec::WrapperTypeDecode for Bytes { + type Wrapped = Vec; +} + #[cfg(feature = "std")] impl sp_std::str::FromStr for Bytes { type Err = bytes::FromHexError;