contracts: Add RPC that allows instantiating of a contract (#8451)

* contracts: Add RPC that allows instantiating of a contract

* Encode `debug_message` as bytes because usage of `String` is forbidden

* Remove erroneous derive attribute

* Fix rpc tests for new `debug_message` encoding

* Fix typo

Co-authored-by: Andrew Jones <ascjones@gmail.com>

Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
Alexander Theißen
2021-04-13 13:26:52 +02:00
committed by GitHub
parent 24311eee3e
commit f854194139
16 changed files with 471 additions and 191 deletions
+2
View File
@@ -4813,6 +4813,8 @@ version = "3.0.0"
dependencies = [
"bitflags",
"parity-scale-codec",
"serde",
"sp-core",
"sp-runtime",
"sp-std",
]
+1 -1
View File
@@ -115,7 +115,7 @@ pub fn create_full<C, P, SC, B>(
C: ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore +
HeaderMetadata<Block, Error=BlockChainError> + Sync + Send + 'static,
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber>,
C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
C::Api: pallet_mmr_rpc::MmrRuntimeApi<Block, <Block as sp_runtime::traits::Block>::Hash>,
C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>,
C::Api: BabeApi<Block>,
+15 -1
View File
@@ -1340,7 +1340,9 @@ impl_runtime_apis! {
}
}
impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber>
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<Hash>,
data: Vec<u8>,
salt: Vec<u8>,
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
{
Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true)
}
fn get_storage(
address: AccountId,
key: [u8; 32],
+2
View File
@@ -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)
@@ -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",
]
+62 -37
View File
@@ -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<T> {
/// 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<ExecReturnValue, DispatchError>>;
/// Result type of a `bare_instantiate` call.
pub type ContractInstantiateResult<AccountId, BlockNumber> =
ContractResult<Result<InstantiateReturnValue<AccountId, BlockNumber>, DispatchError>>;
/// Result type of a `get_storage` call.
pub type GetStorageResult = Result<Option<Vec<u8>>, 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<BlockNumber> {
/// Eviction is projected to happen at the specified block number.
EvictionAt(BlockNumber),
@@ -62,6 +91,8 @@ pub enum RentProjection<BlockNumber> {
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<u8>,
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<AccountId, BlockNumber> {
/// 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,
/// # 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<RentProjection<BlockNumber>>,
}
/// 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,
/// 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<Hash> {
/// A wasm module as raw bytes.
Upload(Bytes),
/// The code hash of an on-chain wasm blob.
Existing(Hash),
}
impl<T: Into<DispatchError>> From<T> for ExecError {
fn from(error: T) -> Self {
Self {
error: error.into(),
origin: ErrorOrigin::Caller,
}
}
}
/// 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<ExecReturnValue, ExecError>;
@@ -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<AccountId, Balance, BlockNumber> where
pub trait ContractsApi<AccountId, Balance, BlockNumber, Hash> 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<u8>,
) -> ContractExecResult;
/// Instantiate a new contract.
///
/// See [`pallet_contracts::Pallet::instantiate`].
fn instantiate(
origin: AccountId,
endowment: Balance,
gas_limit: u64,
code: Code<Hash>,
data: Vec<u8>,
salt: Vec<u8>,
) -> ContractInstantiateResult<AccountId, BlockNumber>;
/// Query a given storage key in a given contract.
///
/// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the
+180 -85
View File
@@ -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<ContractAccessError> for Error {
pub struct CallRequest<AccountId> {
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.
#[serde(deny_unknown_fields)]
pub struct InstantiateRequest<AccountId, Hash> {
origin: AccountId,
endowment: NumberOrHex,
gas_limit: NumberOrHex,
code: Code<Hash>,
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<RpcContractExecSuccess, DispatchError>,
}
impl From<ContractExecResult> 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),
},
}
}
salt: Bytes,
}
/// Contracts RPC methods.
#[rpc]
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance> {
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {
/// 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<BlockHash, BlockNumber, AccountId, Balance> {
&self,
call_request: CallRequest<AccountId>,
at: Option<BlockHash>,
) -> Result<RpcContractExecResult>;
) -> Result<ContractExecResult>;
/// 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<AccountId, Hash>,
at: Option<BlockHash>,
) -> Result<ContractInstantiateResult<AccountId, BlockNumber>>;
/// 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<C, B> Contracts<C, B> {
}
}
}
impl<C, Block, AccountId, Balance>
impl<C, Block, AccountId, Balance, Hash>
ContractsApi<
<Block as BlockT>::Hash,
<<Block as BlockT>::Header as HeaderT>::Number,
AccountId,
Balance,
Hash,
> for Contracts<C, Block>
where
Block: BlockT,
@@ -199,15 +186,17 @@ where
AccountId,
Balance,
<<Block as BlockT>::Header as HeaderT>::Number,
Hash,
>,
AccountId: Codec,
Balance: Codec + TryFrom<number::NumberOrHex>,
Balance: Codec + TryFrom<NumberOrHex>,
Hash: Codec,
{
fn call(
&self,
call_request: CallRequest<AccountId>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<RpcContractExecResult> {
) -> Result<ContractExecResult> {
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<AccountId, Hash>,
at: Option<<Block as BlockT>::Hash>,
) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::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<H: std::fmt::Debug + Copy, T: TryFrom<H>>(from: H, name: &str) -> Result<T> {
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<String>;
@@ -327,13 +351,84 @@ mod tests {
}
#[test]
fn result_should_serialize_deserialize_properly() {
fn test(expected: &str) {
let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
let actual = serde_json::to_string(&res).unwrap();
assert_eq!(actual, expected);
fn instantiate_request_should_serialize_deserialize_properly() {
type Req = InstantiateRequest<String, String>;
let req: Req = serde_json::from_str(r#"
{
"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"endowment": "0x88",
"gasLimit": 42,
"code": { "existing": "0x1122" },
"data": "0x4299",
"salt": "0x9988"
}
test(r#"{"gasConsumed":5000,"debugMessage":"helpOk","result":{"Ok":{"flags":5,"data":"0x1234"}}}"#);
test(r#"{"gasConsumed":3400,"debugMessage":"helpErr","result":{"Err":"BadOrigin"}}"#);
"#).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: ContractInstantiateResult<String, u64> = 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": {
"result": {
"flags": 5,
"data": "0x1234"
},
"accountId": "5CiPP",
"rentProjection": null
}
}
}"#);
test(r#"{
"gasConsumed": 3400,
"debugMessage": "0x68656c70457272",
"result": {
"Err": "BadOrigin"
}
}"#);
}
}
+47 -11
View File
@@ -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<T> = <T as frame_system::Config>::AccountId;
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
pub type SeedOf<T> = <T as frame_system::Config>::Hash;
pub type BlockNumberOf<T> = <T as frame_system::Config>::BlockNumber;
pub type StorageKey = [u8; 32];
pub type ExecResult = Result<ExecReturnValue, ExecError>;
/// A type that represents a topic of an event. At the moment a hash is used.
pub type TopicOf<T> = <T as frame_system::Config>::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<T: Into<DispatchError>> From<T> 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.
+1 -2
View File
@@ -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)]
+62 -4
View File
@@ -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<T> = <T as frame_system::Config>::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<T>,
gas_limit: Weight,
code: Code<CodeHash<T>>,
data: Vec<u8>,
salt: Vec<u8>,
compute_projection: bool,
) -> ContractInstantiateResult<T::AccountId, T::BlockNumber> {
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::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::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
.map_err(|_| <Error<T>>::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()),
}
}
+5 -2
View File
@@ -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 = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
.map_err(|_| IsTombstone)?;
let code_size = module.occupied_storage();
let current_block_number = <frame_system::Pallet<T>>::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.
+22 -21
View File
@@ -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,
+36 -16
View File
@@ -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::<Test>::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 = <RentParams<Test>>::default().encode();
let rent_params = Bytes(<RentParams<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
}
}
@@ -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<ReturnCode, DispatchError> {
use pallet_contracts_primitives::ErrorOrigin::Callee;
use crate::exec::ErrorOrigin::Callee;
let ExecError { error, origin } = match from {
Ok(retval) => return Ok(retval.into()),
+6
View File
@@ -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<u8>;
}
#[cfg(feature = "std")]
impl sp_std::str::FromStr for Bytes {
type Err = bytes::FromHexError;