contracts: Allow runtime authors to define a chain extension (#7548)

* Make host functions return TrapReason

This avoids the need to manually store any trap reasons
to the `Runtime` from the host function. This adds the following
benefits:

* It properly composes with the upcoming chain extensions
* Missing to set a trap value is now a compile error

* Add chain extension

The chain extension is a way for the contract author to add new
host functions for contracts to call.

* Add tests for chain extensions

* Fix regression in set_rent.wat fixture

Not all offsets where properly updated when changing the fixtures
for the new salt on instantiate.

* Pre-charge a weight amount based off the specified length

* Improve fn write docs

* Renamed state to phantom

* Fix typo
This commit is contained in:
Alexander Theißen
2021-01-04 12:15:17 +01:00
committed by GitHub
parent e3e651f72c
commit 51c37ecc15
10 changed files with 768 additions and 37 deletions
+1
View File
@@ -734,6 +734,7 @@ impl pallet_contracts::Config for Runtime {
type MaxValueSize = MaxValueSize;
type WeightPrice = pallet_transaction_payment::Module<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
type ChainExtension = ();
}
impl pallet_sudo::Config for Runtime {
@@ -0,0 +1,46 @@
;; Call chain extension by passing through input and output of this contract
(module
(import "seal0" "seal_call_chain_extension"
(func $seal_call_chain_extension (param i32 i32 i32 i32 i32) (result i32))
)
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 16 16))
(func $assert (param i32)
(block $ok
(br_if $ok (get_local 0))
(unreachable)
)
)
;; [0, 4) len of input output
(data (i32.const 0) "\02")
;; [4, 12) buffer for input
;; [12, 16) len of output buffer
(data (i32.const 12) "\02")
;; [16, inf) buffer for output
(func (export "deploy"))
(func (export "call")
(call $seal_input (i32.const 4) (i32.const 0))
;; the chain extension passes through the input and returns it as output
(call $seal_call_chain_extension
(i32.load8_u (i32.const 4)) ;; func_id
(i32.const 4) ;; input_ptr
(i32.load (i32.const 0)) ;; input_len
(i32.const 16) ;; output_ptr
(i32.const 12) ;; output_len_ptr
)
;; the chain extension passes through the func_id
(call $assert (i32.eq (i32.load8_u (i32.const 4))))
(call $seal_return (i32.const 0) (i32.const 16) (i32.load (i32.const 12)))
)
)
@@ -84,11 +84,11 @@
)
(i32.store (i32.const 128) (i32.const 64))
(call $seal_input
(i32.const 104)
(i32.const 100)
(i32.const 132)
(i32.const 128)
)
(call $seal_set_rent_allowance
(i32.const 104)
(i32.const 132)
(i32.load (i32.const 128))
)
)
@@ -0,0 +1,393 @@
// This file is part of Substrate.
// Copyright (C) 2020 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.
//! A mechanism for runtime authors to augment the functionality of contracts.
//!
//! The runtime is able to call into any contract and retrieve the result using
//! [`bare_call`](crate::Module::bare_call). This already allows customization of runtime
//! behaviour by user generated code (contracts). However, often it is more straightforward
//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter
//! one a "chain extension" because it allows the chain to extend the set of functions that are
//! callable by a contract.
//!
//! In order to create a chain extension the runtime author implements the [`ChainExtension`]
//! trait and declares it in this pallet's [configuration Trait](crate::Config). All types
//! required for this endeavour are defined or re-exported in this module. There is an
//! implementation on `()` which can be used to signal that no chain extension is available.
//!
//! # Security
//!
//! The chain author alone is responsible for the security of the chain extension.
//! This includes avoiding the exposure of exploitable functions and charging the
//! appropriate amount of weight. In order to do so benchmarks must be written and the
//! [`charge_weight`](Environment::charge_weight) function must be called **before**
//! carrying out any action that causes the consumption of the chargeable weight.
//! It cannot be overstated how delicate of a process the creation of a chain extension
//! is. Check whether using [`bare_call`](crate::Module::bare_call) suffices for the
//! use case at hand.
//!
//! # Benchmarking
//!
//! The builtin contract callable functions that pallet-contracts provides all have
//! benchmarks that determine the correct weight that an invocation of these functions
//! induces. In order to be able to charge the correct weight for the functions defined
//! by a chain extension benchmarks must be written, too. In the near future this crate
//! will provide the means for easier creation of those specialized benchmarks.
use crate::{
Error,
wasm::{Runtime, RuntimeToken},
};
use codec::Decode;
use frame_support::weights::Weight;
use sp_runtime::DispatchError;
use sp_std::{
marker::PhantomData,
vec::Vec,
};
pub use frame_system::Config as SysConfig;
pub use pallet_contracts_primitives::ReturnFlags;
pub use sp_core::crypto::UncheckedFrom;
pub use crate::exec::Ext;
pub use state::Init as InitState;
/// Result that returns a [`DispatchError`] on error.
pub type Result<T> = sp_std::result::Result<T, DispatchError>;
/// A trait used to extend the set of contract callable functions.
///
/// In order to create a custom chain extension this trait must be implemented and supplied
/// to the pallet contracts configuration trait as the associated type of the same name.
/// Consult the [module documentation](self) for a general explanation of chain extensions.
pub trait ChainExtension {
/// Call the chain extension logic.
///
/// This is the only function that needs to be implemented in order to write a
/// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension`
/// imported wasm function.
///
/// # Parameters
/// - `func_id`: The first argument to `seal_call_chain_extension`. Usually used to
/// determine which function to realize.
/// - `env`: Access to the remaining arguments and the execution environment.
///
/// # Return
///
/// In case of `Err` the contract execution is immediatly suspended and the passed error
/// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit
/// behaviour.
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>;
/// Determines whether chain extensions are enabled for this chain.
///
/// The default implementation returns `true`. Therefore it is not necessary to overwrite
/// this function when implementing a chain extension. In case of `false` the deployment of
/// a contract that references `seal_call_chain_extension` will be denied and calling this
/// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling
/// into [`call`](Self::call).
fn enabled() -> bool {
true
}
}
/// Implementation that indicates that no chain extension is available.
impl ChainExtension for () {
fn call<E: Ext>(_func_id: u32, mut _env: Environment<E, InitState>) -> Result<RetVal>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
// Never called since [`Self::enabled()`] is set to `false`. Because we want to
// avoid panics at all costs we supply a sensible error value here instead
// of an `unimplemented!`.
Err(Error::<E::T>::NoChainExtension.into())
}
fn enabled() -> bool {
false
}
}
/// Determines the exit behaviour and return value of a chain extension.
pub enum RetVal {
/// The chain extensions returns the supplied value to its calling contract.
Converging(u32),
/// The control does **not** return to the calling contract.
///
/// Use this to stop the execution of the contract when the chain extension returns.
/// The semantic is the same as for calling `seal_return`: The control returns to
/// the caller of the currently executing contract yielding the supplied buffer and
/// flags.
Diverging{flags: ReturnFlags, data: Vec<u8>},
}
/// Grants the chain extension access to its parameters and execution environment.
///
/// It uses the typestate pattern to enforce the correct usage of the parameters passed
/// to the chain extension.
pub struct Environment<'a, 'b, E: Ext, S: state::State> {
/// The actual data of this type.
inner: Inner<'a, 'b, E>,
/// `S` is only used in the type system but never as value.
phantom: PhantomData<S>,
}
/// Functions that are available in every state of this type.
impl<'a, 'b, E: Ext, S: state::State> Environment<'a, 'b, E, S>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
/// Charge the passed `amount` of weight from the overall limit.
///
/// It returns `Ok` when there the remaining weight budget is larger than the passed
/// `weight`. It returns `Err` otherwise. In this case the chain extension should
/// abort the execution and pass through the error.
///
/// # Note
///
/// Weight is synonymous with gas in substrate.
pub fn charge_weight(&mut self, amount: Weight) -> Result<()> {
self.inner.runtime.charge_gas(RuntimeToken::ChainExtension(amount)).map(|_| ())
}
/// Grants access to the execution environment of the current contract call.
///
/// Consult the functions on the returned type before re-implementing those functions.
pub fn ext(&mut self) -> &mut E {
self.inner.runtime.ext()
}
}
/// Functions that are only available in the initial state of this type.
///
/// Those are the functions that determine how the arguments to the chain extensions
/// should be consumed.
impl<'a, 'b, E: Ext> Environment<'a, 'b, E, state::Init> {
/// Creates a new environment for consumption by a chain extension.
///
/// It is only available to this crate because only the wasm runtime module needs to
/// ever create this type. Chain extensions merely consume it.
pub(crate) fn new(
runtime: &'a mut Runtime::<'b, E>,
input_ptr: u32,
input_len: u32,
output_ptr: u32,
output_len_ptr: u32,
) -> Self {
Environment {
inner: Inner {
runtime,
input_ptr,
input_len,
output_ptr,
output_len_ptr,
},
phantom: PhantomData,
}
}
/// Use all arguments as integer values.
pub fn only_in(self) -> Environment<'a, 'b, E, state::OnlyIn> {
Environment {
inner: self.inner,
phantom: PhantomData,
}
}
/// Use input arguments as integer and output arguments as pointer to a buffer.
pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, state::PrimInBufOut> {
Environment {
inner: self.inner,
phantom: PhantomData,
}
}
/// Use input and output arguments as pointers to a buffer.
pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, state::BufInBufOut> {
Environment {
inner: self.inner,
phantom: PhantomData,
}
}
}
/// Functions to use the input arguments as integers.
impl<'a, 'b, E: Ext, S: state::PrimIn> Environment<'a, 'b, E, S> {
/// The `input_ptr` argument.
pub fn val0(&self) -> u32 {
self.inner.input_ptr
}
/// The `input_len` argument.
pub fn val1(&self) -> u32 {
self.inner.input_len
}
}
/// Functions to use the output arguments as integers.
impl<'a, 'b, E: Ext, S: state::PrimOut> Environment<'a, 'b, E, S> {
/// The `output_ptr` argument.
pub fn val2(&self) -> u32 {
self.inner.output_ptr
}
/// The `output_len_ptr` argument.
pub fn val3(&self) -> u32 {
self.inner.output_len_ptr
}
}
/// Functions to use the input arguments as pointer to a buffer.
impl<'a, 'b, E: Ext, S: state::BufIn> Environment<'a, 'b, E, S>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
/// Reads `min(max_len, in_len)` from contract memory.
///
/// This does **not** charge any weight. The caller must make sure that the an
/// appropriate amount of weight is charged **before** reading from contract memory.
/// The reason for that is that usually the costs for reading data and processing
/// said data cannot be separated in a benchmark. Therefore a chain extension would
/// charge the overall costs either using `max_len` (worst case approximation) or using
/// [`in_len()`](Self::in_len).
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
self.inner.runtime.read_sandbox_memory(
self.inner.input_ptr,
self.inner.input_len.min(max_len),
)
}
/// Reads `min(buffer.len(), in_len) from contract memory.
///
/// This takes a mutable pointer to a buffer fills it with data and shrinks it to
/// the size of the actual data. Apart from supporting pre-allocated buffers it is
/// equivalent to to [`read()`](Self::read).
pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> {
let len = buffer.len();
let sliced = {
let buffer = core::mem::take(buffer);
&mut buffer[..len.min(self.inner.input_len as usize)]
};
self.inner.runtime.read_sandbox_memory_into_buf(
self.inner.input_ptr,
sliced,
)?;
*buffer = sliced;
Ok(())
}
/// Reads `in_len` from contract memory and scale decodes it.
///
/// This function is secure and recommended for all input types of fixed size
/// as long as the cost of reading the memory is included in the overall already charged
/// weight of the chain extension. This should usually be the case when fixed input types
/// are used. Non fixed size types (like everything using `Vec`) usually need to use
/// [`in_len()`](Self::in_len) in order to properly charge the necessary weight.
pub fn read_as<T: Decode>(&mut self) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as(
self.inner.input_ptr,
self.inner.input_len,
)
}
/// The length of the input as passed in as `input_len`.
///
/// A chain extension would use this value to calculate the dynamic part of its
/// weight. For example a chain extension that calculates the hash of some passed in
/// bytes would use `in_len` to charge the costs of hashing that amount of bytes.
/// This also subsumes the act of copying those bytes as a benchmarks measures both.
pub fn in_len(&self) -> u32 {
self.inner.input_len
}
}
/// Functions to use the output arguments as pointer to a buffer.
impl<'a, 'b, E: Ext, S: state::BufOut> Environment<'a, 'b, E, S>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
/// Write the supplied buffer to contract memory.
///
/// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned.
/// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer
/// by supplying the guard value of [`u32::max_value()`] as `out_ptr`. The
/// `weight_per_byte` is only charged when the write actually happens and is not skipped or
/// failed due to a too small output buffer.
pub fn write(
&mut self,
buffer: &[u8],
allow_skip: bool,
weight_per_byte: Option<Weight>,
) -> Result<()> {
self.inner.runtime.write_sandbox_output(
self.inner.output_ptr,
self.inner.output_len_ptr,
buffer,
allow_skip,
|len| {
weight_per_byte.map(|w| RuntimeToken::ChainExtension(w.saturating_mul(len.into())))
},
)
}
}
/// The actual data of an `Environment`.
///
/// All data is put into this struct to easily pass it around as part of the typestate
/// pattern. Also it creates the opportunity to box this struct in the future in case it
/// gets too large.
struct Inner<'a, 'b, E: Ext> {
/// The runtime contains all necessary functions to interact with the running contract.
runtime: &'a mut Runtime::<'b, E>,
/// Verbatim argument passed to `seal_call_chain_extension`.
input_ptr: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
input_len: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
output_ptr: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
output_len_ptr: u32,
}
/// Private submodule with public types to prevent other modules from naming them.
mod state {
pub trait State {}
pub trait PrimIn: State {}
pub trait PrimOut: State {}
pub trait BufIn: State {}
pub trait BufOut: State {}
pub enum Init {}
pub enum OnlyIn {}
pub enum PrimInBufOut {}
pub enum BufInBufOut {}
impl State for Init {}
impl State for OnlyIn {}
impl State for PrimInBufOut {}
impl State for BufInBufOut {}
impl PrimIn for OnlyIn {}
impl PrimOut for OnlyIn {}
impl PrimIn for PrimInBufOut {}
impl BufOut for PrimInBufOut {}
impl BufIn for BufInBufOut {}
impl BufOut for BufInBufOut {}
}
+19 -9
View File
@@ -32,7 +32,7 @@ pub type Gas = frame_support::weights::Weight;
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub enum GasMeterResult {
Proceed,
Proceed(ChargedAmount),
OutOfGas,
}
@@ -40,11 +40,20 @@ impl GasMeterResult {
pub fn is_out_of_gas(&self) -> bool {
match *self {
GasMeterResult::OutOfGas => true,
GasMeterResult::Proceed => false,
GasMeterResult::Proceed(_) => false,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChargedAmount(Gas);
impl ChargedAmount {
pub fn amount(&self) -> Gas {
self.0
}
}
#[cfg(not(test))]
pub trait TestAuxiliaries {}
#[cfg(not(test))]
@@ -139,17 +148,18 @@ impl<T: Config> GasMeter<T> {
self.gas_left = new_value.unwrap_or_else(Zero::zero);
match new_value {
Some(_) => GasMeterResult::Proceed,
Some(_) => GasMeterResult::Proceed(ChargedAmount(amount)),
None => GasMeterResult::OutOfGas,
}
}
// Account for not fully used gas.
//
// This can be used after dispatching a runtime call to refund gas that was not
// used by the dispatchable.
pub fn refund(&mut self, gas: Gas) {
self.gas_left = self.gas_left.saturating_add(gas).max(self.gas_limit);
/// Refund previously charged gas back to the gas meter.
///
/// This can be used if a gas worst case estimation must be charged before
/// performing a certain action. This way the difference can be refundend when
/// the worst case did not happen.
pub fn refund(&mut self, amount: ChargedAmount) {
self.gas_left = self.gas_left.saturating_add(amount.0).min(self.gas_limit)
}
/// Allocate some amount of gas and perform some work with
+9
View File
@@ -89,6 +89,8 @@ mod wasm;
mod rent;
mod benchmarking;
mod schedule;
pub mod chain_extension;
pub mod weights;
#[cfg(test)]
@@ -320,6 +322,9 @@ pub trait Config: frame_system::Config {
/// Describes the weights of the dispatchables of this module and is also used to
/// construct a default cost schedule.
type WeightInfo: WeightInfo;
/// Type that allows the runtime authors to add new host functions for a contract to call.
type ChainExtension: chain_extension::ChainExtension;
}
decl_error! {
@@ -387,6 +392,10 @@ decl_error! {
TooManyTopics,
/// The topics passed to `seal_deposit_events` contains at least one duplicate.
DuplicateTopics,
/// The chain does not provide a chain extension. Calling the chain extension results
/// in this error. Note that this usually shouldn't happen as deploying such contracts
/// is rejected.
NoChainExtension,
}
}
+201
View File
@@ -19,6 +19,10 @@ use crate::{
BalanceOf, ContractInfo, ContractInfoOf, GenesisConfig, Module,
RawAliveContractInfo, RawEvent, Config, Schedule, gas::Gas,
Error, ConfigCache, RuntimeReturnCode, storage::Storage,
chain_extension::{
Result as ExtensionResult, Environment, ChainExtension, Ext, SysConfig, RetVal,
UncheckedFrom, InitState, ReturnFlags,
},
exec::AccountIdOf,
};
use assert_matches::assert_matches;
@@ -101,6 +105,85 @@ pub mod test_utils {
}
}
thread_local! {
static TEST_EXTENSION: sp_std::cell::RefCell<TestExtension> = Default::default();
}
pub struct TestExtension {
enabled: bool,
last_seen_buffer: Vec<u8>,
last_seen_inputs: (u32, u32, u32, u32),
}
impl TestExtension {
fn disable() {
TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false)
}
fn last_seen_buffer() -> Vec<u8> {
TEST_EXTENSION.with(|e| e.borrow().last_seen_buffer.clone())
}
fn last_seen_inputs() -> (u32, u32, u32, u32) {
TEST_EXTENSION.with(|e| e.borrow().last_seen_inputs.clone())
}
}
impl Default for TestExtension {
fn default() -> Self {
Self {
enabled: true,
last_seen_buffer: vec![],
last_seen_inputs: (0, 0, 0, 0),
}
}
}
impl ChainExtension for TestExtension {
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> ExtensionResult<RetVal>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
match func_id {
0 => {
let mut env = env.buf_in_buf_out();
let input = env.read(2)?;
env.write(&input, false, None)?;
TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input);
Ok(RetVal::Converging(func_id))
},
1 => {
let env = env.only_in();
TEST_EXTENSION.with(|e|
e.borrow_mut().last_seen_inputs = (
env.val0(), env.val1(), env.val2(), env.val3()
)
);
Ok(RetVal::Converging(func_id))
},
2 => {
let mut env = env.buf_in_buf_out();
let weight = env.read(2)?[1].into();
env.charge_weight(weight)?;
Ok(RetVal::Converging(func_id))
},
3 => {
Ok(RetVal::Diverging{
flags: ReturnFlags::REVERT,
data: vec![42, 99],
})
},
_ => {
panic!("Passed unknown func_id to test chain extension: {}", func_id);
}
}
}
fn enabled() -> bool {
TEST_EXTENSION.with(|e| e.borrow().enabled)
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Test;
parameter_types! {
@@ -188,6 +271,7 @@ impl Config for Test {
type MaxValueSize = MaxValueSize;
type WeightPrice = Self;
type WeightInfo = ();
type ChainExtension = TestExtension;
}
type Balances = pallet_balances::Module<Test>;
@@ -1933,3 +2017,120 @@ fn instantiate_return_code() {
});
}
#[test]
fn disabled_chain_extension_wont_deploy() {
let (code, _hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
TestExtension::disable();
assert_eq!(
Contracts::put_code(Origin::signed(ALICE), code),
Err("module uses chain extensions but chain extensions are disabled".into()),
);
});
}
#[test]
fn disabled_chain_extension_errors_on_call() {
let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
TestExtension::disable();
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
),
Error::<Test>::NoChainExtension,
);
});
}
#[test]
fn chain_extension_works() {
let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
// The contract takes a up to 2 byte buffer where the first byte passed is used as
// as func_id to the chain extension which behaves differently based on the
// func_id.
// 0 = read input buffer and pass it through as output
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![0, 99],
);
let gas_consumed = result.gas_consumed;
assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]);
assert_eq!(result.exec_result.unwrap().data, vec![0, 99]);
// 1 = treat inputs as integer primitives and store the supplied integers
Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![1],
).exec_result.unwrap();
// those values passed in the fixture
assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12));
// 2 = charge some extra weight (amount supplied in second byte)
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![2, 42],
);
assert_ok!(result.exec_result);
assert_eq!(result.gas_consumed, gas_consumed + 42);
// 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![3],
).exec_result.unwrap();
assert_eq!(result.flags, ReturnFlags::REVERT);
assert_eq!(result.data, vec![42, 99]);
});
}
+1 -2
View File
@@ -34,14 +34,13 @@ mod code_cache;
mod prepare;
mod runtime;
use self::runtime::Runtime;
use self::code_cache::load as load_code;
use pallet_contracts_primitives::ExecResult;
pub use self::code_cache::save as save_code;
#[cfg(feature = "runtime-benchmarks")]
pub use self::code_cache::save_raw as save_code_raw;
pub use self::runtime::ReturnCode;
pub use self::runtime::{ReturnCode, Runtime, RuntimeToken};
/// A prepared wasm module ready for execution.
#[derive(Clone, Encode, Decode)]
+11 -4
View File
@@ -19,10 +19,11 @@
//! wasm module before execution. It also extracts some essential information
//! from a module.
use crate::wasm::env_def::ImportSatisfyCheck;
use crate::wasm::PrefabWasmModule;
use crate::{Schedule, Config};
use crate::{
Schedule, Config,
chain_extension::ChainExtension,
wasm::{PrefabWasmModule, env_def::ImportSatisfyCheck},
};
use parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType};
use pwasm_utils;
use sp_std::prelude::*;
@@ -355,6 +356,12 @@ impl<'a, T: Config> ContractModule<'a, T> {
return Err("module imports `seal_println` but debug features disabled");
}
if !T::ChainExtension::enabled() &&
import.field().as_bytes() == b"seal_call_chain_extension"
{
return Err("module uses chain extensions but chain extensions are disabled");
}
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f)
|| !C::can_satisfy(import.field().as_bytes(), func_ty)
{
+84 -19
View File
@@ -20,7 +20,7 @@
use crate::{
HostFnWeights, Schedule, Config, CodeHash, BalanceOf, Error,
exec::{Ext, StorageKey, TopicOf},
gas::{Gas, GasMeter, Token, GasMeterResult},
gas::{Gas, GasMeter, Token, GasMeterResult, ChargedAmount},
wasm::env_def::ConvertibleToWasm,
};
use sp_sandbox;
@@ -28,7 +28,7 @@ use parity_wasm::elements::ValueType;
use frame_system;
use frame_support::dispatch::DispatchError;
use sp_std::prelude::*;
use codec::{Decode, Encode};
use codec::{Decode, DecodeAll, Encode};
use sp_runtime::traits::SaturatedConversion;
use sp_core::crypto::UncheckedFrom;
use sp_io::hashing::{
@@ -126,7 +126,7 @@ impl<T: Into<DispatchError>> From<T> for TrapReason {
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
enum RuntimeToken {
pub enum RuntimeToken {
/// 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),
@@ -198,6 +198,10 @@ enum RuntimeToken {
HashBlake256(u32),
/// Weight of calling `seal_hash_blake2_128` for the given input size.
HashBlake128(u32),
/// Weight charged by a chain extension through `seal_call_chain_extension`.
ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
CopyIn(u32),
}
impl<T: Config> Token<T> for RuntimeToken
@@ -256,6 +260,8 @@ where
.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())),
ChainExtension(amount) => amount,
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
}
}
}
@@ -376,6 +382,14 @@ where
}
}
/// 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
}
/// 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
@@ -388,12 +402,12 @@ where
/// Charge the gas meter with the specified token.
///
/// Returns `Err(HostError)` if there is not enough gas.
fn charge_gas<Tok>(&mut self, token: Tok) -> Result<(), DispatchError>
pub fn charge_gas<Tok>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError>
where
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
{
match self.gas_meter.charge(&self.schedule.host_fn_weights, token) {
GasMeterResult::Proceed => Ok(()),
GasMeterResult::Proceed(amount) => Ok(amount),
GasMeterResult::OutOfGas => Err(Error::<E::T>::OutOfGas.into())
}
}
@@ -403,7 +417,7 @@ where
/// Returns `Err` if one of the following conditions occurs:
///
/// - requested buffer is not within the bounds of the sandbox memory.
fn read_sandbox_memory(&self, ptr: u32, len: u32)
pub fn read_sandbox_memory(&self, ptr: u32, len: u32)
-> Result<Vec<u8>, DispatchError>
{
let mut buf = vec![0u8; len as usize];
@@ -417,7 +431,7 @@ where
/// Returns `Err` if one of the following conditions occurs:
///
/// - requested buffer is not within the bounds of the sandbox memory.
fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
pub fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
-> Result<(), DispatchError>
{
self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
@@ -429,20 +443,29 @@ where
///
/// - requested buffer is not within the bounds of the sandbox memory.
/// - the buffer contents cannot be decoded as the required type.
fn read_sandbox_memory_as<D: Decode>(&self, ptr: u32, len: u32)
///
/// # Note
///
/// It is safe to forgo benchmarking and charging weight relative to `len` for fixed
/// size types (basically everything not containing a heap collection):
/// Despite the fact that we are usually about to read the encoding of a fixed size
/// type, we cannot know the encoded size of that type. We therefore are required to
/// use the length provided by the contract. This length is untrusted and therefore
/// we charge weight relative to the provided size upfront that covers the copy costs.
/// On success this cost is refunded as the copying was already covered in the
/// overall cost of the host function. This is different from `read_sandbox_memory`
/// where the size is dynamic and the costs resulting from that dynamic size must
/// be charged relative to this dynamic size anyways (before reading) by constructing
/// the benchmark for that.
pub fn read_sandbox_memory_as<D: Decode>(&mut self, ptr: u32, len: u32)
-> Result<D, DispatchError>
{
let amount = self.charge_gas(RuntimeToken::CopyIn(len))?;
let buf = self.read_sandbox_memory(ptr, len)?;
D::decode(&mut &buf[..]).map_err(|_| Error::<E::T>::DecodingFailed.into())
}
/// 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())
let decoded = D::decode_all(&mut &buf[..])
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
self.gas_meter.refund(amount);
Ok(decoded)
}
/// Write the given buffer and its length to the designated locations in sandbox memory and
@@ -464,7 +487,7 @@ where
///
/// 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`.
fn write_sandbox_output(
pub fn write_sandbox_output(
&mut self,
out_ptr: u32,
out_len_ptr: u32,
@@ -496,6 +519,15 @@ where
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.
@@ -1362,4 +1394,37 @@ define_env!(Env, <E: Ext>,
ctx.charge_gas(RuntimeToken::HashBlake128(input_len))?;
Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?)
},
// Call into the chain extension provided by the chain if any.
//
// Handling of the input values is up to the specific chain extension and so is the
// return value. The extension can decide to use the inputs as primitive inputs or as
// in/out arguments by interpreting them as pointers. Any caller of this function
// must therefore coordinate with the chain that it targets.
//
// # Note
//
// If no chain extension exists the contract will trap with the `NoChainExtension`
// module error.
seal_call_chain_extension(
ctx,
func_id: u32,
input_ptr: u32,
input_len: u32,
output_ptr: u32,
output_len_ptr: u32
) -> u32 => {
use crate::chain_extension::{ChainExtension, Environment, RetVal};
if <E::T as Config>::ChainExtension::enabled() == false {
Err(Error::<E::T>::NoChainExtension)?;
}
let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr);
match <E::T as Config>::ChainExtension::call(func_id, env)? {
RetVal::Converging(val) => Ok(val),
RetVal::Diverging{flags, data} => Err(TrapReason::Return(ReturnData {
flags: flags.bits(),
data,
})),
}
},
);