mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 11:07:56 +00:00
feee773d15
`execute` and `send` try to decode the xcm in the parameters before reaching the filter line. The new extrinsics decode only after the filter line. These should be used instead of the old ones. ## TODO - [x] Tests - [x] Generate weights - [x] Deprecation issue -> https://github.com/paritytech/polkadot-sdk/issues/3771 - [x] PRDoc - [x] Handle error in pallet-contracts This would make writing XCMs in PJS Apps more difficult, but here's the fix for that: https://github.com/polkadot-js/apps/pull/10350. Already deployed! https://polkadot.js.org/apps/#/utilities/xcm Supersedes https://github.com/paritytech/polkadot-sdk/pull/1798/ --------- Co-authored-by: PG Herveou <pgherveou@gmail.com> Co-authored-by: command-bot <> Co-authored-by: Adrian Catangiu <adrian@parity.io>
2289 lines
70 KiB
Rust
2289 lines
70 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Environment definition of the wasm smart-contract runtime.
|
|
|
|
use crate::{
|
|
exec::{ExecError, ExecResult, Ext, Key, TopicOf},
|
|
gas::{ChargedAmount, Token},
|
|
primitives::ExecReturnValue,
|
|
BalanceOf, CodeHash, Config, DebugBufferVec, Error, SENTINEL,
|
|
};
|
|
use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen};
|
|
use frame_support::{
|
|
dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types,
|
|
traits::Get, weights::Weight,
|
|
};
|
|
use pallet_contracts_proc_macro::define_env;
|
|
use pallet_contracts_uapi::{CallFlags, ReturnFlags};
|
|
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
|
|
use sp_runtime::{
|
|
traits::{Bounded, Zero},
|
|
DispatchError, RuntimeDebug,
|
|
};
|
|
use sp_std::{fmt, prelude::*};
|
|
use wasmi::{core::HostError, errors::LinkerError, Linker, Memory, Store};
|
|
|
|
/// The maximum nesting depth a contract can use when encoding types.
|
|
const MAX_DECODE_NESTING: u32 = 256;
|
|
|
|
/// Passed to [`Environment`] to determine whether it should expose deprecated interfaces.
|
|
pub enum AllowDeprecatedInterface {
|
|
/// No deprecated interfaces are exposed.
|
|
No,
|
|
/// Deprecated interfaces are exposed.
|
|
Yes,
|
|
}
|
|
|
|
/// Passed to [`Environment`] to determine whether it should expose unstable interfaces.
|
|
pub enum AllowUnstableInterface {
|
|
/// No unstable interfaces are exposed.
|
|
No,
|
|
/// Unstable interfaces are exposed.
|
|
Yes,
|
|
}
|
|
|
|
/// Trait implemented by the [`define_env`](pallet_contracts_proc_macro::define_env) macro for the
|
|
/// emitted `Env` struct.
|
|
pub trait Environment<HostState> {
|
|
/// Adds all declared functions to the supplied [`Linker`](wasmi::Linker) and
|
|
/// [`Store`](wasmi::Store).
|
|
fn define(
|
|
store: &mut Store<HostState>,
|
|
linker: &mut Linker<HostState>,
|
|
allow_unstable: AllowUnstableInterface,
|
|
allow_deprecated: AllowDeprecatedInterface,
|
|
) -> Result<(), LinkerError>;
|
|
}
|
|
|
|
/// Type of a storage key.
|
|
enum KeyType {
|
|
/// Legacy fix sized key `[u8;32]`.
|
|
Fix,
|
|
/// Variable sized key used in transparent hashing,
|
|
/// cannot be larger than MaxStorageKeyLen.
|
|
Var(u32),
|
|
}
|
|
|
|
pub use pallet_contracts_uapi::ReturnErrorCode;
|
|
|
|
parameter_types! {
|
|
/// Getter types used by [`crate::api_doc::Current::call_runtime`]
|
|
const CallRuntimeFailed: ReturnErrorCode = ReturnErrorCode::CallRuntimeFailed;
|
|
/// Getter types used by [`crate::api_doc::Current::xcm_execute`]
|
|
const XcmExecutionFailed: ReturnErrorCode = ReturnErrorCode::XcmExecutionFailed;
|
|
}
|
|
|
|
impl From<ExecReturnValue> for ReturnErrorCode {
|
|
fn from(from: ExecReturnValue) -> Self {
|
|
if from.flags.contains(ReturnFlags::REVERT) {
|
|
Self::CalleeReverted
|
|
} else {
|
|
Self::Success
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The data passed through when a contract uses `seal_return`.
|
|
#[derive(RuntimeDebug)]
|
|
pub struct ReturnData {
|
|
/// The flags as passed through by the contract. They are still unchecked and
|
|
/// will later be parsed into a `ReturnFlags` bitflags struct.
|
|
flags: u32,
|
|
/// The output buffer passed by the contract as return data.
|
|
data: Vec<u8>,
|
|
}
|
|
|
|
/// Enumerates all possible reasons why a trap was generated.
|
|
///
|
|
/// This is either used to supply the caller with more information about why an error
|
|
/// occurred (the SupervisorError variant).
|
|
/// The other case is where the trap does not constitute an error but rather was invoked
|
|
/// as a quick way to terminate the application (all other variants).
|
|
#[derive(RuntimeDebug)]
|
|
pub enum TrapReason {
|
|
/// The supervisor trapped the contract because of an error condition occurred during
|
|
/// execution in privileged code.
|
|
SupervisorError(DispatchError),
|
|
/// Signals that trap was generated in response to call `seal_return` host function.
|
|
Return(ReturnData),
|
|
/// Signals that a trap was generated in response to a successful call to the
|
|
/// `seal_terminate` host function.
|
|
Termination,
|
|
}
|
|
|
|
impl<T: Into<DispatchError>> From<T> for TrapReason {
|
|
fn from(from: T) -> Self {
|
|
Self::SupervisorError(from.into())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for TrapReason {
|
|
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl HostError for TrapReason {}
|
|
|
|
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
|
#[derive(Copy, Clone)]
|
|
pub enum RuntimeCosts {
|
|
/// Weight charged for copying data from the sandbox.
|
|
CopyFromContract(u32),
|
|
/// Weight charged for copying data to the sandbox.
|
|
CopyToContract(u32),
|
|
/// Weight of calling `seal_caller`.
|
|
Caller,
|
|
/// Weight of calling `seal_is_contract`.
|
|
IsContract,
|
|
/// Weight of calling `seal_code_hash`.
|
|
CodeHash,
|
|
/// Weight of calling `seal_own_code_hash`.
|
|
OwnCodeHash,
|
|
/// Weight of calling `seal_caller_is_origin`.
|
|
CallerIsOrigin,
|
|
/// Weight of calling `caller_is_root`.
|
|
CallerIsRoot,
|
|
/// Weight of calling `seal_address`.
|
|
Address,
|
|
/// Weight of calling `seal_gas_left`.
|
|
GasLeft,
|
|
/// Weight of calling `seal_balance`.
|
|
Balance,
|
|
/// Weight of calling `seal_value_transferred`.
|
|
ValueTransferred,
|
|
/// Weight of calling `seal_minimum_balance`.
|
|
MinimumBalance,
|
|
/// Weight of calling `seal_block_number`.
|
|
BlockNumber,
|
|
/// Weight of calling `seal_now`.
|
|
Now,
|
|
/// Weight of calling `seal_weight_to_fee`.
|
|
WeightToFee,
|
|
/// Weight of calling `seal_input` without the weight of copying the input.
|
|
InputBase,
|
|
/// Weight of calling `seal_return` for the given output size.
|
|
Return(u32),
|
|
/// Weight of calling `seal_terminate`.
|
|
Terminate,
|
|
/// Weight of calling `seal_random`. It includes the weight for copying the subject.
|
|
Random,
|
|
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
|
|
DepositEvent { num_topic: u32, len: u32 },
|
|
/// Weight of calling `seal_debug_message` per byte of passed message.
|
|
DebugMessage(u32),
|
|
/// Weight of calling `seal_set_storage` for the given storage item sizes.
|
|
SetStorage { old_bytes: u32, new_bytes: u32 },
|
|
/// Weight of calling `seal_clear_storage` per cleared byte.
|
|
ClearStorage(u32),
|
|
/// Weight of calling `seal_contains_storage` per byte of the checked item.
|
|
ContainsStorage(u32),
|
|
/// Weight of calling `seal_get_storage` with the specified size in storage.
|
|
GetStorage(u32),
|
|
/// Weight of calling `seal_take_storage` for the given size.
|
|
TakeStorage(u32),
|
|
/// Weight of calling `seal_transfer`.
|
|
Transfer,
|
|
/// Base weight of calling `seal_call`.
|
|
CallBase,
|
|
/// Weight of calling `seal_delegate_call` for the given input size.
|
|
DelegateCallBase,
|
|
/// Weight of the transfer performed during a call.
|
|
CallSurchargeTransfer,
|
|
/// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag.
|
|
CallInputCloned(u32),
|
|
/// Weight of calling `seal_instantiate` for the given input length and salt.
|
|
InstantiateBase { input_data_len: u32, salt_len: u32 },
|
|
/// Weight of the transfer performed during an instantiate.
|
|
InstantiateSurchargeTransfer,
|
|
/// Weight of calling `seal_hash_sha_256` for the given input size.
|
|
HashSha256(u32),
|
|
/// Weight of calling `seal_hash_keccak_256` for the given input size.
|
|
HashKeccak256(u32),
|
|
/// Weight of calling `seal_hash_blake2_256` for the given input size.
|
|
HashBlake256(u32),
|
|
/// Weight of calling `seal_hash_blake2_128` for the given input size.
|
|
HashBlake128(u32),
|
|
/// Weight of calling `seal_ecdsa_recover`.
|
|
EcdsaRecovery,
|
|
/// Weight of calling `seal_sr25519_verify` for the given input size.
|
|
Sr25519Verify(u32),
|
|
/// Weight charged by a chain extension through `seal_call_chain_extension`.
|
|
ChainExtension(Weight),
|
|
/// Weight charged for calling into the runtime.
|
|
CallRuntime(Weight),
|
|
/// Weight charged for calling xcm_execute.
|
|
CallXcmExecute(Weight),
|
|
/// Weight of calling `seal_set_code_hash`
|
|
SetCodeHash,
|
|
/// Weight of calling `ecdsa_to_eth_address`
|
|
EcdsaToEthAddress,
|
|
/// Weight of calling `reentrance_count`
|
|
ReentrantCount,
|
|
/// Weight of calling `account_reentrance_count`
|
|
AccountEntranceCount,
|
|
/// Weight of calling `instantiation_nonce`
|
|
InstantiationNonce,
|
|
/// Weight of calling `lock_delegate_dependency`
|
|
LockDelegateDependency,
|
|
/// Weight of calling `unlock_delegate_dependency`
|
|
UnlockDelegateDependency,
|
|
}
|
|
|
|
impl<T: Config> Token<T> for RuntimeCosts {
|
|
fn influence_lowest_gas_limit(&self) -> bool {
|
|
match self {
|
|
&Self::CallXcmExecute(_) => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
fn weight(&self) -> Weight {
|
|
let s = T::Schedule::get().host_fn_weights;
|
|
use self::RuntimeCosts::*;
|
|
match *self {
|
|
CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()),
|
|
CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()),
|
|
Caller => s.caller,
|
|
IsContract => s.is_contract,
|
|
CodeHash => s.code_hash,
|
|
OwnCodeHash => s.own_code_hash,
|
|
CallerIsOrigin => s.caller_is_origin,
|
|
CallerIsRoot => s.caller_is_root,
|
|
Address => s.address,
|
|
GasLeft => s.gas_left,
|
|
Balance => s.balance,
|
|
ValueTransferred => s.value_transferred,
|
|
MinimumBalance => s.minimum_balance,
|
|
BlockNumber => s.block_number,
|
|
Now => s.now,
|
|
WeightToFee => s.weight_to_fee,
|
|
InputBase => s.input,
|
|
Return(len) => s.r#return.saturating_add(s.return_per_byte.saturating_mul(len.into())),
|
|
Terminate => s.terminate,
|
|
Random => s.random,
|
|
DepositEvent { num_topic, len } => s
|
|
.deposit_event
|
|
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
|
|
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
|
DebugMessage(len) => s
|
|
.debug_message
|
|
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
|
SetStorage { new_bytes, old_bytes } => s
|
|
.set_storage
|
|
.saturating_add(s.set_storage_per_new_byte.saturating_mul(new_bytes.into()))
|
|
.saturating_add(s.set_storage_per_old_byte.saturating_mul(old_bytes.into())),
|
|
ClearStorage(len) => s
|
|
.clear_storage
|
|
.saturating_add(s.clear_storage_per_byte.saturating_mul(len.into())),
|
|
ContainsStorage(len) => s
|
|
.contains_storage
|
|
.saturating_add(s.contains_storage_per_byte.saturating_mul(len.into())),
|
|
GetStorage(len) =>
|
|
s.get_storage.saturating_add(s.get_storage_per_byte.saturating_mul(len.into())),
|
|
TakeStorage(len) => s
|
|
.take_storage
|
|
.saturating_add(s.take_storage_per_byte.saturating_mul(len.into())),
|
|
Transfer => s.transfer,
|
|
CallBase => s.call,
|
|
DelegateCallBase => s.delegate_call,
|
|
CallSurchargeTransfer => s.call_transfer_surcharge,
|
|
CallInputCloned(len) => s.call_per_cloned_byte.saturating_mul(len.into()),
|
|
InstantiateBase { input_data_len, salt_len } => s
|
|
.instantiate
|
|
.saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into()))
|
|
.saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())),
|
|
InstantiateSurchargeTransfer => s.instantiate_transfer_surcharge,
|
|
HashSha256(len) => s
|
|
.hash_sha2_256
|
|
.saturating_add(s.hash_sha2_256_per_byte.saturating_mul(len.into())),
|
|
HashKeccak256(len) => s
|
|
.hash_keccak_256
|
|
.saturating_add(s.hash_keccak_256_per_byte.saturating_mul(len.into())),
|
|
HashBlake256(len) => s
|
|
.hash_blake2_256
|
|
.saturating_add(s.hash_blake2_256_per_byte.saturating_mul(len.into())),
|
|
HashBlake128(len) => s
|
|
.hash_blake2_128
|
|
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
|
EcdsaRecovery => s.ecdsa_recover,
|
|
Sr25519Verify(len) => s
|
|
.sr25519_verify
|
|
.saturating_add(s.sr25519_verify_per_byte.saturating_mul(len.into())),
|
|
ChainExtension(weight) | CallRuntime(weight) | CallXcmExecute(weight) => weight,
|
|
SetCodeHash => s.set_code_hash,
|
|
EcdsaToEthAddress => s.ecdsa_to_eth_address,
|
|
ReentrantCount => s.reentrance_count,
|
|
AccountEntranceCount => s.account_reentrance_count,
|
|
InstantiationNonce => s.instantiation_nonce,
|
|
LockDelegateDependency => s.lock_delegate_dependency,
|
|
UnlockDelegateDependency => s.unlock_delegate_dependency,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Same as [`Runtime::charge_gas`].
|
|
///
|
|
/// We need this access as a macro because sometimes hiding the lifetimes behind
|
|
/// a function won't work out.
|
|
macro_rules! charge_gas {
|
|
($runtime:expr, $costs:expr) => {{
|
|
$runtime.ext.gas_meter_mut().charge($costs)
|
|
}};
|
|
}
|
|
|
|
/// The kind of call that should be performed.
|
|
enum CallType {
|
|
/// Execute another instantiated contract
|
|
Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight },
|
|
/// Execute deployed code in the context (storage, account ID, value) of the caller contract
|
|
DelegateCall { code_hash_ptr: u32 },
|
|
}
|
|
|
|
impl CallType {
|
|
fn cost(&self) -> RuntimeCosts {
|
|
match self {
|
|
CallType::Call { .. } => RuntimeCosts::CallBase,
|
|
CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This is only appropriate when writing out data of constant size that does not depend on user
|
|
/// input. In this case the costs for this copy was already charged as part of the token at
|
|
/// the beginning of the API entry point.
|
|
fn already_charged(_: u32) -> Option<RuntimeCosts> {
|
|
None
|
|
}
|
|
|
|
/// Can only be used for one call.
|
|
pub struct Runtime<'a, E: Ext + 'a> {
|
|
ext: &'a mut E,
|
|
input_data: Option<Vec<u8>>,
|
|
memory: Option<Memory>,
|
|
chain_extension: Option<Box<<E::T as Config>::ChainExtension>>,
|
|
}
|
|
|
|
impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
|
pub fn new(ext: &'a mut E, input_data: Vec<u8>) -> Self {
|
|
Runtime {
|
|
ext,
|
|
input_data: Some(input_data),
|
|
memory: None,
|
|
chain_extension: Some(Box::new(Default::default())),
|
|
}
|
|
}
|
|
|
|
pub fn memory(&self) -> Option<Memory> {
|
|
self.memory
|
|
}
|
|
|
|
pub fn set_memory(&mut self, memory: Memory) {
|
|
self.memory = Some(memory);
|
|
}
|
|
|
|
/// Converts the sandbox result and the runtime state into the execution outcome.
|
|
pub fn to_execution_result(self, sandbox_result: Result<(), wasmi::Error>) -> ExecResult {
|
|
use wasmi::core::TrapCode::OutOfFuel;
|
|
use TrapReason::*;
|
|
|
|
match sandbox_result {
|
|
// Contract returned from main function -> no data was returned.
|
|
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
|
|
// `OutOfGas` when host asks engine to consume more than left in the _store_.
|
|
// We should never get this case, as gas meter is being charged (and hence raises error)
|
|
// first.
|
|
Err(wasmi::Error::Store(_)) => Err(Error::<E::T>::OutOfGas.into()),
|
|
// Contract either trapped or some host function aborted the execution.
|
|
Err(wasmi::Error::Trap(trap)) => {
|
|
if let Some(OutOfFuel) = trap.trap_code() {
|
|
// `OutOfGas` during engine execution.
|
|
return Err(Error::<E::T>::OutOfGas.into())
|
|
}
|
|
// If we encoded a reason then it is some abort generated by a host function.
|
|
if let Some(reason) = &trap.downcast_ref::<TrapReason>() {
|
|
match &reason {
|
|
Return(ReturnData { flags, data }) => {
|
|
let flags = ReturnFlags::from_bits(*flags)
|
|
.ok_or(Error::<E::T>::InvalidCallFlags)?;
|
|
return Ok(ExecReturnValue { flags, data: data.to_vec() })
|
|
},
|
|
Termination =>
|
|
return Ok(ExecReturnValue {
|
|
flags: ReturnFlags::empty(),
|
|
data: Vec::new(),
|
|
}),
|
|
SupervisorError(error) => return Err((*error).into()),
|
|
}
|
|
}
|
|
// Otherwise the trap came from the contract itself.
|
|
Err(Error::<E::T>::ContractTrapped.into())
|
|
},
|
|
// Any other error is returned only if instantiation or linking failed (i.e.
|
|
// wasm binary tried to import a function that is not provided by the host).
|
|
// This shouldn't happen because validation process ought to reject such binaries.
|
|
//
|
|
// Because panics are really undesirable in the runtime code, we treat this as
|
|
// a trap for now. Eventually, we might want to revisit this.
|
|
Err(_) => Err(Error::<E::T>::CodeRejected.into()),
|
|
}
|
|
}
|
|
|
|
/// Get a mutable reference to the inner `Ext`.
|
|
///
|
|
/// This is mainly for the chain extension to have access to the environment the
|
|
/// contract is executing in.
|
|
pub fn ext(&mut self) -> &mut E {
|
|
self.ext
|
|
}
|
|
|
|
/// Charge the gas meter with the specified token.
|
|
///
|
|
/// Returns `Err(HostError)` if there is not enough gas.
|
|
pub fn charge_gas(&mut self, costs: RuntimeCosts) -> Result<ChargedAmount, DispatchError> {
|
|
charge_gas!(self, costs)
|
|
}
|
|
|
|
/// Adjust a previously charged amount down to its actual amount.
|
|
///
|
|
/// This is when a maximum a priori amount was charged and then should be partially
|
|
/// refunded to match the actual amount.
|
|
pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) {
|
|
self.ext.gas_meter_mut().adjust_gas(charged, actual_costs);
|
|
}
|
|
|
|
/// Charge, Run and adjust gas, for executing the given dispatchable.
|
|
fn call_dispatchable<ErrorReturnCode: Get<ReturnErrorCode>>(
|
|
&mut self,
|
|
dispatch_info: DispatchInfo,
|
|
runtime_cost: impl Fn(Weight) -> RuntimeCosts,
|
|
run: impl FnOnce(&mut Self) -> DispatchResultWithPostInfo,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
use frame_support::dispatch::extract_actual_weight;
|
|
let charged = self.charge_gas(runtime_cost(dispatch_info.weight))?;
|
|
let result = run(self);
|
|
let actual_weight = extract_actual_weight(&result, &dispatch_info);
|
|
self.adjust_gas(charged, runtime_cost(actual_weight));
|
|
match result {
|
|
Ok(_) => Ok(ReturnErrorCode::Success),
|
|
Err(e) => {
|
|
if self.ext.debug_buffer_enabled() {
|
|
self.ext.append_debug_buffer("call failed with: ");
|
|
self.ext.append_debug_buffer(e.into());
|
|
};
|
|
Ok(ErrorReturnCode::get())
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Read designated chunk from the sandbox memory.
|
|
///
|
|
/// Returns `Err` if one of the following conditions occurs:
|
|
///
|
|
/// - requested buffer is not within the bounds of the sandbox memory.
|
|
pub fn read_sandbox_memory(
|
|
&self,
|
|
memory: &[u8],
|
|
ptr: u32,
|
|
len: u32,
|
|
) -> Result<Vec<u8>, DispatchError> {
|
|
ensure!(len <= self.ext.schedule().limits.max_memory_size(), Error::<E::T>::OutOfBounds);
|
|
let mut buf = vec![0u8; len as usize];
|
|
self.read_sandbox_memory_into_buf(memory, ptr, buf.as_mut_slice())?;
|
|
Ok(buf)
|
|
}
|
|
|
|
/// Read designated chunk from the sandbox memory into the supplied buffer.
|
|
///
|
|
/// Returns `Err` if one of the following conditions occurs:
|
|
///
|
|
/// - requested buffer is not within the bounds of the sandbox memory.
|
|
pub fn read_sandbox_memory_into_buf(
|
|
&self,
|
|
memory: &[u8],
|
|
ptr: u32,
|
|
buf: &mut [u8],
|
|
) -> Result<(), DispatchError> {
|
|
let ptr = ptr as usize;
|
|
let bound_checked =
|
|
memory.get(ptr..ptr + buf.len()).ok_or_else(|| Error::<E::T>::OutOfBounds)?;
|
|
buf.copy_from_slice(bound_checked);
|
|
Ok(())
|
|
}
|
|
|
|
/// Reads and decodes a type with a size fixed at compile time from contract memory.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The weight of reading a fixed value is included in the overall weight of any
|
|
/// contract callable function.
|
|
pub fn read_sandbox_memory_as<D: Decode + MaxEncodedLen>(
|
|
&self,
|
|
memory: &[u8],
|
|
ptr: u32,
|
|
) -> Result<D, DispatchError> {
|
|
let ptr = ptr as usize;
|
|
let mut bound_checked = memory.get(ptr..).ok_or_else(|| Error::<E::T>::OutOfBounds)?;
|
|
|
|
let decoded = D::decode_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked)
|
|
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
|
|
Ok(decoded)
|
|
}
|
|
|
|
/// Read designated chunk from the sandbox memory and attempt to decode into the specified type.
|
|
///
|
|
/// Returns `Err` if one of the following conditions occurs:
|
|
///
|
|
/// - requested buffer is not within the bounds of the sandbox memory.
|
|
/// - the buffer contents cannot be decoded as the required type.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// There must be an extra benchmark for determining the influence of `len` with
|
|
/// regard to the overall weight.
|
|
pub fn read_sandbox_memory_as_unbounded<D: Decode>(
|
|
&self,
|
|
memory: &[u8],
|
|
ptr: u32,
|
|
len: u32,
|
|
) -> Result<D, DispatchError> {
|
|
let ptr = ptr as usize;
|
|
let mut bound_checked =
|
|
memory.get(ptr..ptr + len as usize).ok_or_else(|| Error::<E::T>::OutOfBounds)?;
|
|
|
|
let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut bound_checked)
|
|
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
|
|
|
|
Ok(decoded)
|
|
}
|
|
|
|
/// Write the given buffer and its length to the designated locations in sandbox memory and
|
|
/// charge gas according to the token returned by `create_token`.
|
|
//
|
|
/// `out_ptr` is the location in sandbox memory where `buf` should be written to.
|
|
/// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the
|
|
/// length of the buffer located at `out_ptr`. If that buffer is large enough the actual
|
|
/// `buf.len()` is written to this location.
|
|
///
|
|
/// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the
|
|
/// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying
|
|
/// output optional. For example to skip copying back the output buffer of an `seal_call`
|
|
/// when the caller is not interested in the result.
|
|
///
|
|
/// `create_token` can optionally instruct this function to charge the gas meter with the token
|
|
/// it returns. `create_token` receives the variable amount of bytes that are about to be copied
|
|
/// by this function.
|
|
///
|
|
/// In addition to the error conditions of `write_sandbox_memory` this functions returns
|
|
/// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`.
|
|
pub fn write_sandbox_output(
|
|
&mut self,
|
|
memory: &mut [u8],
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
buf: &[u8],
|
|
allow_skip: bool,
|
|
create_token: impl FnOnce(u32) -> Option<RuntimeCosts>,
|
|
) -> Result<(), DispatchError> {
|
|
if allow_skip && out_ptr == SENTINEL {
|
|
return Ok(())
|
|
}
|
|
|
|
let buf_len = buf.len() as u32;
|
|
let len: u32 = self.read_sandbox_memory_as(memory, out_len_ptr)?;
|
|
|
|
if len < buf_len {
|
|
return Err(Error::<E::T>::OutputBufferTooSmall.into())
|
|
}
|
|
|
|
if let Some(costs) = create_token(buf_len) {
|
|
self.charge_gas(costs)?;
|
|
}
|
|
|
|
self.write_sandbox_memory(memory, out_ptr, buf)?;
|
|
self.write_sandbox_memory(memory, out_len_ptr, &buf_len.encode())
|
|
}
|
|
|
|
/// Write the given buffer to the designated location in the sandbox memory.
|
|
///
|
|
/// Returns `Err` if one of the following conditions occurs:
|
|
///
|
|
/// - designated area is not within the bounds of the sandbox memory.
|
|
fn write_sandbox_memory(
|
|
&self,
|
|
memory: &mut [u8],
|
|
ptr: u32,
|
|
buf: &[u8],
|
|
) -> Result<(), DispatchError> {
|
|
let ptr = ptr as usize;
|
|
let bound_checked =
|
|
memory.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::<E::T>::OutOfBounds)?;
|
|
bound_checked.copy_from_slice(buf);
|
|
Ok(())
|
|
}
|
|
|
|
/// Computes the given hash function on the supplied input.
|
|
///
|
|
/// Reads from the sandboxed input buffer into an intermediate buffer.
|
|
/// Returns the result directly to the output buffer of the sandboxed memory.
|
|
///
|
|
/// It is the callers responsibility to provide an output buffer that
|
|
/// is large enough to hold the expected amount of bytes returned by the
|
|
/// chosen hash function.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The `input` and `output` buffers may overlap.
|
|
fn compute_hash_on_intermediate_buffer<F, R>(
|
|
&self,
|
|
memory: &mut [u8],
|
|
hash_fn: F,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<(), DispatchError>
|
|
where
|
|
F: FnOnce(&[u8]) -> R,
|
|
R: AsRef<[u8]>,
|
|
{
|
|
// Copy input into supervisor memory.
|
|
let input = self.read_sandbox_memory(memory, input_ptr, input_len)?;
|
|
// Compute the hash on the input buffer using the given hash function.
|
|
let hash = hash_fn(&input);
|
|
// Write the resulting hash back into the sandboxed output buffer.
|
|
self.write_sandbox_memory(memory, output_ptr, hash.as_ref())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Fallible conversion of `DispatchError` to `ReturnErrorCode`.
|
|
fn err_into_return_code(from: DispatchError) -> Result<ReturnErrorCode, DispatchError> {
|
|
use ReturnErrorCode::*;
|
|
|
|
let transfer_failed = Error::<E::T>::TransferFailed.into();
|
|
let no_code = Error::<E::T>::CodeNotFound.into();
|
|
let not_found = Error::<E::T>::ContractNotFound.into();
|
|
|
|
match from {
|
|
x if x == transfer_failed => Ok(TransferFailed),
|
|
x if x == no_code => Ok(CodeNotFound),
|
|
x if x == not_found => Ok(NotCallable),
|
|
err => Err(err),
|
|
}
|
|
}
|
|
|
|
/// Fallible conversion of a `ExecResult` to `ReturnErrorCode`.
|
|
fn exec_into_return_code(from: ExecResult) -> Result<ReturnErrorCode, DispatchError> {
|
|
use crate::exec::ErrorOrigin::Callee;
|
|
|
|
let ExecError { error, origin } = match from {
|
|
Ok(retval) => return Ok(retval.into()),
|
|
Err(err) => err,
|
|
};
|
|
|
|
match (error, origin) {
|
|
(_, Callee) => Ok(ReturnErrorCode::CalleeTrapped),
|
|
(err, _) => Self::err_into_return_code(err),
|
|
}
|
|
}
|
|
fn decode_key(
|
|
&self,
|
|
memory: &[u8],
|
|
key_type: KeyType,
|
|
key_ptr: u32,
|
|
) -> Result<crate::exec::Key<E::T>, TrapReason> {
|
|
let res = match key_type {
|
|
KeyType::Fix => {
|
|
let key = self.read_sandbox_memory(memory, key_ptr, 32u32)?;
|
|
Key::try_from_fix(key)
|
|
},
|
|
KeyType::Var(len) => {
|
|
ensure!(
|
|
len <= <<E as Ext>::T as Config>::MaxStorageKeyLen::get(),
|
|
Error::<E::T>::DecodingFailed
|
|
);
|
|
let key = self.read_sandbox_memory(memory, key_ptr, len)?;
|
|
Key::try_from_var(key)
|
|
},
|
|
};
|
|
|
|
res.map_err(|_| Error::<E::T>::DecodingFailed.into())
|
|
}
|
|
|
|
fn set_storage(
|
|
&mut self,
|
|
memory: &[u8],
|
|
key_type: KeyType,
|
|
key_ptr: u32,
|
|
value_ptr: u32,
|
|
value_len: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
let max_size = self.ext.max_value_size();
|
|
let charged = self
|
|
.charge_gas(RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: max_size })?;
|
|
if value_len > max_size {
|
|
return Err(Error::<E::T>::ValueTooLarge.into())
|
|
}
|
|
let key = self.decode_key(memory, key_type, key_ptr)?;
|
|
let value = Some(self.read_sandbox_memory(memory, value_ptr, value_len)?);
|
|
let write_outcome = self.ext.set_storage(&key, value, false)?;
|
|
|
|
self.adjust_gas(
|
|
charged,
|
|
RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() },
|
|
);
|
|
Ok(write_outcome.old_len_with_sentinel())
|
|
}
|
|
|
|
fn clear_storage(
|
|
&mut self,
|
|
memory: &[u8],
|
|
key_type: KeyType,
|
|
key_ptr: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?;
|
|
let key = self.decode_key(memory, key_type, key_ptr)?;
|
|
let outcome = self.ext.set_storage(&key, None, false)?;
|
|
|
|
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len()));
|
|
Ok(outcome.old_len_with_sentinel())
|
|
}
|
|
|
|
fn get_storage(
|
|
&mut self,
|
|
memory: &mut [u8],
|
|
key_type: KeyType,
|
|
key_ptr: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?;
|
|
let key = self.decode_key(memory, key_type, key_ptr)?;
|
|
let outcome = self.ext.get_storage(&key);
|
|
|
|
if let Some(value) = outcome {
|
|
self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
|
|
self.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&value,
|
|
false,
|
|
already_charged,
|
|
)?;
|
|
Ok(ReturnErrorCode::Success)
|
|
} else {
|
|
self.adjust_gas(charged, RuntimeCosts::GetStorage(0));
|
|
Ok(ReturnErrorCode::KeyNotFound)
|
|
}
|
|
}
|
|
|
|
fn contains_storage(
|
|
&mut self,
|
|
memory: &[u8],
|
|
key_type: KeyType,
|
|
key_ptr: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?;
|
|
let key = self.decode_key(memory, key_type, key_ptr)?;
|
|
let outcome = self.ext.get_storage_size(&key);
|
|
|
|
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0)));
|
|
Ok(outcome.unwrap_or(SENTINEL))
|
|
}
|
|
|
|
fn call(
|
|
&mut self,
|
|
memory: &mut [u8],
|
|
flags: CallFlags,
|
|
call_type: CallType,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
self.charge_gas(call_type.cost())?;
|
|
let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
|
|
let input = self.input_data.as_ref().ok_or(Error::<E::T>::InputForwarded)?;
|
|
charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?;
|
|
input.clone()
|
|
} else if flags.contains(CallFlags::FORWARD_INPUT) {
|
|
self.input_data.take().ok_or(Error::<E::T>::InputForwarded)?
|
|
} else {
|
|
self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?;
|
|
self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?
|
|
};
|
|
|
|
let call_outcome = match call_type {
|
|
CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => {
|
|
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
self.read_sandbox_memory_as(memory, callee_ptr)?;
|
|
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
|
|
BalanceOf::<<E as Ext>::T>::zero()
|
|
} else {
|
|
self.read_sandbox_memory_as(memory, deposit_ptr)?
|
|
};
|
|
let value: BalanceOf<<E as Ext>::T> =
|
|
self.read_sandbox_memory_as(memory, value_ptr)?;
|
|
if value > 0u32.into() {
|
|
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
|
}
|
|
self.ext.call(
|
|
weight,
|
|
deposit_limit,
|
|
callee,
|
|
value,
|
|
input_data,
|
|
flags.contains(CallFlags::ALLOW_REENTRY),
|
|
)
|
|
},
|
|
CallType::DelegateCall { code_hash_ptr } => {
|
|
if flags.contains(CallFlags::ALLOW_REENTRY) {
|
|
return Err(Error::<E::T>::InvalidCallFlags.into())
|
|
}
|
|
let code_hash = self.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
|
self.ext.delegate_call(code_hash, input_data)
|
|
},
|
|
};
|
|
|
|
// `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to
|
|
// a halt anyways without anymore code being executed.
|
|
if flags.contains(CallFlags::TAIL_CALL) {
|
|
if let Ok(return_value) = call_outcome {
|
|
return Err(TrapReason::Return(ReturnData {
|
|
flags: return_value.flags.bits(),
|
|
data: return_value.data,
|
|
}))
|
|
}
|
|
}
|
|
|
|
if let Ok(output) = &call_outcome {
|
|
self.write_sandbox_output(
|
|
memory,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
&output.data,
|
|
true,
|
|
|len| Some(RuntimeCosts::CopyToContract(len)),
|
|
)?;
|
|
}
|
|
Ok(Runtime::<E>::exec_into_return_code(call_outcome)?)
|
|
}
|
|
|
|
fn instantiate(
|
|
&mut self,
|
|
memory: &mut [u8],
|
|
code_hash_ptr: u32,
|
|
weight: Weight,
|
|
deposit_ptr: u32,
|
|
value_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
address_ptr: u32,
|
|
address_len_ptr: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
salt_ptr: u32,
|
|
salt_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
|
|
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
|
|
BalanceOf::<<E as Ext>::T>::zero()
|
|
} else {
|
|
self.read_sandbox_memory_as(memory, deposit_ptr)?
|
|
};
|
|
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(memory, value_ptr)?;
|
|
if value > 0u32.into() {
|
|
self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?;
|
|
}
|
|
let code_hash: CodeHash<<E as Ext>::T> =
|
|
self.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
|
let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?;
|
|
let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?;
|
|
let instantiate_outcome =
|
|
self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt);
|
|
if let Ok((address, output)) = &instantiate_outcome {
|
|
if !output.flags.contains(ReturnFlags::REVERT) {
|
|
self.write_sandbox_output(
|
|
memory,
|
|
address_ptr,
|
|
address_len_ptr,
|
|
&address.encode(),
|
|
true,
|
|
already_charged,
|
|
)?;
|
|
}
|
|
self.write_sandbox_output(
|
|
memory,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
&output.data,
|
|
true,
|
|
|len| Some(RuntimeCosts::CopyToContract(len)),
|
|
)?;
|
|
}
|
|
Ok(Runtime::<E>::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?)
|
|
}
|
|
|
|
fn terminate(&mut self, memory: &[u8], beneficiary_ptr: u32) -> Result<(), TrapReason> {
|
|
self.charge_gas(RuntimeCosts::Terminate)?;
|
|
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
self.read_sandbox_memory_as(memory, beneficiary_ptr)?;
|
|
self.ext.terminate(&beneficiary)?;
|
|
Err(TrapReason::Termination)
|
|
}
|
|
}
|
|
|
|
// This is the API exposed to contracts.
|
|
//
|
|
// # Note
|
|
//
|
|
// Any input that leads to a out of bound error (reading or writing) or failing to decode
|
|
// data passed to the supervisor will lead to a trap. This is not documented explicitly
|
|
// for every function.
|
|
#[define_env(doc)]
|
|
pub mod env {
|
|
/// Set the value at the given key in the contract storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::set_storage`]
|
|
#[prefixed_alias]
|
|
fn set_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
value_ptr: u32,
|
|
value_len: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
|
}
|
|
|
|
/// Set the value at the given key in the contract storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::set_storage_v1`]
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn set_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
value_ptr: u32,
|
|
value_len: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
ctx.set_storage(memory, KeyType::Fix, key_ptr, value_ptr, value_len)
|
|
}
|
|
|
|
/// Set the value at the given key in the contract storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::set_storage_v2`]
|
|
#[version(2)]
|
|
#[prefixed_alias]
|
|
fn set_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
key_len: u32,
|
|
value_ptr: u32,
|
|
value_len: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
ctx.set_storage(memory, KeyType::Var(key_len), key_ptr, value_ptr, value_len)
|
|
}
|
|
|
|
/// Clear the value at the given key in the contract storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::clear_storage`]
|
|
#[prefixed_alias]
|
|
fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ())
|
|
}
|
|
|
|
/// Clear the value at the given key in the contract storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::clear_storage_v1`]
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn clear_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result<u32, TrapReason> {
|
|
ctx.clear_storage(memory, KeyType::Var(key_len), key_ptr)
|
|
}
|
|
|
|
/// Retrieve the value under the given key from storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::get_storage`]
|
|
#[prefixed_alias]
|
|
fn get_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.get_storage(memory, KeyType::Fix, key_ptr, out_ptr, out_len_ptr)
|
|
}
|
|
|
|
/// Retrieve the value under the given key from storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::get_storage_v1`]
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn get_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
key_len: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.get_storage(memory, KeyType::Var(key_len), key_ptr, out_ptr, out_len_ptr)
|
|
}
|
|
|
|
/// Checks whether there is a value stored under the given key.
|
|
/// See [`pallet_contracts_uapi::HostFn::contains_storage`]
|
|
#[prefixed_alias]
|
|
fn contains_storage(ctx: _, memory: _, key_ptr: u32) -> Result<u32, TrapReason> {
|
|
ctx.contains_storage(memory, KeyType::Fix, key_ptr)
|
|
}
|
|
|
|
/// Checks whether there is a value stored under the given key.
|
|
/// See [`pallet_contracts_uapi::HostFn::contains_storage_v1`]
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn contains_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result<u32, TrapReason> {
|
|
ctx.contains_storage(memory, KeyType::Var(key_len), key_ptr)
|
|
}
|
|
|
|
/// Retrieve and remove the value under the given key from storage.
|
|
/// See [`pallet_contracts_uapi::HostFn::take_storage`]
|
|
#[prefixed_alias]
|
|
fn take_storage(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
key_len: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?;
|
|
ensure!(
|
|
key_len <= <<E as Ext>::T as Config>::MaxStorageKeyLen::get(),
|
|
Error::<E::T>::DecodingFailed
|
|
);
|
|
let key = ctx.read_sandbox_memory(memory, key_ptr, key_len)?;
|
|
if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage(
|
|
&Key::<E::T>::try_from_var(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
|
|
None,
|
|
true,
|
|
)? {
|
|
ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32));
|
|
ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &value, false, already_charged)?;
|
|
Ok(ReturnErrorCode::Success)
|
|
} else {
|
|
ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(0));
|
|
Ok(ReturnErrorCode::KeyNotFound)
|
|
}
|
|
}
|
|
|
|
/// Transfer some value to another account.
|
|
/// See [`pallet_contracts_uapi::HostFn::transfer`].
|
|
#[prefixed_alias]
|
|
fn transfer(
|
|
ctx: _,
|
|
memory: _,
|
|
account_ptr: u32,
|
|
_account_len: u32,
|
|
value_ptr: u32,
|
|
_value_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Transfer)?;
|
|
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
ctx.read_sandbox_memory_as(memory, account_ptr)?;
|
|
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(memory, value_ptr)?;
|
|
let result = ctx.ext.transfer(&callee, value);
|
|
match result {
|
|
Ok(()) => Ok(ReturnErrorCode::Success),
|
|
Err(err) => {
|
|
let code = Runtime::<E>::err_into_return_code(err)?;
|
|
Ok(code)
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Make a call to another contract.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The values `_callee_len` and `_value_len` are ignored because the encoded sizes of those
|
|
/// types are fixed through [`codec::MaxEncodedLen`]. The fields exist for backwards
|
|
/// compatibility. Consider switching to the newest version of this function.
|
|
#[prefixed_alias]
|
|
fn call(
|
|
ctx: _,
|
|
memory: _,
|
|
callee_ptr: u32,
|
|
_callee_len: u32,
|
|
gas: u64,
|
|
value_ptr: u32,
|
|
_value_len: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.call(
|
|
memory,
|
|
CallFlags::ALLOW_REENTRY,
|
|
CallType::Call {
|
|
callee_ptr,
|
|
value_ptr,
|
|
deposit_ptr: SENTINEL,
|
|
weight: Weight::from_parts(gas, 0),
|
|
},
|
|
input_data_ptr,
|
|
input_data_len,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
)
|
|
}
|
|
|
|
/// Make a call to another contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::call_v1`].
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn call(
|
|
ctx: _,
|
|
memory: _,
|
|
flags: u32,
|
|
callee_ptr: u32,
|
|
gas: u64,
|
|
value_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.call(
|
|
memory,
|
|
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
|
|
CallType::Call {
|
|
callee_ptr,
|
|
value_ptr,
|
|
deposit_ptr: SENTINEL,
|
|
weight: Weight::from_parts(gas, 0),
|
|
},
|
|
input_data_ptr,
|
|
input_data_len,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
)
|
|
}
|
|
|
|
/// Make a call to another contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::call_v2`].
|
|
#[version(2)]
|
|
fn call(
|
|
ctx: _,
|
|
memory: _,
|
|
flags: u32,
|
|
callee_ptr: u32,
|
|
ref_time_limit: u64,
|
|
proof_size_limit: u64,
|
|
deposit_ptr: u32,
|
|
value_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.call(
|
|
memory,
|
|
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
|
|
CallType::Call {
|
|
callee_ptr,
|
|
value_ptr,
|
|
deposit_ptr,
|
|
weight: Weight::from_parts(ref_time_limit, proof_size_limit),
|
|
},
|
|
input_data_ptr,
|
|
input_data_len,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
)
|
|
}
|
|
|
|
/// Execute code in the context (storage, caller, value) of the current contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::delegate_call`].
|
|
#[prefixed_alias]
|
|
fn delegate_call(
|
|
ctx: _,
|
|
memory: _,
|
|
flags: u32,
|
|
code_hash_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.call(
|
|
memory,
|
|
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
|
|
CallType::DelegateCall { code_hash_ptr },
|
|
input_data_ptr,
|
|
input_data_len,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
)
|
|
}
|
|
|
|
/// Instantiate a contract with the specified code hash.
|
|
/// See [`pallet_contracts_uapi::HostFn::instantiate`].
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes
|
|
/// of those types are fixed through [`codec::MaxEncodedLen`]. The fields exist
|
|
/// for backwards compatibility. Consider switching to the newest version of this function.
|
|
#[prefixed_alias]
|
|
fn instantiate(
|
|
ctx: _,
|
|
memory: _,
|
|
code_hash_ptr: u32,
|
|
_code_hash_len: u32,
|
|
gas: u64,
|
|
value_ptr: u32,
|
|
_value_len: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
address_ptr: u32,
|
|
address_len_ptr: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
salt_ptr: u32,
|
|
salt_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.instantiate(
|
|
memory,
|
|
code_hash_ptr,
|
|
Weight::from_parts(gas, 0),
|
|
SENTINEL,
|
|
value_ptr,
|
|
input_data_ptr,
|
|
input_data_len,
|
|
address_ptr,
|
|
address_len_ptr,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
salt_ptr,
|
|
salt_len,
|
|
)
|
|
}
|
|
|
|
/// Instantiate a contract with the specified code hash.
|
|
/// See [`pallet_contracts_uapi::HostFn::instantiate_v1`].
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn instantiate(
|
|
ctx: _,
|
|
memory: _,
|
|
code_hash_ptr: u32,
|
|
gas: u64,
|
|
value_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
address_ptr: u32,
|
|
address_len_ptr: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
salt_ptr: u32,
|
|
salt_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.instantiate(
|
|
memory,
|
|
code_hash_ptr,
|
|
Weight::from_parts(gas, 0),
|
|
SENTINEL,
|
|
value_ptr,
|
|
input_data_ptr,
|
|
input_data_len,
|
|
address_ptr,
|
|
address_len_ptr,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
salt_ptr,
|
|
salt_len,
|
|
)
|
|
}
|
|
|
|
/// Instantiate a contract with the specified code hash.
|
|
/// See [`pallet_contracts_uapi::HostFn::instantiate_v2`].
|
|
#[version(2)]
|
|
fn instantiate(
|
|
ctx: _,
|
|
memory: _,
|
|
code_hash_ptr: u32,
|
|
ref_time_limit: u64,
|
|
proof_size_limit: u64,
|
|
deposit_ptr: u32,
|
|
value_ptr: u32,
|
|
input_data_ptr: u32,
|
|
input_data_len: u32,
|
|
address_ptr: u32,
|
|
address_len_ptr: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
salt_ptr: u32,
|
|
salt_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.instantiate(
|
|
memory,
|
|
code_hash_ptr,
|
|
Weight::from_parts(ref_time_limit, proof_size_limit),
|
|
deposit_ptr,
|
|
value_ptr,
|
|
input_data_ptr,
|
|
input_data_len,
|
|
address_ptr,
|
|
address_len_ptr,
|
|
output_ptr,
|
|
output_len_ptr,
|
|
salt_ptr,
|
|
salt_len,
|
|
)
|
|
}
|
|
|
|
/// Remove the calling account and transfer remaining balance.
|
|
/// See [`pallet_contracts_uapi::HostFn::terminate`].
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The value `_beneficiary_len` is ignored because the encoded sizes
|
|
/// this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards
|
|
/// compatibility. Consider switching to the newest version of this function.
|
|
#[prefixed_alias]
|
|
fn terminate(
|
|
ctx: _,
|
|
memory: _,
|
|
beneficiary_ptr: u32,
|
|
_beneficiary_len: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.terminate(memory, beneficiary_ptr)
|
|
}
|
|
|
|
/// Remove the calling account and transfer remaining **free** balance.
|
|
/// See [`pallet_contracts_uapi::HostFn::terminate_v1`].
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
fn terminate(ctx: _, memory: _, beneficiary_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.terminate(memory, beneficiary_ptr)
|
|
}
|
|
|
|
/// Stores the input passed by the caller into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::input`].
|
|
#[prefixed_alias]
|
|
fn input(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::InputBase)?;
|
|
if let Some(input) = ctx.input_data.take() {
|
|
ctx.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| {
|
|
Some(RuntimeCosts::CopyToContract(len))
|
|
})?;
|
|
ctx.input_data = Some(input);
|
|
Ok(())
|
|
} else {
|
|
Err(Error::<E::T>::InputForwarded.into())
|
|
}
|
|
}
|
|
|
|
/// Cease contract execution and save a data buffer as a result of the execution.
|
|
/// See [`pallet_contracts_uapi::HostFn::return_value`].
|
|
fn seal_return(
|
|
ctx: _,
|
|
memory: _,
|
|
flags: u32,
|
|
data_ptr: u32,
|
|
data_len: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Return(data_len))?;
|
|
Err(TrapReason::Return(ReturnData {
|
|
flags,
|
|
data: ctx.read_sandbox_memory(memory, data_ptr, data_len)?,
|
|
}))
|
|
}
|
|
|
|
/// Stores the address of the caller into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::caller`].
|
|
#[prefixed_alias]
|
|
fn caller(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Caller)?;
|
|
let caller = ctx.ext.caller().account_id()?.clone();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&caller.encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Checks whether a specified address belongs to a contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::is_contract`].
|
|
#[prefixed_alias]
|
|
fn is_contract(ctx: _, memory: _, account_ptr: u32) -> Result<u32, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::IsContract)?;
|
|
let address: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
ctx.read_sandbox_memory_as(memory, account_ptr)?;
|
|
|
|
Ok(ctx.ext.is_contract(&address) as u32)
|
|
}
|
|
|
|
/// Retrieve the code hash for a specified contract address.
|
|
/// See [`pallet_contracts_uapi::HostFn::code_hash`].
|
|
#[prefixed_alias]
|
|
fn code_hash(
|
|
ctx: _,
|
|
memory: _,
|
|
account_ptr: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::CodeHash)?;
|
|
let address: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
ctx.read_sandbox_memory_as(memory, account_ptr)?;
|
|
if let Some(value) = ctx.ext.code_hash(&address) {
|
|
ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&value.encode(),
|
|
false,
|
|
already_charged,
|
|
)?;
|
|
Ok(ReturnErrorCode::Success)
|
|
} else {
|
|
Ok(ReturnErrorCode::KeyNotFound)
|
|
}
|
|
}
|
|
|
|
/// Retrieve the code hash of the currently executing contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::own_code_hash`].
|
|
#[prefixed_alias]
|
|
fn own_code_hash(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::OwnCodeHash)?;
|
|
let code_hash_encoded = &ctx.ext.own_code_hash().encode();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
code_hash_encoded,
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Checks whether the caller of the current contract is the origin of the whole call stack.
|
|
/// See [`pallet_contracts_uapi::HostFn::caller_is_origin`].
|
|
#[prefixed_alias]
|
|
fn caller_is_origin(ctx: _, _memory: _) -> Result<u32, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::CallerIsOrigin)?;
|
|
Ok(ctx.ext.caller_is_origin() as u32)
|
|
}
|
|
|
|
/// Checks whether the caller of the current contract is root.
|
|
/// See [`pallet_contracts_uapi::HostFn::caller_is_root`].
|
|
fn caller_is_root(ctx: _, _memory: _) -> Result<u32, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::CallerIsRoot)?;
|
|
Ok(ctx.ext.caller_is_root() as u32)
|
|
}
|
|
|
|
/// Stores the address of the current contract into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::address`].
|
|
#[prefixed_alias]
|
|
fn address(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Address)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.address().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the price for the specified amount of gas into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::weight_to_fee`].
|
|
#[prefixed_alias]
|
|
fn weight_to_fee(
|
|
ctx: _,
|
|
memory: _,
|
|
gas: u64,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
let gas = Weight::from_parts(gas, 0);
|
|
ctx.charge_gas(RuntimeCosts::WeightToFee)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.get_weight_price(gas).encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the price for the specified amount of weight into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::weight_to_fee_v1`].
|
|
#[version(1)]
|
|
#[unstable]
|
|
fn weight_to_fee(
|
|
ctx: _,
|
|
memory: _,
|
|
ref_time_limit: u64,
|
|
proof_size_limit: u64,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
|
|
ctx.charge_gas(RuntimeCosts::WeightToFee)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.get_weight_price(weight).encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the weight left into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::gas_left`].
|
|
#[prefixed_alias]
|
|
fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::GasLeft)?;
|
|
let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
gas_left,
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the amount of weight left into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::gas_left_v1`].
|
|
#[version(1)]
|
|
#[unstable]
|
|
fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::GasLeft)?;
|
|
let gas_left = &ctx.ext.gas_meter().gas_left().encode();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
gas_left,
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the *free* balance of the current account into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::balance`].
|
|
#[prefixed_alias]
|
|
fn balance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Balance)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.balance().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the value transferred along with this call/instantiate into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::value_transferred`].
|
|
#[prefixed_alias]
|
|
fn value_transferred(
|
|
ctx: _,
|
|
memory: _,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::ValueTransferred)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.value_transferred().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores a random number for the current block and the given subject into the supplied buffer.
|
|
///
|
|
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
|
/// `out_len_ptr` must point to a u32 value that describes the available space at
|
|
/// `out_ptr`. This call overwrites it with the size of the value. If the available
|
|
/// space at `out_ptr` is less than the size of the value a trap is triggered.
|
|
///
|
|
/// The data is encoded as `T::Hash`.
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn random(
|
|
ctx: _,
|
|
memory: _,
|
|
subject_ptr: u32,
|
|
subject_len: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Random)?;
|
|
if subject_len > ctx.ext.schedule().limits.subject_len {
|
|
return Err(Error::<E::T>::RandomSubjectTooLong.into())
|
|
}
|
|
let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.random(&subject_buf).0.encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores a random number for the current block and the given subject into the supplied buffer.
|
|
///
|
|
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
|
/// `out_len_ptr` must point to a u32 value that describes the available space at
|
|
/// `out_ptr`. This call overwrites it with the size of the value. If the available
|
|
/// space at `out_ptr` is less than the size of the value a trap is triggered.
|
|
///
|
|
/// The data is encoded as (T::Hash, frame_system::pallet_prelude::BlockNumberFor::<T>).
|
|
///
|
|
/// # Changes from v0
|
|
///
|
|
/// In addition to the seed it returns the block number since which it was determinable
|
|
/// by chain observers.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The returned seed should only be used to distinguish commitments made before
|
|
/// the returned block number. If the block number is too early (i.e. commitments were
|
|
/// made afterwards), then ensure no further commitments may be made and repeatedly
|
|
/// call this on later blocks until the block number returned is later than the latest
|
|
/// commitment.
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn random(
|
|
ctx: _,
|
|
memory: _,
|
|
subject_ptr: u32,
|
|
subject_len: u32,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Random)?;
|
|
if subject_len > ctx.ext.schedule().limits.subject_len {
|
|
return Err(Error::<E::T>::RandomSubjectTooLong.into())
|
|
}
|
|
let subject_buf = ctx.read_sandbox_memory(memory, subject_ptr, subject_len)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.random(&subject_buf).encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Load the latest block timestamp into the supplied buffer
|
|
/// See [`pallet_contracts_uapi::HostFn::now`].
|
|
#[prefixed_alias]
|
|
fn now(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Now)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.now().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::minimum_balance`].
|
|
#[prefixed_alias]
|
|
fn minimum_balance(
|
|
ctx: _,
|
|
memory: _,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::MinimumBalance)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.minimum_balance().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Stores the tombstone deposit into the supplied buffer.
|
|
///
|
|
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
|
/// `out_len_ptr` must point to a u32 value that describes the available space at
|
|
/// `out_ptr`. This call overwrites it with the size of the value. If the available
|
|
/// space at `out_ptr` is less than the size of the value a trap is triggered.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// There is no longer a tombstone deposit. This function always returns `0`.
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn tombstone_deposit(
|
|
ctx: _,
|
|
memory: _,
|
|
out_ptr: u32,
|
|
out_len_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Balance)?;
|
|
let deposit = <BalanceOf<E::T>>::zero().encode();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&deposit,
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Was used to restore the given destination contract sacrificing the caller.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The state rent functionality was removed. This is stub only exists for
|
|
/// backwards compatibility
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn restore_to(
|
|
ctx: _,
|
|
memory: _,
|
|
_dest_ptr: u32,
|
|
_dest_len: u32,
|
|
_code_hash_ptr: u32,
|
|
_code_hash_len: u32,
|
|
_rent_allowance_ptr: u32,
|
|
_rent_allowance_len: u32,
|
|
_delta_ptr: u32,
|
|
_delta_count: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::DebugMessage(0))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Was used to restore the given destination contract sacrificing the caller.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The state rent functionality was removed. This is stub only exists for
|
|
/// backwards compatibility
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn restore_to(
|
|
ctx: _,
|
|
memory: _,
|
|
_dest_ptr: u32,
|
|
_code_hash_ptr: u32,
|
|
_rent_allowance_ptr: u32,
|
|
_delta_ptr: u32,
|
|
_delta_count: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::DebugMessage(0))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Was used to set rent allowance of the contract.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The state rent functionality was removed. This is stub only exists for
|
|
/// backwards compatibility.
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn set_rent_allowance(
|
|
ctx: _,
|
|
memory: _,
|
|
_value_ptr: u32,
|
|
_value_len: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::DebugMessage(0))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Was used to set rent allowance of the contract.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The state rent functionality was removed. This is stub only exists for
|
|
/// backwards compatibility.
|
|
#[version(1)]
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn set_rent_allowance(ctx: _, _memory: _, _value_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::DebugMessage(0))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Was used to store the rent allowance into the supplied buffer.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// The state rent functionality was removed. This is stub only exists for
|
|
/// backwards compatibility.
|
|
#[prefixed_alias]
|
|
#[deprecated]
|
|
fn rent_allowance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Balance)?;
|
|
let rent_allowance = <BalanceOf<E::T>>::max_value().encode();
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&rent_allowance,
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Deposit a contract event with the data buffer and optional list of topics.
|
|
/// See [pallet_contracts_uapi::HostFn::deposit_event]
|
|
#[prefixed_alias]
|
|
fn deposit_event(
|
|
ctx: _,
|
|
memory: _,
|
|
topics_ptr: u32,
|
|
topics_len: u32,
|
|
data_ptr: u32,
|
|
data_len: u32,
|
|
) -> Result<(), TrapReason> {
|
|
let num_topic = topics_len
|
|
.checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32)
|
|
.ok_or("Zero sized topics are not allowed")?;
|
|
ctx.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?;
|
|
if data_len > ctx.ext.max_value_size() {
|
|
return Err(Error::<E::T>::ValueTooLarge.into())
|
|
}
|
|
|
|
let topics: Vec<TopicOf<<E as Ext>::T>> = match topics_len {
|
|
0 => Vec::new(),
|
|
_ => ctx.read_sandbox_memory_as_unbounded(memory, topics_ptr, topics_len)?,
|
|
};
|
|
|
|
// If there are more than `event_topics`, then trap.
|
|
if topics.len() > ctx.ext.schedule().limits.event_topics as usize {
|
|
return Err(Error::<E::T>::TooManyTopics.into())
|
|
}
|
|
|
|
let event_data = ctx.read_sandbox_memory(memory, data_ptr, data_len)?;
|
|
|
|
ctx.ext.deposit_event(topics, event_data);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Stores the current block number of the current contract into the supplied buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::block_number`].
|
|
#[prefixed_alias]
|
|
fn block_number(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::BlockNumber)?;
|
|
Ok(ctx.write_sandbox_output(
|
|
memory,
|
|
out_ptr,
|
|
out_len_ptr,
|
|
&ctx.ext.block_number().encode(),
|
|
false,
|
|
already_charged,
|
|
)?)
|
|
}
|
|
|
|
/// Computes the SHA2 256-bit hash on the given input buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::hash_sha2_256`].
|
|
#[prefixed_alias]
|
|
fn hash_sha2_256(
|
|
ctx: _,
|
|
memory: _,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::HashSha256(input_len))?;
|
|
Ok(ctx.compute_hash_on_intermediate_buffer(
|
|
memory, sha2_256, input_ptr, input_len, output_ptr,
|
|
)?)
|
|
}
|
|
|
|
/// Computes the KECCAK 256-bit hash on the given input buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::hash_keccak_256`].
|
|
#[prefixed_alias]
|
|
fn hash_keccak_256(
|
|
ctx: _,
|
|
memory: _,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::HashKeccak256(input_len))?;
|
|
Ok(ctx.compute_hash_on_intermediate_buffer(
|
|
memory, keccak_256, input_ptr, input_len, output_ptr,
|
|
)?)
|
|
}
|
|
|
|
/// Computes the BLAKE2 256-bit hash on the given input buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::hash_blake2_256`].
|
|
#[prefixed_alias]
|
|
fn hash_blake2_256(
|
|
ctx: _,
|
|
memory: _,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::HashBlake256(input_len))?;
|
|
Ok(ctx.compute_hash_on_intermediate_buffer(
|
|
memory, blake2_256, input_ptr, input_len, output_ptr,
|
|
)?)
|
|
}
|
|
|
|
/// Computes the BLAKE2 128-bit hash on the given input buffer.
|
|
/// See [`pallet_contracts_uapi::HostFn::hash_blake2_128`].
|
|
#[prefixed_alias]
|
|
fn hash_blake2_128(
|
|
ctx: _,
|
|
memory: _,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::HashBlake128(input_len))?;
|
|
Ok(ctx.compute_hash_on_intermediate_buffer(
|
|
memory, blake2_128, input_ptr, input_len, output_ptr,
|
|
)?)
|
|
}
|
|
|
|
/// Call into the chain extension provided by the chain if any.
|
|
/// See [`pallet_contracts_uapi::HostFn::call_chain_extension`].
|
|
#[prefixed_alias]
|
|
fn call_chain_extension(
|
|
ctx: _,
|
|
memory: _,
|
|
id: u32,
|
|
input_ptr: u32,
|
|
input_len: u32,
|
|
output_ptr: u32,
|
|
output_len_ptr: u32,
|
|
) -> Result<u32, TrapReason> {
|
|
use crate::chain_extension::{ChainExtension, Environment, RetVal};
|
|
if !<E::T as Config>::ChainExtension::enabled() {
|
|
return Err(Error::<E::T>::NoChainExtension.into())
|
|
}
|
|
let mut chain_extension = ctx.chain_extension.take().expect(
|
|
"Constructor initializes with `Some`. This is the only place where it is set to `None`.\
|
|
It is always reset to `Some` afterwards. qed"
|
|
);
|
|
let env =
|
|
Environment::new(ctx, memory, id, input_ptr, input_len, output_ptr, output_len_ptr);
|
|
let ret = match chain_extension.call(env)? {
|
|
RetVal::Converging(val) => Ok(val),
|
|
RetVal::Diverging { flags, data } =>
|
|
Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })),
|
|
};
|
|
ctx.chain_extension = Some(chain_extension);
|
|
ret
|
|
}
|
|
|
|
/// Emit a custom debug message.
|
|
///
|
|
/// No newlines are added to the supplied message.
|
|
/// Specifying invalid UTF-8 just drops the message with no trap.
|
|
///
|
|
/// This is a no-op if debug message recording is disabled which is always the case
|
|
/// when the code is executing on-chain. The message is interpreted as UTF-8 and
|
|
/// appended to the debug buffer which is then supplied to the calling RPC client.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// Even though no action is taken when debug message recording is disabled there is still
|
|
/// a non trivial overhead (and weight cost) associated with calling this function. Contract
|
|
/// languages should remove calls to this function (either at runtime or compile time) when
|
|
/// not being executed as an RPC. For example, they could allow users to disable logging
|
|
/// through compile time flags (cargo features) for on-chain deployment. Additionally, the
|
|
/// return value of this function can be cached in order to prevent further calls at runtime.
|
|
#[prefixed_alias]
|
|
fn debug_message(
|
|
ctx: _,
|
|
memory: _,
|
|
str_ptr: u32,
|
|
str_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
let str_len = str_len.min(DebugBufferVec::<E::T>::bound() as u32);
|
|
ctx.charge_gas(RuntimeCosts::DebugMessage(str_len))?;
|
|
if ctx.ext.append_debug_buffer("") {
|
|
let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?;
|
|
if let Some(msg) = core::str::from_utf8(&data).ok() {
|
|
ctx.ext.append_debug_buffer(msg);
|
|
}
|
|
}
|
|
Ok(ReturnErrorCode::Success)
|
|
}
|
|
|
|
/// Call some dispatchable of the runtime.
|
|
/// See [`frame_support::traits::call_runtime`].
|
|
fn call_runtime(
|
|
ctx: _,
|
|
memory: _,
|
|
call_ptr: u32,
|
|
call_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
use frame_support::dispatch::GetDispatchInfo;
|
|
ctx.charge_gas(RuntimeCosts::CopyFromContract(call_len))?;
|
|
let call: <E::T as Config>::RuntimeCall =
|
|
ctx.read_sandbox_memory_as_unbounded(memory, call_ptr, call_len)?;
|
|
ctx.call_dispatchable::<CallRuntimeFailed>(
|
|
call.get_dispatch_info(),
|
|
RuntimeCosts::CallRuntime,
|
|
|ctx| ctx.ext.call_runtime(call),
|
|
)
|
|
}
|
|
|
|
/// Execute an XCM program locally, using the contract's address as the origin.
|
|
/// See [`pallet_contracts_uapi::HostFn::execute_xcm`].
|
|
#[unstable]
|
|
fn xcm_execute(
|
|
ctx: _,
|
|
memory: _,
|
|
msg_ptr: u32,
|
|
msg_len: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
use frame_support::dispatch::DispatchInfo;
|
|
use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo};
|
|
|
|
ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?;
|
|
let message = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?;
|
|
|
|
let execute_weight =
|
|
<<E::T as Config>::Xcm as ExecuteController<_, _>>::WeightInfo::execute_blob();
|
|
let weight = ctx.ext.gas_meter().gas_left().max(execute_weight);
|
|
let dispatch_info = DispatchInfo { weight, ..Default::default() };
|
|
|
|
ctx.call_dispatchable::<XcmExecutionFailed>(
|
|
dispatch_info,
|
|
RuntimeCosts::CallXcmExecute,
|
|
|ctx| {
|
|
let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into();
|
|
let weight_used = <<E::T as Config>::Xcm>::execute_blob(
|
|
origin,
|
|
message,
|
|
weight.saturating_sub(execute_weight),
|
|
)?;
|
|
|
|
Ok(Some(weight_used.saturating_add(execute_weight)).into())
|
|
},
|
|
)
|
|
}
|
|
|
|
/// Send an XCM program from the contract to the specified destination.
|
|
/// See [`pallet_contracts_uapi::HostFn::send_xcm`].
|
|
#[unstable]
|
|
fn xcm_send(
|
|
ctx: _,
|
|
memory: _,
|
|
dest_ptr: u32,
|
|
msg_ptr: u32,
|
|
msg_len: u32,
|
|
output_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
use xcm::VersionedLocation;
|
|
use xcm_builder::{SendController, SendControllerWeightInfo};
|
|
|
|
ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?;
|
|
let dest: VersionedLocation = ctx.read_sandbox_memory_as(memory, dest_ptr)?;
|
|
|
|
let message = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?;
|
|
let weight = <<E::T as Config>::Xcm as SendController<_>>::WeightInfo::send_blob();
|
|
ctx.charge_gas(RuntimeCosts::CallRuntime(weight))?;
|
|
let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into();
|
|
|
|
match <<E::T as Config>::Xcm>::send_blob(origin, dest.into(), message) {
|
|
Ok(message_id) => {
|
|
ctx.write_sandbox_memory(memory, output_ptr, &message_id.encode())?;
|
|
Ok(ReturnErrorCode::Success)
|
|
},
|
|
Err(e) => {
|
|
if ctx.ext.append_debug_buffer("") {
|
|
ctx.ext.append_debug_buffer("seal0::xcm_send failed with: ");
|
|
ctx.ext.append_debug_buffer(e.into());
|
|
};
|
|
Ok(ReturnErrorCode::XcmSendFailed)
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Recovers the ECDSA public key from the given message hash and signature.
|
|
/// See [`pallet_contracts_uapi::HostFn::ecdsa_recover`].
|
|
#[prefixed_alias]
|
|
fn ecdsa_recover(
|
|
ctx: _,
|
|
memory: _,
|
|
signature_ptr: u32,
|
|
message_hash_ptr: u32,
|
|
output_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::EcdsaRecovery)?;
|
|
|
|
let mut signature: [u8; 65] = [0; 65];
|
|
ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?;
|
|
let mut message_hash: [u8; 32] = [0; 32];
|
|
ctx.read_sandbox_memory_into_buf(memory, message_hash_ptr, &mut message_hash)?;
|
|
|
|
let result = ctx.ext.ecdsa_recover(&signature, &message_hash);
|
|
|
|
match result {
|
|
Ok(pub_key) => {
|
|
// Write the recovered compressed ecdsa public key back into the sandboxed output
|
|
// buffer.
|
|
ctx.write_sandbox_memory(memory, output_ptr, pub_key.as_ref())?;
|
|
|
|
Ok(ReturnErrorCode::Success)
|
|
},
|
|
Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed),
|
|
}
|
|
}
|
|
|
|
/// Verify a sr25519 signature
|
|
/// See [`pallet_contracts_uapi::HostFn::sr25519_verify`].
|
|
fn sr25519_verify(
|
|
ctx: _,
|
|
memory: _,
|
|
signature_ptr: u32,
|
|
pub_key_ptr: u32,
|
|
message_len: u32,
|
|
message_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?;
|
|
|
|
let mut signature: [u8; 64] = [0; 64];
|
|
ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?;
|
|
|
|
let mut pub_key: [u8; 32] = [0; 32];
|
|
ctx.read_sandbox_memory_into_buf(memory, pub_key_ptr, &mut pub_key)?;
|
|
|
|
let message: Vec<u8> = ctx.read_sandbox_memory(memory, message_ptr, message_len)?;
|
|
|
|
if ctx.ext.sr25519_verify(&signature, &message, &pub_key) {
|
|
Ok(ReturnErrorCode::Success)
|
|
} else {
|
|
Ok(ReturnErrorCode::Sr25519VerifyFailed)
|
|
}
|
|
}
|
|
|
|
/// Replace the contract code at the specified address with new code.
|
|
/// See [`pallet_contracts_uapi::HostFn::set_code_hash`].
|
|
#[prefixed_alias]
|
|
fn set_code_hash(ctx: _, memory: _, code_hash_ptr: u32) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::SetCodeHash)?;
|
|
let code_hash: CodeHash<<E as Ext>::T> =
|
|
ctx.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
|
match ctx.ext.set_code_hash(code_hash) {
|
|
Err(err) => {
|
|
let code = Runtime::<E>::err_into_return_code(err)?;
|
|
Ok(code)
|
|
},
|
|
Ok(()) => Ok(ReturnErrorCode::Success),
|
|
}
|
|
}
|
|
|
|
/// Calculates Ethereum address from the ECDSA compressed public key and stores
|
|
/// See [`pallet_contracts_uapi::HostFn::ecdsa_to_eth_address`].
|
|
#[prefixed_alias]
|
|
fn ecdsa_to_eth_address(
|
|
ctx: _,
|
|
memory: _,
|
|
key_ptr: u32,
|
|
out_ptr: u32,
|
|
) -> Result<ReturnErrorCode, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?;
|
|
let mut compressed_key: [u8; 33] = [0; 33];
|
|
ctx.read_sandbox_memory_into_buf(memory, key_ptr, &mut compressed_key)?;
|
|
let result = ctx.ext.ecdsa_to_eth_address(&compressed_key);
|
|
match result {
|
|
Ok(eth_address) => {
|
|
ctx.write_sandbox_memory(memory, out_ptr, eth_address.as_ref())?;
|
|
Ok(ReturnErrorCode::Success)
|
|
},
|
|
Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed),
|
|
}
|
|
}
|
|
|
|
/// Returns the number of times the currently executing contract exists on the call stack in
|
|
/// addition to the calling instance.
|
|
/// See [`pallet_contracts_uapi::HostFn::reentrance_count`].
|
|
#[unstable]
|
|
fn reentrance_count(ctx: _, memory: _) -> Result<u32, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::ReentrantCount)?;
|
|
Ok(ctx.ext.reentrance_count())
|
|
}
|
|
|
|
/// Returns the number of times specified contract exists on the call stack. Delegated calls are
|
|
/// not counted as separate calls.
|
|
/// See [`pallet_contracts_uapi::HostFn::account_reentrance_count`].
|
|
#[unstable]
|
|
fn account_reentrance_count(ctx: _, memory: _, account_ptr: u32) -> Result<u32, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::AccountEntranceCount)?;
|
|
let account_id: <<E as Ext>::T as frame_system::Config>::AccountId =
|
|
ctx.read_sandbox_memory_as(memory, account_ptr)?;
|
|
Ok(ctx.ext.account_reentrance_count(&account_id))
|
|
}
|
|
|
|
/// Returns a nonce that is unique per contract instantiation.
|
|
/// See [`pallet_contracts_uapi::HostFn::instantiation_nonce`].
|
|
fn instantiation_nonce(ctx: _, _memory: _) -> Result<u64, TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::InstantiationNonce)?;
|
|
Ok(ctx.ext.nonce())
|
|
}
|
|
|
|
/// Adds a new delegate dependency to the contract.
|
|
/// See [`pallet_contracts_uapi::HostFn::lock_delegate_dependency`].
|
|
fn lock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::LockDelegateDependency)?;
|
|
let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
|
ctx.ext.lock_delegate_dependency(code_hash)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Removes the delegate dependency from the contract.
|
|
/// see [`pallet_contracts_uapi::HostFn::unlock_delegate_dependency`].
|
|
fn unlock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> {
|
|
ctx.charge_gas(RuntimeCosts::UnlockDelegateDependency)?;
|
|
let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?;
|
|
ctx.ext.unlock_delegate_dependency(&code_hash)?;
|
|
Ok(())
|
|
}
|
|
}
|