Newer
Older
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
Alexander Theißen
committed
use crate::{
exec::{ExecError, ExecResult, Ext, Key, TopicOf},
gas::{ChargedAmount, Token},
Sasha Gryaznov
committed
BalanceOf, CodeHash, Config, DebugBufferVec, Error, SENTINEL,
use bitflags::bitflags;
use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen};
Alexander Theißen
committed
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight, RuntimeDebug};
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
use pallet_contracts_proc_macro::define_env;
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
use sp_runtime::traits::{Bounded, Zero};
Alexander Theißen
committed
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,
}
Alexander Theißen
committed
/// 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,
Alexander Theißen
committed
) -> Result<(), LinkerError>;
}
/// Type of a storage key.
#[allow(dead_code)]
enum KeyType {
/// Legacy fix sized key `[u8;32]`.
Fix,
/// Variable sized key used in transparent hashing,
/// cannot be larger than MaxStorageKeyLen.
Var(u32),
}
/// Every error that can be returned to a contract when it calls any of the host functions.
///
/// # Note
///
/// This enum can be extended in the future: New codes can be added but existing codes
/// will not be changed or removed. This means that any contract **must not** exhaustively
/// match return codes. Instead, contracts should prepare for unknown variants and deal with
Alexander Theißen
committed
/// those errors gracefully in order to be forward compatible.
#[repr(u32)]
pub enum ReturnCode {
/// API call successful.
Success = 0,
/// The called function trapped and has its state changes reverted.
/// In this case no output buffer is returned.
CalleeTrapped = 1,
/// The called function ran to completion but decided to revert its state.
/// An output buffer is returned when one was supplied.
CalleeReverted = 2,
/// The passed key does not exist in storage.
KeyNotFound = 3,
/// See [`Error::TransferFailed`].
TransferFailed = 5,
/// No code could be found at the supplied code hash.
CodeNotFound = 7,
/// The contract that was called is no contract (a plain account).
/// The call dispatched by `seal_call_runtime` was executed but returned an error.
Sasha Gryaznov
committed
/// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or
/// ECDSA compressed pubkey conversion into Ethereum address failed (most probably
/// wrong pubkey provided).
EcdsaRecoverFailed = 11,
/// sr25519 signature verification failed.
Sr25519VerifyFailed = 12,
}
impl From<ExecReturnValue> for ReturnCode {
fn from(from: ExecReturnValue) -> Self {
if from.flags.contains(ReturnFlags::REVERT) {
Self::CalleeReverted
} else {
Self::Success
}
}
}
Alexander Theißen
committed
impl From<ReturnCode> for u32 {
fn from(code: ReturnCode) -> u32 {
code as u32
}
}
Alexander Theißen
committed
/// The data passed through when a contract uses `seal_return`.
Alexander Theißen
committed
#[derive(RuntimeDebug)]
Alexander Theißen
committed
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).
Alexander Theißen
committed
#[derive(RuntimeDebug)]
Alexander Theißen
committed
pub enum TrapReason {
/// The supervisor trapped the contract because of an error condition occurred during
/// execution in privileged code.
SupervisorError(DispatchError),
Alexander Theißen
committed
/// Signals that trap was generated in response to call `seal_return` host function.
/// Signals that a trap was generated in response to a successful call to the
Alexander Theißen
committed
/// `seal_terminate` host function.
Alexander Theißen
committed
impl<T: Into<DispatchError>> From<T> for TrapReason {
fn from(from: T) -> Self {
Self::SupervisorError(from.into())
}
}
Alexander Theißen
committed
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)]
/// Charge the gas meter with the cost of a metering block. The charged costs are
/// the supplied cost of the block plus the overhead of the metering itself.
/// 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,
Sasha Gryaznov
committed
/// Weight of calling `seal_is_contract`.
IsContract,
/// Weight of calling `seal_code_hash`.
CodeHash,
/// Weight of calling `seal_own_code_hash`.
OwnCodeHash,
Sasha Gryaznov
committed
/// Weight of calling `seal_caller_is_origin`.
CallerIsOrigin,
/// 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 },
Sasha Gryaznov
committed
/// 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,
Yarik Bratashchuk
committed
/// Weight of calling `seal_delegate_call` for the given input size.
/// 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 of calling `seal_set_code_hash`
SetCodeHash,
Sasha Gryaznov
committed
/// Weight of calling `ecdsa_to_eth_address`
EcdsaToEthAddress,
Alexander Theißen
committed
/// Weight of calling `reentrance_count`
ReentrantCount,
Alexander Theißen
committed
/// Weight of calling `account_reentrance_count`
AccountEntranceCount,
/// Weight of calling `instantiation_nonce`
InstantationNonce,
fn token<T: Config>(&self, s: &HostFnWeights<T>) -> RuntimeToken {
use self::RuntimeCosts::*;
let weight = match *self {
MeteringBlock(amount) => s.gas.saturating_add(Weight::from_parts(amount, 0)),
CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()),
CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()),
Caller => s.caller,
Sasha Gryaznov
committed
IsContract => s.is_contract,
CodeHash => s.code_hash,
OwnCodeHash => s.own_code_hash,
Sasha Gryaznov
committed
CallerIsOrigin => s.caller_is_origin,
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())),
Sasha Gryaznov
committed
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) => weight,
CallRuntime(weight) => weight,
SetCodeHash => s.set_code_hash,
Sasha Gryaznov
committed
EcdsaToEthAddress => s.ecdsa_to_eth_address,
Alexander Theißen
committed
ReentrantCount => s.reentrance_count,
AccountEntranceCount => s.account_reentrance_count,
InstantationNonce => s.instantiation_nonce,
};
RuntimeToken {
#[cfg(test)]
_created_from: *self,
weight,
/// 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) => {{
let token = $costs.token(&$runtime.ext.schedule().host_fn_weights);
$runtime.ext.gas_meter().charge(token)
}};
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
struct RuntimeToken {
#[cfg(test)]
_created_from: RuntimeCosts,
weight: Weight,
}
impl<T: Config> Token<T> for RuntimeToken {
fn weight(&self) -> Weight {
self.weight
}
}
bitflags! {
Yarik Bratashchuk
committed
/// Flags used to change the behaviour of `seal_call` and `seal_delegate_call`.
pub struct CallFlags: u32 {
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/// Forward the input of current function to the callee.
///
/// Supplied input pointers are ignored when set.
///
/// # Note
///
/// A forwarding call will consume the current contracts input. Any attempt to
/// access the input after this call returns will lead to [`Error::InputForwarded`].
/// It does not matter if this is due to calling `seal_input` or trying another
/// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve
/// the input.
const FORWARD_INPUT = 0b0000_0001;
/// Identical to [`Self::FORWARD_INPUT`] but without consuming the input.
///
/// This adds some additional weight costs to the call.
///
/// # Note
///
/// This implies [`Self::FORWARD_INPUT`] and takes precedence when both are set.
const CLONE_INPUT = 0b0000_0010;
/// Do not return from the call but rather return the result of the callee to the
/// callers caller.
///
/// # Note
///
/// This makes the current contract completely transparent to its caller by replacing
/// this contracts potential output by the callee ones. Any code after `seal_call`
/// can be safely considered unreachable.
const TAIL_CALL = 0b0000_0100;
/// Allow the callee to reenter into the current contract.
///
/// Without this flag any reentrancy into the current contract that originates from
/// the callee (or any of its callees) is denied. This includes the first callee:
/// You cannot call into yourself with this flag set.
Yarik Bratashchuk
committed
///
/// # Note
///
/// For `seal_delegate_call` should be always unset, otherwise
/// [`Error::InvalidCallFlags`] is returned.
const ALLOW_REENTRY = 0b0000_1000;
}
}
Yarik Bratashchuk
committed
/// The kind of call that should be performed.
enum CallType {
/// Execute another instantiated contract
Call { callee_ptr: u32, value_ptr: u32, gas: u64 },
/// 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 {
Yarik Bratashchuk
committed
match self {
CallType::Call { .. } => RuntimeCosts::CallBase,
CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase,
Yarik Bratashchuk
committed
}
}
}
/// 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>>,
Alexander Theißen
committed
memory: Option<Memory>,
Alexander Theißen
committed
chain_extension: Option<Box<<E::T as Config>::ChainExtension>>,
impl<'a, E: Ext + 'a> Runtime<'a, E> {
Alexander Theißen
committed
pub fn new(ext: &'a mut E, input_data: Vec<u8>) -> Self {
Alexander Theißen
committed
Runtime {
ext,
input_data: Some(input_data),
Alexander Theißen
committed
memory: None,
Alexander Theißen
committed
chain_extension: Some(Box::new(Default::default())),
}
Alexander Theißen
committed
pub fn memory(&self) -> Option<Memory> {
self.memory
}
pub fn set_memory(&mut self, memory: Memory) {
self.memory = Some(memory);
}
Alexander Theißen
committed
/// 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 TrapReason::*;
match sandbox_result {
Alexander Theißen
committed
// Contract returned from main function -> no data was returned.
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
Alexander Theißen
committed
// Contract either trapped or some host function aborted the execution.
Err(wasmi::Error::Trap(trap)) => {
// If we encoded a reason then it is some abort generated by a host function.
// Otherwise the trap came from the contract.
let reason: TrapReason = trap.downcast().ok_or(Error::<E::T>::ContractTrapped)?;
Alexander Theißen
committed
match reason {
Return(ReturnData { flags, data }) => {
let flags =
ReturnFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?;
Ok(ExecReturnValue { flags, data })
},
Termination =>
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
SupervisorError(error) => return Err(error.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.
Alexander Theißen
committed
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) {
let token = actual_costs.token(&self.ext.schedule().host_fn_weights);
self.ext.gas_meter().adjust_gas(charged, token);
}
/// 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.
Alexander Theißen
committed
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];
Alexander Theißen
committed
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,
Alexander Theißen
committed
memory: &[u8],
ptr: u32,
buf: &mut [u8],
) -> Result<(), DispatchError> {
Alexander Theißen
committed
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,
Alexander Theißen
committed
memory: &[u8],
ptr: u32,
) -> Result<D, DispatchError> {
Alexander Theißen
committed
let ptr = ptr as usize;
let mut bound_checked = memory
.get(ptr..ptr + D::max_encoded_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)
}
/// 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,
Alexander Theißen
committed
memory: &[u8],
ptr: u32,
len: u32,
) -> Result<D, DispatchError> {
Alexander Theißen
committed
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,
Alexander Theißen
committed
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 {
let buf_len = buf.len() as u32;
Alexander Theißen
committed
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)?;
}
Alexander Theißen
committed
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.
Alexander Theißen
committed
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>(
Alexander Theißen
committed
&self,
memory: &mut [u8],
hash_fn: F,
input_ptr: u32,
input_len: u32,
output_ptr: u32,
Alexander Theißen
committed
) -> Result<(), DispatchError>
where
F: FnOnce(&[u8]) -> R,
R: AsRef<[u8]>,
{
// Copy input into supervisor memory.
Alexander Theißen
committed
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.
Alexander Theißen
committed
self.write_sandbox_memory(memory, output_ptr, hash.as_ref())?;
Ok(())
}
/// Fallible conversion of `DispatchError` to `ReturnCode`.
fn err_into_return_code(from: DispatchError) -> Result<ReturnCode, DispatchError> {
use ReturnCode::*;
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),
}
}
/// Fallible conversion of a `ExecResult` to `ReturnCode`.
fn exec_into_return_code(from: ExecResult) -> Result<ReturnCode, 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(ReturnCode::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,
Alexander Theißen
committed
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)?;
Alexander Theißen
committed
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())
}
Alexander Theißen
committed
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,
Alexander Theißen
committed
memory: &mut [u8],
key_type: KeyType,
key_ptr: u32,
out_ptr: u32,
out_len_ptr: u32,
) -> Result<ReturnCode, 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));
Alexander Theißen
committed
self.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
&value,
false,
already_charged,
)?;
Ok(ReturnCode::Success)
} else {
self.adjust_gas(charged, RuntimeCosts::GetStorage(0));
Ok(ReturnCode::KeyNotFound)
}
}
Alexander Theißen
committed
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,
Alexander Theißen
committed
memory: &mut [u8],
flags: CallFlags,
Yarik Bratashchuk
committed
call_type: CallType,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32,
) -> Result<ReturnCode, 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))?;
Alexander Theißen
committed
self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?
Yarik Bratashchuk
committed
let call_outcome = match call_type {
CallType::Call { callee_ptr, value_ptr, gas } => {
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
Alexander Theißen
committed
self.read_sandbox_memory_as(memory, callee_ptr)?;
let value: BalanceOf<<E as Ext>::T> =
self.read_sandbox_memory_as(memory, value_ptr)?;
Yarik Bratashchuk
committed
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
}
self.ext.call(
Weight::from_parts(gas, 0),
Yarik Bratashchuk
committed
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())
}
Alexander Theißen
committed
let code_hash = self.read_sandbox_memory_as(memory, code_hash_ptr)?;
Yarik Bratashchuk
committed
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(),
}
}
if let Ok(output) = &call_outcome {
Alexander Theißen
committed
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,
Alexander Theißen
committed
memory: &mut [u8],
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<ReturnCode, TrapReason> {
let gas = Weight::from_parts(gas, 0);
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
Alexander Theißen
committed
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(memory, value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?;
}
Alexander Theißen
committed
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(gas, code_hash, value, input_data, &salt);
if let Ok((address, output)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) {
self.write_sandbox_output(
Alexander Theißen
committed
memory,
address_ptr,
address_len_ptr,
&address.encode(),
true,
already_charged,
Alexander Theißen
committed
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))?)
}
Alexander Theißen
committed
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 =
Alexander Theißen
committed
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.
Sasha Gryaznov
committed
#[define_env(doc)]
pub mod env {
/// Account for used gas. Traps if gas used is greater than gas limit.
///
/// NOTE: This is a implementation defined call and is NOT a part of the public API.
/// This call is supposed to be called only by instrumentation injected code.
///
Sasha Gryaznov
committed
/// - `amount`: How much gas is used.
Alexander Theißen
committed
fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> {
ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?;