mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 18:41:05 +00:00
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:
committed by
GitHub
parent
24311eee3e
commit
f854194139
Generated
+2
@@ -4813,6 +4813,8 @@ version = "3.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"parity-scale-codec",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user