Skip to content
runtime.rs 40.1 KiB
Newer Older
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
Sergey Pepyakin's avatar
Sergey Pepyakin committed
// This file is part of Substrate.

// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

//! Environment definition of the wasm smart-contract runtime.

use crate::{Schedule, Trait, CodeHash, ComputeDispatchFee, BalanceOf};
use crate::exec::{
	Ext, ExecResult, ExecError, ExecReturnValue, StorageKey, TopicOf, STATUS_SUCCESS,
use crate::gas::{Gas, GasMeter, Token, GasMeterResult, approx_gas_for_balance};
use sp_sandbox;
use frame_system;
use sp_std::{prelude::*, mem, convert::TryInto};
use codec::{Decode, Encode};
use sp_runtime::traits::{Bounded, SaturatedConversion};
use sp_io::hashing::{
	keccak_256,
	blake2_256,
	blake2_128,
	sha2_256,
};
/// The value returned from ext_call and ext_instantiate contract external functions if the call or
/// instantiation traps. This value is chosen as if the execution does not trap, the return value
/// will always be an 8-bit integer, so 0x0100 is the smallest value that could not be returned.
const TRAP_RETURN_CODE: u32 = 0x0100;

Sergey Pepyakin's avatar
Sergey Pepyakin committed
/// Enumerates all possible *special* trap conditions.
///
/// In this runtime traps used not only for signaling about errors but also
/// to just terminate quickly in some cases.
enum SpecialTrap {
	/// Signals that trap was generated in response to call `ext_return` host function.
	/// Signals that trap was generated because the contract exhausted its gas limit.
	OutOfGas,
	/// Signals that a trap was generated in response to a succesful call to the
	/// `ext_terminate` host function.
	Termination,
/// Can only be used for one call.
pub(crate) struct Runtime<'a, E: Ext + 'a> {
Sergey Pepyakin's avatar
Sergey Pepyakin committed
	ext: &'a mut E,
	scratch_buf: Vec<u8>,
	schedule: &'a Schedule,
	memory: sp_sandbox::Memory,
Sergey Pepyakin's avatar
Sergey Pepyakin committed
	gas_meter: &'a mut GasMeter<E::T>,
	special_trap: Option<SpecialTrap>,
}
impl<'a, E: Ext + 'a> Runtime<'a, E> {
Sergey Pepyakin's avatar
Sergey Pepyakin committed
	pub(crate) fn new(
		ext: &'a mut E,
		input_data: Vec<u8>,
		schedule: &'a Schedule,
		memory: sp_sandbox::Memory,
Sergey Pepyakin's avatar
Sergey Pepyakin committed
		gas_meter: &'a mut GasMeter<E::T>,
	) -> Self {
		Runtime {
			ext,
			// Put the input data into the scratch buffer immediately.
			scratch_buf: input_data,
Sergey Pepyakin's avatar
Sergey Pepyakin committed
			memory,
			gas_meter,
			special_trap: None,
		}
	}
}

pub(crate) fn to_execution_result<E: Ext>(
	runtime: Runtime<E>,
	sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
	match runtime.special_trap {
		// The trap was the result of the execution `return` host function.
		Some(SpecialTrap::Return(data)) => {
			return Ok(ExecReturnValue {
				status: STATUS_SUCCESS,
				data,
			})
		},
		Some(SpecialTrap::Termination) => {
			return Ok(ExecReturnValue {
				status: STATUS_SUCCESS,
				data: Vec::new(),
			})
		},
		Some(SpecialTrap::OutOfGas) => {
			return Err(ExecError {
				reason: "ran out of gas during contract execution".into(),
				buffer: runtime.scratch_buf,
			})
		},
		None => (),
	}

	// Check the exact type of the error.
	match sandbox_result {
Sergey Pepyakin's avatar
Sergey Pepyakin committed
		// No traps were generated. Proceed normally.
		Ok(sp_sandbox::ReturnValue::Unit) => {
			let mut buffer = runtime.scratch_buf;
			buffer.clear();
			Ok(ExecReturnValue { status: STATUS_SUCCESS, data: buffer })
		}
		Ok(sp_sandbox::ReturnValue::Value(sp_sandbox::Value::I32(exit_code))) => {
			let status = (exit_code & 0xFF).try_into()
				.expect("exit_code is masked into the range of a u8; qed");
			Ok(ExecReturnValue { status, data: runtime.scratch_buf })
		}
		// This should never happen as the return type of exported functions should have been
		// validated by the code preparation process. However, because panics are really
		// undesirable in the runtime code, we treat this as a trap for now. Eventually, we might
		// want to revisit this.
		Ok(_) => Err(ExecError { reason: "return type error".into(), buffer: runtime.scratch_buf }),
		// `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) =>
			Err(ExecError { reason: "validation error".into(), buffer: runtime.scratch_buf }),
Sergey Pepyakin's avatar
Sergey Pepyakin committed
		// Any other kind of a trap should result in a failure.
		Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) =>
			Err(ExecError { reason: "contract trapped during execution".into(), buffer: runtime.scratch_buf }),
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum RuntimeToken {
	/// Explicit call to the `gas` function. Charge the gas meter
	/// with the value provided.
	Explicit(u32),
	/// The given number of bytes is read from the sandbox memory.
	ReadMemory(u32),
	/// The given number of bytes is written to the sandbox memory.
	WriteMemory(u32),
	/// The given number of bytes is read from the sandbox memory and
	/// is returned as the return data buffer of the call.
	ReturnData(u32),
	/// Dispatch fee calculated by `T::ComputeDispatchFee`.
	ComputedDispatchFee(Gas),
	/// (topic_count, data_bytes): A buffer of the given size is posted as an event indexed with the
	/// given number of topics.
	DepositEvent(u32, u32),
impl<T: Trait> Token<T> for RuntimeToken {
	type Metadata = Schedule;
	fn calculate_amount(&self, metadata: &Schedule) -> Gas {
		use self::RuntimeToken::*;
		let value = match *self {
Gavin Wood's avatar
Gavin Wood committed
			Explicit(amount) => Some(amount.into()),
			ReadMemory(byte_count) => metadata
				.sandbox_data_read_cost
				.checked_mul(byte_count.into()),
			WriteMemory(byte_count) => metadata
				.sandbox_data_write_cost
				.checked_mul(byte_count.into()),
			ReturnData(byte_count) => metadata
				.return_data_per_byte_cost
				.checked_mul(byte_count.into()),
			DepositEvent(topic_count, data_byte_count) => {
				let data_cost = metadata
					.event_data_per_byte_cost
					.checked_mul(data_byte_count.into());

				let topics_cost = metadata
					.event_per_topic_cost
					.checked_mul(topic_count.into());

				data_cost
					.and_then(|data_cost| {
						topics_cost.and_then(|topics_cost| {
							data_cost.checked_add(topics_cost)
						})
					})
					.and_then(|data_and_topics_cost|
						data_and_topics_cost.checked_add(metadata.event_base_cost)
			ComputedDispatchFee(gas) => Some(gas),
Loading full blame...