Newer
Older
// Copyright (C) 2018-2022 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, FixSizedKey, TopicOf, VarSizedKey},
gas::{ChargedAmount, Token},
wasm::env_def::ConvertibleToWasm,
BalanceOf, CodeHash, Config, Error, SENTINEL,
use bitflags::bitflags;
use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen};
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
use sp_core::{crypto::UncheckedFrom, Bytes};
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
use sp_runtime::traits::{Bounded, Zero};
use sp_sandbox::SandboxMemory;
use wasm_instrument::parity_wasm::elements::ValueType;
/// The maximum nesting depth a contract can use when encoding types.
const MAX_DECODE_NESTING: u32 = 256;
/// Type of a storage key.
#[allow(dead_code)]
enum KeyType {
/// Deprecated fix sized key [0;32].
Fix,
/// Variable sized key used in transparent hashing,
/// cannot be larger than MaxStorageKeyLen.
Variable(u32),
}
impl KeyType {
fn len<T: Config>(&self) -> Result<u32, TrapReason> {
match self {
KeyType::Fix => Ok(32u32),
KeyType::Variable(len) => {
ensure!(len <= &<T>::MaxStorageKeyLen::get(), Error::<T>::DecodingFailed);
Ok(*len)
},
}
}
}
/// 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,
/// Deprecated and no longer returned: There is only the minimum balance.
_BelowSubsistenceThreshold = 4,
/// See [`Error::TransferFailed`].
/// Deprecated and no longer returned: Endowment is no longer required.
_EndowmentTooLow = 6,
/// 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 to `seal_debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled = 9,
/// The call dispatched by `seal_call_runtime` was executed but returned an error.
#[cfg(feature = "unstable-interface")]
CallRuntimeReturnedError = 10,
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,
}
impl ConvertibleToWasm for ReturnCode {
const VALUE_TYPE: ValueType = ValueType::I32;
Alexander Theißen
committed
type NativeType = Self;
fn to_typed_value(self) -> sp_sandbox::Value {
sp_sandbox::Value::I32(self as i32)
}
fn from_typed_value(_: sp_sandbox::Value) -> Option<Self> {
debug_assert!(false, "We will never receive a ReturnCode but only send it to wasm.");
None
}
}
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
/// The data passed through when a contract uses `seal_return`.
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
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())
}
}
#[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.
MeteringBlock(u32),
/// 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 },
/// Weight of calling `seal_debug_message`.
DebugMessage,
/// 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.
#[cfg(feature = "unstable-interface")]
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 charged by a chain extension through `seal_call_chain_extension`.
ChainExtension(u64),
/// Weight charged for calling into the runtime.
#[cfg(feature = "unstable-interface")]
CallRuntime(Weight),
/// Weight of calling `seal_set_code_hash`
SetCodeHash,
Sasha Gryaznov
committed
/// Weight of calling `ecdsa_to_eth_address`
EcdsaToEthAddress,
impl RuntimeCosts {
fn token<T>(&self, s: &HostFnWeights<T>) -> RuntimeToken
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
use self::RuntimeCosts::*;
let weight = match *self {
MeteringBlock(amount) => s.gas.saturating_add(amount.into()),
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())),
DebugMessage => s.debug_message,
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())),
#[cfg(feature = "unstable-interface")]
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.return_per_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,
ChainExtension(amount) => amount,
#[cfg(feature = "unstable-interface")]
CallRuntime(weight) => weight,
SetCodeHash => s.set_code_hash,
Sasha Gryaznov
committed
EcdsaToEthAddress => s.ecdsa_to_eth_address,
};
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> Token<T> for RuntimeToken
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
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 {
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/// 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>>,
memory: sp_sandbox::default_executor::Memory,
trap_reason: Option<TrapReason>,
Alexander Theißen
committed
chain_extension: Option<Box<<E::T as Config>::ChainExtension>>,
impl<'a, E> Runtime<'a, E>
where
E: Ext + 'a,
<E::T as frame_system::Config>::AccountId:
UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>,
pub fn new(
ext: &'a mut E,
input_data: Vec<u8>,
memory: sp_sandbox::default_executor::Memory,
) -> Self {
Alexander Theißen
committed
Runtime {
ext,
input_data: Some(input_data),
memory,
trap_reason: None,
chain_extension: Some(Box::new(Default::default())),
}
/// Converts the sandbox result and the runtime state into the execution outcome.
///
/// It evaluates information stored in the `trap_reason` variable of the runtime and
/// bases the outcome on the value if this variable. Only if `trap_reason` is `None`
/// the result of the sandbox is evaluated.
pub fn to_execution_result(
self,
sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
) -> ExecResult {
// If a trap reason is set we base our decision solely on that.
if let Some(trap_reason) = self.trap_reason {
return match trap_reason {
// The trap was the result of the execution `return` host function.
TrapReason::Return(ReturnData { flags, data }) => {
let flags =
ReturnFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?;
Ok(ExecReturnValue { flags, data: Bytes(data) })
TrapReason::Termination =>
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
TrapReason::SupervisorError(error) => return Err(error.into()),
}
}
// Check the exact type of the error.
match sandbox_result {
// No traps were generated. Proceed normally.
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }),
// `Error::Module` is returned only if instantiation or linking failed (i.e.
// wasm binary tried to import a function that is not provided by the host).
// 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(sp_sandbox::Error::Module) => return Err("validation error".into()),
// Any other kind of a trap should result in a failure.
Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
return Err(Error::<E::T>::ContractTrapped.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
}
Alexander Theißen
committed
/// Store the reason for a host function triggered trap.
///
/// This is called by the `define_env` macro in order to store any error returned by
/// the host functions defined through the said macro. It should **not** be called
/// manually.
pub fn set_trap_reason(&mut self, reason: TrapReason) {
self.trap_reason = Some(reason);
}
/// 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.
pub fn read_sandbox_memory(&self, 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.memory
.get(ptr, buf.as_mut_slice())
Alexander Theißen
committed
.map_err(|_| Error::<E::T>::OutOfBounds)?;
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,
ptr: u32,
buf: &mut [u8],
) -> Result<(), DispatchError> {
Alexander Theißen
committed
self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
/// 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,
ptr: u32,
) -> Result<D, DispatchError> {
let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?;
let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..])
.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,
ptr: u32,
len: u32,
) -> Result<D, DispatchError> {
let buf = self.read_sandbox_memory(ptr, len)?;
let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..])
.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,
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;
let len: u32 = self.read_sandbox_memory_as(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.memory
.set(out_ptr, buf)
.and_then(|_| self.memory.set(out_len_ptr, &buf_len.encode()))
.map_err(|_| Error::<E::T>::OutOfBounds)?;
Ok(())
/// 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(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
self.memory.set(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
}
/// 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>(
&mut self,
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.
let input = self.read_sandbox_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(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 set_storage(
&mut self,
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.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
let value = Some(self.read_sandbox_memory(value_ptr, value_len)?);
let write_outcome = match key_type {
KeyType::Fix => self.ext.set_storage(
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
value,
false,
)?,
KeyType::Variable(_) => self.ext.set_storage_transparent(
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
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, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?;
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
let outcome = match key_type {
KeyType::Fix => self.ext.set_storage(
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
None,
false,
)?,
KeyType::Variable(_) => self.ext.set_storage_transparent(
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
None,
false,
)?,
};
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len()));
Ok(outcome.old_len_with_sentinel())
}
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
fn get_storage(
&mut self,
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.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
let outcome = match key_type {
KeyType::Fix => self.ext.get_storage(
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
),
KeyType::Variable(_) => self.ext.get_storage_transparent(
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
),
};
if let Some(value) = outcome {
self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32));
self.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?;
Ok(ReturnCode::Success)
} else {
self.adjust_gas(charged, RuntimeCosts::GetStorage(0));
Ok(ReturnCode::KeyNotFound)
}
}
fn contains_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result<u32, TrapReason> {
let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?;
let key = self.read_sandbox_memory(key_ptr, key_type.len::<E::T>()?)?;
let outcome = match key_type {
KeyType::Fix => self.ext.get_storage_size(
&FixSizedKey::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
),
KeyType::Variable(_) => self.ext.get_storage_size_transparent(
&VarSizedKey::<E::T>::try_from(key).map_err(|_| Error::<E::T>::DecodingFailed)?,
),
};
self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0)));
Ok(outcome.unwrap_or(SENTINEL))
}
fn call(
&mut self,
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))?;
self.read_sandbox_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 =
self.read_sandbox_memory_as(callee_ptr)?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
}
self.ext.call(
gas,
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(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.0,
}
}
if let Ok(output) = &call_outcome {
self.write_sandbox_output(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,
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> {
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?;
}
let code_hash: CodeHash<<E as Ext>::T> = self.read_sandbox_memory_as(code_hash_ptr)?;
let input_data = self.read_sandbox_memory(input_data_ptr, input_data_len)?;
let salt = self.read_sandbox_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(
address_ptr,
address_len_ptr,
&address.encode(),
true,
already_charged,
)?;
}
self.write_sandbox_output(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, 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(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.
// 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.
//
[seal0] gas(ctx, amount: u32) => {
ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?;
Ok(())
// Set the value at the given key in the contract storage.
//
// Equivalent to the newer version of `seal_set_storage` with the exception of the return
// type. Still a valid thing to call when not interested in the return value.
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
},
// Set the value at the given key in the contract storage.
// This version is to be used with a fixed sized storage key. For runtimes supporting transparent
// hashing, please use the newer version of this function.
//
// The value length must not exceed the maximum defined by the contracts module parameters.
// Specifying a `value_len` of zero will store an empty value.
//
// # Parameters
//
// - `key_ptr`: pointer into the linear memory where the location to store the value is placed.
// - `value_ptr`: pointer into the linear memory where the value to set is placed.
// - `value_len`: the length of the value in bytes.
//
// # Return Value
// Returns the size of the pre-existing value at the specified key if any. Otherwise
// `SENTINEL` is returned as a sentinel value.
Sasha Gryaznov
committed
[seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => {
ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len)
},
// Set the value at the given key in the contract storage.
//
// The key and value lengths must not exceed the maximums defined by the contracts module parameters.
// Specifying a `value_len` of zero will store an empty value.
//
// # Parameters
//
// - `key_ptr`: pointer into the linear memory where the location to store the value is placed.
// - `key_len`: the length of the key in bytes.