diff --git a/alloc/src/handlers.rs b/alloc/src/handlers.rs index ef6397742ad..a706a711be9 100644 --- a/alloc/src/handlers.rs +++ b/alloc/src/handlers.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2019 Parity Technologies (UK) Ltd. +// Copyright 2018-2020 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. @@ -12,14 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - core::intrinsics::abort() -} - -// `extern` fn uses type `core::alloc::Layout`, which is not FFI-safe -#[allow(improper_ctypes)] #[alloc_error_handler] -pub extern "C" fn oom(_: core::alloc::Layout) -> ! { +fn oom(_: core::alloc::Layout) -> ! { core::intrinsics::abort() } diff --git a/alloc/src/lib.rs b/alloc/src/lib.rs index 0021bc1704c..0c801f4c047 100644 --- a/alloc/src/lib.rs +++ b/alloc/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2019 Parity Technologies (UK) Ltd. +// Copyright 2018-2020 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. @@ -15,7 +15,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(alloc_error_handler, core_intrinsics))] -// Use `wee_alloc` as the global allocator. +// We use `wee_alloc` as the global allocator since it is optimized for binary file size +// so that contracts compiled with it as allocator do not grow too much in size. #[cfg(not(feature = "std"))] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; diff --git a/core/Cargo.toml b/core/Cargo.toml index 22af38fcebc..45f1007c3b5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -73,6 +73,7 @@ std = [ "blake2", ] ink-fuzz-tests = ["std"] +ink-unstable-chain-extensions = [] [[bench]] name = "bench_lazy" diff --git a/core/src/env/api.rs b/core/src/env/api.rs index 5e0f73c8b21..a36286785a3 100644 --- a/core/src/env/api.rs +++ b/core/src/env/api.rs @@ -17,6 +17,7 @@ use crate::env::{ backend::{ Env, + ReturnFlags, TypedEnv, }, call::{ @@ -65,12 +66,12 @@ where /// # Errors /// /// If the returned value cannot be properly decoded. -pub fn gas_price(gas: u64) -> Result +pub fn weight_to_fee(gas: u64) -> Result where T: EnvTypes, { ::on_instance(|instance| { - TypedEnv::gas_price::(instance, gas) + TypedEnv::weight_to_fee::(instance, gas) }) } @@ -208,6 +209,10 @@ where } /// Writes the value to the contract storage under the given key. +/// +/// # Panics +/// +/// - If the encode length of value exceeds the configured maximum value length of a storage entry. pub fn set_contract_storage(key: &Key, value: &V) where V: scale::Encode, @@ -221,8 +226,8 @@ where /// /// # Errors /// -/// - If the decoding of the typed value failed -pub fn get_contract_storage(key: &Key) -> Option> +/// - If the decoding of the typed value failed (`KeyNotFound`) +pub fn get_contract_storage(key: &Key) -> Result> where R: scale::Decode, { @@ -238,25 +243,6 @@ pub fn clear_contract_storage(key: &Key) { }) } -/// Invokes a call to the runtime. -/// -/// # Note -/// -/// The call is not guaranteed to execute immediately but might be deferred -/// to the end of the contract execution. -/// -/// # Errors -/// -/// - If the called runtime function does not exist. -pub fn invoke_runtime(params: &T::Call) -> Result<()> -where - T: EnvTypes, -{ - ::on_instance(|instance| { - TypedEnv::invoke_runtime::(instance, params) - }) -} - /// Invokes a contract message. /// /// # Note @@ -268,7 +254,8 @@ where /// /// # Errors /// -/// - If the called contract does not exist. +/// - If the called account does not exist. +/// - If the called account is not a contract. /// - If the called contract is a tombstone. /// - If arguments passed to the called contract message are invalid. /// - If the called contract execution has trapped. @@ -292,7 +279,8 @@ where /// /// # Errors /// -/// - If the called contract does not exist. +/// - If the called account does not exist. +/// - If the called account is not a contract. /// - If the called contract is a tombstone. /// - If arguments passed to the called contract message are invalid. /// - If the called contract execution has trapped. @@ -424,7 +412,7 @@ where /// contract call or invoke a runtime function that performs the /// transaction. /// -/// # Errors +/// # Panics /// /// If the contract doesn't have sufficient funds. pub fn transfer(destination: T::AccountId, value: T::Balance) -> Result<()> @@ -436,6 +424,27 @@ where }) } +/// Calls the chain extension with the given ID and inputs. +/// +/// Returns the given output type. +/// +/// # Errors +/// +/// - If the given function ID does not exist in the runtime. +/// - If the given inputs cannot be properly decoded by the runtime. +/// - If the given output type cannot be properly decoded by the contract. +/// - If some chain extension specific conditions are not met. +#[cfg(feature = "ink-unstable-chain-extensions")] +pub fn call_chain_extension(func_id: u32, input: &I) -> Result +where + I: scale::Codec + 'static, + O: scale::Codec + 'static, +{ + ::on_instance(|instance| { + Env::call_chain_extension(instance, func_id, input) + }) +} + /// Returns the execution input to the executed contract and decodes it as `T`. /// /// # Note @@ -470,14 +479,13 @@ where /// /// # Note /// -/// This call must be the last call to the contract -/// environment for every contract execution. -pub fn output(return_value: &R) +/// This function stops the execution of the contract immediately. +pub fn return_value(return_flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode, { ::on_instance(|instance| { - Env::output::(instance, return_value) + Env::return_value::(instance, return_flags, return_value) }) } @@ -505,20 +513,6 @@ pub fn println(content: &str) { ::on_instance(|instance| Env::println(instance, content)) } -/// Returns the value from the *runtime* storage at the position of the key if any. -/// -/// # Errors -/// -/// - If the decoding of the typed value failed -pub fn get_runtime_storage(runtime_key: &[u8]) -> Option> -where - R: scale::Decode, -{ - ::on_instance(|instance| { - Env::get_runtime_storage::(instance, runtime_key) - }) -} - /// Built-in efficient cryptographic hash functions. pub mod hash { use super::*; diff --git a/core/src/env/backend.rs b/core/src/env/backend.rs index 54599365528..2fd4e442886 100644 --- a/core/src/env/backend.rs +++ b/core/src/env/backend.rs @@ -24,6 +24,33 @@ use crate::env::{ }; use ink_primitives::Key; +/// The flags to indicate further information about the end of a contract execution. +pub struct ReturnFlags { + value: u32, +} + +impl Default for ReturnFlags { + fn default() -> Self { + Self { value: 0 } + } +} + +impl ReturnFlags { + /// Sets the bit to indicate that the execution is going to be reverted. + pub fn set_reverted(mut self, has_reverted: bool) -> Self { + match has_reverted { + true => self.value |= has_reverted as u32, + false => self.value &= !has_reverted as u32, + } + self + } + + /// Returns the underlying `u32` representation. + pub(crate) fn into_u32(self) -> u32 { + self.value + } +} + /// Environmental contract functionality that does not require `EnvTypes`. pub trait Env { /// Writes the value to the contract storage under the given key. @@ -36,22 +63,13 @@ pub trait Env { /// # Errors /// /// - If the decoding of the typed value failed - fn get_contract_storage(&mut self, key: &Key) -> Option> + fn get_contract_storage(&mut self, key: &Key) -> Result> where R: scale::Decode; /// Clears the contract's storage key entry. fn clear_contract_storage(&mut self, key: &Key); - /// Returns the value from the *runtime* storage at the position of the key if any. - /// - /// # Errors - /// - /// - If the decoding of the typed value failed - fn get_runtime_storage(&mut self, runtime_key: &[u8]) -> Option> - where - R: scale::Decode; - /// Returns the execution input to the executed contract and decodes it as `T`. /// /// # Note @@ -83,10 +101,12 @@ pub trait Env { /// /// # Note /// - /// The setting of this property must be the last interaction between - /// the executed contract and its environment. - /// The environment access asserts this guarantee. - fn output(&mut self, return_value: &R) + /// Calling this method will end contract execution immediately. + /// It will return the given return value back to its caller. + /// + /// The `flags` parameter can be used to revert the state changes of the + /// entire execution if necessary. + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode; @@ -108,6 +128,22 @@ pub trait Env { /// Conducts the BLAKE2 128-bit hash of the input /// puts the result into the output buffer. fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]); + + /// Calls the chain extension with the given ID and inputs. + /// + /// Returns the output of the chain extension of the specified type. + /// + /// # Errors + /// + /// - If the chain extension with the given ID does not exist. + /// - If the inputs had an unexpected encoding. + /// - If the output could not be properly decoded. + /// - If some extension specific condition has not been met. + #[cfg(feature = "ink-unstable-chain-extensions")] + fn call_chain_extension(&mut self, func_id: u32, input: &I) -> Result + where + I: scale::Codec + 'static, + O: scale::Codec + 'static; } /// Environmental contract functionality. @@ -131,7 +167,7 @@ pub trait TypedEnv: Env { /// # Note /// /// For more details visit: [`ink_core::env::gas_price`] - fn gas_price(&mut self, gas: u64) -> Result; + fn weight_to_fee(&mut self, gas: u64) -> Result; /// Returns the amount of gas left for the contract execution. /// @@ -208,15 +244,6 @@ pub trait TypedEnv: Env { where T: EnvTypes; - /// Invokes a call of the runtime. - /// - /// # Note - /// - /// For more details visit: [`ink_core::env::invoke_runtime`] - fn invoke_runtime(&mut self, call: &T::Call) -> Result<()> - where - T: EnvTypes; - /// Invokes a contract message. /// /// # Note diff --git a/core/src/env/engine/off_chain/chain_extension.rs b/core/src/env/engine/off_chain/chain_extension.rs new file mode 100644 index 00000000000..f9a94713a77 --- /dev/null +++ b/core/src/env/engine/off_chain/chain_extension.rs @@ -0,0 +1,114 @@ +// Copyright 2019-2020 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. + +use super::OffChainError; +use crate::env::Result; +use std::collections::HashMap; + +type FuncId = u32; + +/// Types implementing this trait can be used as chain extensions. +/// +/// This trait is only useful for testing contract via the off-chain environment. +pub trait ChainExtension { + /// The expected input type. + /// + /// # Note + /// + /// This can be a tuple to expect multiple input types. + type Input: scale::Codec; + /// The expected output type. + type Output: scale::Codec; + + /// The static function ID of the chain extension. + /// + /// # Note + /// + /// This is expected to return a constant value. + fn func_id(&self) -> u32; + + /// Calls the chain extension with the given input. + fn call(&mut self, input: &Self::Input) -> Result; +} + +/// A raw chain extension function. +/// +/// This is mostly a wrapper closure around the real chain extension function +/// that handles marshalling of types between their encoded and decoded +/// representations. +type ChainExtensionFn = Box) -> Result>>; + +/// Runtime call handler. +/// +/// More generically a mapping from bytes to bytes. +pub struct ChainExtensionHandler { + /// The currently registered runtime call handler. + registered: HashMap, +} + +impl ChainExtensionHandler { + /// Creates a new chain extension handler. + /// + /// Initialized with an empty set of chain extensions. + pub fn new() -> Self { + Self { + registered: HashMap::new(), + } + } + + /// Resets the chain extension handler to uninitialized state. + pub fn reset(&mut self) { + self.registered.clear() + } + + /// Register a new chain extension. + pub fn register( + &mut self, + mut extension: Box>, + ) where + I: scale::Codec + 'static, + O: scale::Codec + 'static, + { + let func_id = extension.func_id(); + self.registered.insert( + func_id, + Box::new(move |encoded_input: Vec| { + let decoded_input = scale::Decode::decode(&mut &encoded_input[..])?; + let decoded_output = extension.call(&decoded_input)?; + Ok(scale::Encode::encode(&decoded_output)) + }), + ); + } + + /// Evaluates the chain extension with the given parameters. + /// + /// Upon success returns the values returned by the evaluated chain extension. + pub fn eval(&mut self, func_id: FuncId, input: &I) -> Result + where + I: scale::Codec + 'static, + O: scale::Codec + 'static, + { + use std::collections::hash_map::Entry; + match self.registered.entry(func_id) { + Entry::Occupied(mut occupied) => { + let encoded_input = scale::Encode::encode(input); + let encoded_output = occupied.get_mut()(encoded_input)?; + scale::Decode::decode(&mut &encoded_output[..]).map_err(Into::into) + } + Entry::Vacant(_vacant) => { + Err(OffChainError::UnregisteredChainExtension.into()) + } + } + } +} diff --git a/core/src/env/engine/off_chain/db/accounts.rs b/core/src/env/engine/off_chain/db/accounts.rs index 0f353244995..63590497053 100644 --- a/core/src/env/engine/off_chain/db/accounts.rs +++ b/core/src/env/engine/off_chain/db/accounts.rs @@ -247,13 +247,12 @@ impl Account { } /// Returns the value stored in the contract storage at the given key. - pub fn get_storage(&self, at: Key) -> Option> + pub fn get_storage(&self, at: Key) -> Result> where T: scale::Decode, { self.contract_or_err() .and_then(|contract| contract.storage.get_storage::(at)) - .transpose() } /// Returns the total number of reads and write from and to the contract's storage. diff --git a/core/src/env/engine/off_chain/impls.rs b/core/src/env/engine/off_chain/impls.rs index df41adea4fa..7a5cb4324ef 100644 --- a/core/src/env/engine/off_chain/impls.rs +++ b/core/src/env/engine/off_chain/impls.rs @@ -27,11 +27,12 @@ use crate::env::{ EnvError, EnvTypes, Result, + ReturnFlags, Topics, TypedEnv, }; -use ink_primitives::Key; use core::convert::TryInto; +use ink_primitives::Key; use num_traits::Bounded; impl EnvInstance { @@ -70,13 +71,13 @@ impl Env for EnvInstance { .expect("callee account is not a smart contract"); } - fn get_contract_storage(&mut self, key: &Key) -> Option> + fn get_contract_storage(&mut self, key: &Key) -> Result> where R: scale::Decode, { self.callee_account() .get_storage::(*key) - .map(|result| result.map_err(Into::into)) + .map_err(Into::into) } fn clear_contract_storage(&mut self, key: &Key) { @@ -85,13 +86,6 @@ impl Env for EnvInstance { .expect("callee account is not a smart contract"); } - fn get_runtime_storage(&mut self, runtime_key: &[u8]) -> Option> - where - R: scale::Decode, - { - self.runtime_storage.load::(runtime_key) - } - fn decode_input(&mut self) -> Result where T: scale::Decode, @@ -107,7 +101,7 @@ impl Env for EnvInstance { }) } - fn output(&mut self, return_value: &R) + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode, { @@ -115,6 +109,7 @@ impl Env for EnvInstance { .exec_context_mut() .expect("uninitialized execution context"); ctx.output = Some(return_value.encode()); + std::process::exit(flags.into_u32() as i32) } fn println(&mut self, content: &str) { @@ -136,6 +131,45 @@ impl Env for EnvInstance { fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) { hashing::sha2_256(input, output) } + + #[cfg(feature = "ink-unstable-chain-extensions")] + fn call_chain_extension(&mut self, func_id: u32, input: &I) -> Result + where + I: scale::Codec + 'static, + O: scale::Codec + 'static, + { + self.chain_extension_handler.eval(func_id, input) + } +} + +impl EnvInstance { + fn transfer_impl(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> + where + T: EnvTypes, + { + let src_id = self.account_id::()?; + let src_value = self + .accounts + .get_account::(&src_id) + .expect("account of executed contract must exist") + .balance::()?; + if src_value < value { + return Err(EnvError::TransferFailed) + } + let dst_value = self + .accounts + .get_or_create_account::(&destination) + .balance::()?; + self.accounts + .get_account_mut::(&src_id) + .expect("account of executed contract must exist") + .set_balance::(src_value - value)?; + self.accounts + .get_account_mut::(&destination) + .expect("the account must exist already or has just been created") + .set_balance::(dst_value + value)?; + Ok(()) + } } impl TypedEnv for EnvInstance { @@ -156,14 +190,16 @@ impl TypedEnv for EnvInstance { } /// Emulates gas price calculation - fn gas_price(&mut self, gas: u64) -> Result { + fn weight_to_fee(&mut self, gas: u64) -> Result { use crate::env::arithmetic::Saturating as _; - let gas_price = self.chain_spec + let gas_price = self + .chain_spec .gas_price::() .map_err(|_| scale::Error::from("could not decode gas price"))?; - Ok(gas_price.saturating_mul(gas.try_into().unwrap_or_else(|_| Bounded::max_value()))) + Ok(gas_price + .saturating_mul(gas.try_into().unwrap_or_else(|_| Bounded::max_value()))) } fn gas_left(&mut self) -> Result { @@ -254,13 +290,6 @@ impl TypedEnv for EnvInstance { unimplemented!("off-chain environment does not support contract invokation") } - fn invoke_runtime(&mut self, params: &T::Call) -> Result<()> - where - T: EnvTypes, - { - self.runtime_call_handler.invoke::(params) - } - fn eval_contract( &mut self, _call_params: &CallParams>, @@ -307,28 +336,7 @@ impl TypedEnv for EnvInstance { where T: EnvTypes, { - let src_id = self.account_id::()?; - let src_value = self - .accounts - .get_account::(&src_id) - .expect("account of executed contract must exist") - .balance::()?; - if src_value < value { - return Err(EnvError::TransferCallFailed) - } - let dst_value = self - .accounts - .get_or_create_account::(&destination) - .balance::()?; - self.accounts - .get_account_mut::(&src_id) - .expect("account of executed contract must exist") - .set_balance::(src_value - value)?; - self.accounts - .get_account_mut::(&destination) - .expect("the account must exist already or has just been created") - .set_balance::(dst_value + value)?; - Ok(()) + self.transfer_impl::(destination, value) } fn random(&mut self, subject: &[u8]) -> Result diff --git a/core/src/env/engine/off_chain/mod.rs b/core/src/env/engine/off_chain/mod.rs index 3a77bc84ba1..42e0a54935f 100644 --- a/core/src/env/engine/off_chain/mod.rs +++ b/core/src/env/engine/off_chain/mod.rs @@ -16,10 +16,10 @@ mod call_data; mod db; mod hashing; mod impls; -mod runtime_calls; -mod runtime_storage; pub mod test_api; mod typed_encoded; +#[cfg(feature = "ink-unstable-chain-extensions")] +mod chain_extension; mod types; #[cfg(test)] @@ -44,18 +44,17 @@ use self::{ EmittedEventsRecorder, ExecContext, }, - runtime_calls::RuntimeCallHandler, - runtime_storage::RuntimeStorage, typed_encoded::TypedEncoded, types::{ OffAccountId, OffBalance, OffBlockNumber, - OffCall, OffHash, OffTimestamp, }, }; +#[cfg(feature = "ink-unstable-chain-extensions")] +use self::chain_extension::ChainExtensionHandler; use super::OnInstance; use crate::env::EnvTypes; use core::cell::RefCell; @@ -70,7 +69,7 @@ pub enum OffChainError { #[from(ignore)] UninitializedExecutionContext, #[from(ignore)] - UnregisteredRuntimeCallHandler, + UnregisteredChainExtension, } pub type Result = core::result::Result; @@ -89,10 +88,9 @@ pub struct EnvInstance { blocks: Vec, /// The console to print debug contents. console: Console, - /// The emulated runtime storage. - runtime_storage: RuntimeStorage, - /// The runtime calls handler. - runtime_call_handler: RuntimeCallHandler, + /// Handler for registered chain extensions. + #[cfg(feature = "ink-unstable-chain-extensions")] + chain_extension_handler: ChainExtensionHandler, /// Emitted events recorder. emitted_events: EmittedEventsRecorder, } @@ -106,8 +104,8 @@ impl EnvInstance { chain_spec: ChainSpec::uninitialized(), blocks: Vec::new(), console: Console::new(), - runtime_storage: RuntimeStorage::new(), - runtime_call_handler: RuntimeCallHandler::new(), + #[cfg(feature = "ink-unstable-chain-extensions")] + chain_extension_handler: ChainExtensionHandler::new(), emitted_events: EmittedEventsRecorder::new(), } } @@ -137,8 +135,8 @@ impl EnvInstance { self.chain_spec.reset(); self.blocks.clear(); self.console.reset(); - self.runtime_storage.reset(); - self.runtime_call_handler.reset(); + #[cfg(feature = "ink-unstable-chain-extensions")] + self.chain_extension_handler.reset(); self.emitted_events.reset(); } diff --git a/core/src/env/engine/off_chain/runtime_calls.rs b/core/src/env/engine/off_chain/runtime_calls.rs deleted file mode 100644 index 8296f4af416..00000000000 --- a/core/src/env/engine/off_chain/runtime_calls.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019-2020 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. - -use super::{ - OffCall, - OffChainError, -}; -use crate::env::{ - EnvTypes, - Result, -}; - -/// Runtime call handler. -/// -/// More generically a mapping from bytes to bytes. -pub struct RuntimeCallHandler { - /// The currently registered runtime call handler. - registered: Option>, -} - -impl RuntimeCallHandler { - /// Creates a new runtime call handler. - /// - /// Initialized without any handler. - pub fn new() -> Self { - Self { registered: None } - } - - /// Resets the runtime call handler to uninitialized state. - pub fn reset(&mut self) { - self.registered = None; - } - - /// Register a runtime call handler. - pub fn register(&mut self, mut f: F) - where - T: EnvTypes, - F: FnMut(::Call) + 'static, - { - self.registered = Some(Box::new(move |call: OffCall| { - f(call - .decode::<::Call>() - .expect("could not decode call")) - })); - } - - /// Invokes the runtime with the given parameters. - pub fn invoke(&mut self, params: &T::Call) -> Result<()> - where - T: EnvTypes, - { - match &mut self.registered { - Some(ref mut handler) => { - handler(OffCall::new(params)); - Ok(()) - } - None => Err(OffChainError::UnregisteredRuntimeCallHandler.into()), - } - } -} diff --git a/core/src/env/engine/off_chain/runtime_storage.rs b/core/src/env/engine/off_chain/runtime_storage.rs deleted file mode 100644 index 5b4b3c17d53..00000000000 --- a/core/src/env/engine/off_chain/runtime_storage.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019-2020 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. - -use crate::env::Result; -use ink_prelude::{ - collections::btree_map::BTreeMap, - vec::Vec, -}; - -/// Runtime storage. -/// -/// More generically a mapping from bytes to bytes. -pub struct RuntimeStorage { - /// The underlying storage mapping. - entries: BTreeMap, Vec>, -} - -impl RuntimeStorage { - /// Creates a new runtime storage. - pub fn new() -> Self { - Self { - entries: BTreeMap::new(), - } - } - - /// Resets the runtime storage to uninitialized state. - pub fn reset(&mut self) { - self.entries.clear(); - } - - /// Stores the value under the given key. - pub fn store(&mut self, key: Vec, value: T) - where - T: scale::Encode, - { - self.entries.insert(key, value.encode()); - } - - /// Loads the value under the given key if any. - pub fn load(&self, key: &[u8]) -> Option> - where - T: scale::Decode, - { - self.entries.get(key).map(|encoded| { - ::decode(&mut &encoded[..]).map_err(Into::into) - }) - } -} diff --git a/core/src/env/engine/off_chain/test_api.rs b/core/src/env/engine/off_chain/test_api.rs index e0372edcb65..322d5184062 100644 --- a/core/src/env/engine/off_chain/test_api.rs +++ b/core/src/env/engine/off_chain/test_api.rs @@ -17,7 +17,10 @@ pub use super::{ CallData, EmittedEvent, + db::ChainSpec, }; +#[cfg(feature = "ink-unstable-chain-extensions")] +use super::chain_extension::ChainExtension; use super::{ db::ExecContext, AccountError, @@ -27,7 +30,6 @@ use super::{ use crate::env::{ EnvTypes, Result, - engine::off_chain::db::ChainSpec, }; use ink_prelude::string::String; @@ -177,24 +179,16 @@ where }) } -/// Sets the runtime storage to value for the given key. -pub fn set_runtime_storage(key: &[u8], value: T) +/// Registers a new chain extension. +#[cfg(feature = "ink-unstable-chain-extensions")] +pub fn register_chain_extension(extension: E) where - T: scale::Encode, + E: ChainExtension + 'static, + I: scale::Codec + 'static, + O: scale::Codec + 'static, { ::on_instance(|instance| { - instance.runtime_storage.store(key.to_vec(), value) - }) -} - -/// Sets the call handler for runtime calls. -pub fn set_runtime_call_handler(f: F) -where - T: EnvTypes, - F: FnMut(::Call) + 'static, -{ - ::on_instance(|instance| { - instance.runtime_call_handler.register::(f) + instance.chain_extension_handler.register(Box::new(extension)); }) } diff --git a/core/src/env/engine/off_chain/tests.rs b/core/src/env/engine/off_chain/tests.rs index f3979b24592..39e0b1db895 100644 --- a/core/src/env/engine/off_chain/tests.rs +++ b/core/src/env/engine/off_chain/tests.rs @@ -22,14 +22,14 @@ use ink_primitives::Key; fn store_load_clear() -> Result<()> { env::test::run_test::(|_| { let key = Key::from([0x42; 32]); - assert_eq!(env::get_contract_storage::<()>(&key), None,); + assert_eq!(env::get_contract_storage::<()>(&key), Ok(None)); env::set_contract_storage(&key, &[0x05_u8; 5]); assert_eq!( env::get_contract_storage::<[i8; 5]>(&key), - Some(Ok([0x05; 5])), + Ok(Some([0x05; 5])), ); env::clear_contract_storage(&key); - assert_eq!(env::get_contract_storage::<[u8; 5]>(&key), None,); + assert_eq!(env::get_contract_storage::<[u8; 5]>(&key), Ok(None)); Ok(()) }) } @@ -42,9 +42,9 @@ fn key_add() -> Result<()> { let key10 = key00 + 10_u64; // -> 10 | same as key55 let key55 = key05 + 05_u64; // -> 5 + 5 = 10 | same as key10 env::set_contract_storage(&key55, &42); - assert_eq!(env::get_contract_storage::(&key10), Some(Ok(42))); + assert_eq!(env::get_contract_storage::(&key10), Ok(Some(42))); env::set_contract_storage(&key10, &1337); - assert_eq!(env::get_contract_storage::(&key55), Some(Ok(1337))); + assert_eq!(env::get_contract_storage::(&key55), Ok(Some(1337))); Ok(()) }) } @@ -77,9 +77,9 @@ fn gas_price() -> env::Result<()> { chain_spec.set_gas_price::(gas_price.into()) })?; - assert_eq!(2u128, env::gas_price::(1).unwrap()); - assert_eq!(20u128, env::gas_price::(10).unwrap()); - assert_eq!(6u128, env::gas_price::(3).unwrap()); + assert_eq!(2u128, env::weight_to_fee::(1).unwrap()); + assert_eq!(20u128, env::weight_to_fee::(10).unwrap()); + assert_eq!(6u128, env::weight_to_fee::(3).unwrap()); Ok(()) }) diff --git a/core/src/env/engine/off_chain/types.rs b/core/src/env/engine/off_chain/types.rs index 08c690a467c..a0a86398ee3 100644 --- a/core/src/env/engine/off_chain/types.rs +++ b/core/src/env/engine/off_chain/types.rs @@ -40,8 +40,6 @@ mod type_marker { #[derive(Debug, Clone)] pub enum OffTimestamp {} /// Type marker representing an environmental `BlockNumber`. #[derive(Debug, Clone)] pub enum BlockNumber {} - /// Type marker representing an environmental `Call`. - #[derive(Debug, Clone)] pub enum Call {} } /// Off-chain environment account ID type. @@ -54,5 +52,3 @@ pub type OffHash = TypedEncoded; pub type OffTimestamp = TypedEncoded; /// Off-chain environment block number type. pub type OffBlockNumber = TypedEncoded; -/// Off-chain environment call (runtime dispatch) type. -pub type OffCall = TypedEncoded; diff --git a/core/src/env/engine/on_chain/buffer.rs b/core/src/env/engine/on_chain/buffer.rs index 8deb5ccc68b..1d3ac4b3faa 100644 --- a/core/src/env/engine/on_chain/buffer.rs +++ b/core/src/env/engine/on_chain/buffer.rs @@ -16,9 +16,6 @@ pub struct StaticBuffer { /// The static buffer with a total capacity of 16kB. buffer: [u8; Self::CAPACITY], - /// The number of elements currently in use by the buffer - /// counting from the start. - len: usize, } impl StaticBuffer { @@ -29,38 +26,60 @@ impl StaticBuffer { pub const fn new() -> Self { Self { buffer: [0; Self::CAPACITY], - len: 0, } } +} - /// Returns the current length of the static buffer. - pub fn len(&self) -> usize { - self.len +impl core::ops::Index for StaticBuffer { + type Output = [u8]; + + fn index(&self, index: core::ops::RangeFull) -> &Self::Output { + core::ops::Index::index(&self.buffer[..], index) } +} - /// Resizes the static buffer to the given length. - /// - /// # Panics - /// - /// Panics for lengths greater than its capacity. - pub fn resize(&mut self, new_len: usize) { - if new_len > Self::CAPACITY { - panic!("static buffer overflowed") - } - self.len = new_len; +impl core::ops::IndexMut for StaticBuffer { + fn index_mut(&mut self, index: core::ops::RangeFull) -> &mut Self::Output { + core::ops::IndexMut::index_mut(&mut self.buffer[..], index) } +} - /// Resets the length of the buffer to 0. - pub fn clear(&mut self) { - self.len = 0; +struct EncodeScope<'a> { + buffer: &'a mut [u8], + len: usize, +} + +impl<'a> From<&'a mut [u8]> for EncodeScope<'a> { + fn from(buffer: &'a mut [u8]) -> Self { + Self { buffer, len: 0 } } } -impl scale::Output for StaticBuffer { +impl<'a> EncodeScope<'a> { + /// Returns the capacity of the encoded scope. + pub fn capacity(&self) -> usize { + self.buffer.len() + } + + /// Returns the length of the encoded scope. + pub fn len(&self) -> usize { + self.len + } + + /// Returns the internal mutable byte slice. + pub fn into_buffer(self) -> &'a mut [u8] { + self.buffer + } +} + +impl<'a> scale::Output for EncodeScope<'a> { fn write(&mut self, bytes: &[u8]) { - if self.len + bytes.len() > Self::CAPACITY { - panic!("static buffer overflowed") - } + debug_assert!( + self.len() + bytes.len() <= self.capacity(), + "encode scope buffer overflowed. capacity is {} but last write index is {}", + self.capacity(), + self.len() + bytes.len(), + ); let start = self.len; let len_bytes = bytes.len(); self.buffer[start..(start + len_bytes)].copy_from_slice(bytes); @@ -68,24 +87,72 @@ impl scale::Output for StaticBuffer { } fn push_byte(&mut self, byte: u8) { - if self.len == Self::CAPACITY { - panic!("static buffer overflowed") - } + debug_assert_ne!( + self.len(), + self.capacity(), + "encode scope buffer overflowed. capacity is {} and buffer is already full", + self.capacity(), + ); self.buffer[self.len] = byte; self.len += 1; } } -impl> core::ops::Index for StaticBuffer { - type Output = I::Output; +/// Scoped access to an underlying bytes buffer. +/// +/// # Note +/// +/// This is used to efficiently chunk up ink!'s internal static 16kB buffer +/// into smaller sub buffers for processing different parts of computations. +#[derive(Debug)] +pub struct ScopedBuffer<'a> { + buffer: &'a mut [u8], +} - fn index(&self, index: I) -> &Self::Output { - core::ops::Index::index(&self.buffer[..self.len], index) +impl<'a> From<&'a mut [u8]> for ScopedBuffer<'a> { + fn from(buffer: &'a mut [u8]) -> Self { + Self { buffer } } } -impl> core::ops::IndexMut for StaticBuffer { - fn index_mut(&mut self, index: I) -> &mut Self::Output { - core::ops::IndexMut::index_mut(&mut self.buffer[..self.len], index) +impl<'a> ScopedBuffer<'a> { + /// Returns the first `len` bytes of the buffer as mutable slice. + pub fn take(&mut self, len: usize) -> &'a mut [u8] { + assert!(len <= self.buffer.len()); + let len_before = self.buffer.len(); + let buffer = core::mem::take(&mut self.buffer); + let (lhs, rhs) = buffer.split_at_mut(len); + self.buffer = rhs; + debug_assert_eq!(lhs.len(), len); + let len_after = self.buffer.len(); + debug_assert_eq!(len_before - len_after, len); + lhs + } + + /// Returns a buffer scope filled with `bytes` with the proper length. + pub fn take_bytes(&mut self, bytes: &[u8]) -> &'a mut [u8] { + let buffer = self.take(bytes.len()); + buffer.copy_from_slice(bytes); + buffer + } + + /// Encode the given value into the scoped buffer and return the sub slice + /// containing all the encoded bytes. + pub fn take_encoded(&mut self, value: &T) -> &'a mut [u8] + where + T: scale::Encode, + { + let buffer = core::mem::take(&mut self.buffer); + let mut encode_scope = EncodeScope::from(buffer); + scale::Encode::encode_to(&value, &mut encode_scope); + let encode_len = encode_scope.len(); + let _ = core::mem::replace(&mut self.buffer, encode_scope.into_buffer()); + self.take(encode_len) + } + + /// Returns all of the remaining bytes of the buffer as mutable slice. + pub fn take_rest(self) -> &'a mut [u8] { + assert!(!self.buffer.is_empty()); + self.buffer } } diff --git a/core/src/env/engine/on_chain/ext.rs b/core/src/env/engine/on_chain/ext.rs index 98a27d84f6c..c995f88dc56 100644 --- a/core/src/env/engine/on_chain/ext.rs +++ b/core/src/env/engine/on_chain/ext.rs @@ -16,166 +16,400 @@ //! //! Refer to substrate SRML contract module for more documentation. -use crate::env::{ - EnvError, - Result, -}; +use crate::env::ReturnFlags; +use core::marker::PhantomData; use ink_primitives::Key; -/// Returned by the host environment if a contract call trapped. -const TRAP_RETURN_CODE: u32 = 0x0100; +macro_rules! define_error_codes { + ( + $( + $( #[$attr:meta] )* + $name:ident = $discr:literal, + )* + ) => { + /// Every error that can be returned to a contract when it calls any of the host functions. + #[repr(u32)] + pub enum Error { + $( + $( #[$attr] )* + $name = $discr, + )* + /// Returns if an unknown error was received from the host module. + UnknownError, + } + + impl From for Result { + #[inline] + fn from(return_code: ReturnCode) -> Self { + match return_code.0 { + 0 => Ok(()), + $( + $discr => Err(Error::$name), + )* + _ => Err(Error::UnknownError), + } + } + } + }; +} +define_error_codes! { + /// The called function trapped and has its state changes reverted. + /// In this case no output buffer is returned. + /// Can only be returned from `seal_call` and `seal_instantiate`. + CalleeTrapped = 1, + /// The called function ran to completion but decided to revert its state. + /// An output buffer is returned when one was supplied. + /// Can only be returned from `seal_call` and `seal_instantiate`. + CalleeReverted = 2, + /// The passed key does not exist in storage. + KeyNotFound = 3, + /// Transfer failed because it would have brought the sender's total balance + /// bwlow the subsistence threshold. + BelowSubsistenceThreshold = 4, + /// Transfer failed for other not further specified reason. Most probably + /// reserved or locked balance of the sender that was preventing the transfer. + TransferFailed = 5, + /// The newly created contract is below the subsistence threshold after executing + /// its constructor so no usable contract instance will be created. + NewContractNotFunded = 6, + /// No code could be found at the supplied code hash. + CodeNotFound = 7, + /// The account that was called is either no contract (e.g. user account) or is a tombstone. + NotCallable = 8, +} + +/// Thin-wrapper around a `u32` representing a pointer for Wasm32. +/// +/// Only for shared references. +/// +/// # Note +/// +/// Can only be constructed from shared reference types and encapsulates the +/// conversion from reference to raw `u32`. +/// Does not allow accessing the internal `u32` value. +#[derive(Debug)] +#[repr(transparent)] +pub struct Ptr32<'a, T> +where + T: ?Sized, +{ + /// The internal Wasm32 raw pointer value. + /// + /// Must not be readable or directly usable by any safe Rust code. + value: u32, + /// We handle types like these as if the associated lifetime was exclusive. + marker: PhantomData &'a T>, +} + +impl<'a, T> Ptr32<'a, T> +where + T: ?Sized, +{ + /// Creates a new Wasm32 pointer for the given raw pointer value. + fn new(value: u32) -> Self { + Self { + value, + marker: Default::default(), + } + } +} + +impl<'a, T> Ptr32<'a, [T]> { + /// Creates a new Wasm32 pointer from the given shared slice. + pub fn from_slice(slice: &'a [T]) -> Self { + Self::new(slice.as_ptr() as u32) + } +} + +/// Thin-wrapper around a `u32` representing a pointer for Wasm32. +/// +/// Only for exclusive references. +/// +/// # Note +/// +/// Can only be constructed from exclusive reference types and encapsulates the +/// conversion from reference to raw `u32`. +/// Does not allow accessing the internal `u32` value. +#[derive(Debug)] +#[repr(transparent)] +pub struct Ptr32Mut<'a, T> +where + T: ?Sized, +{ + /// The internal Wasm32 raw pointer value. + /// + /// Must not be readable or directly usable by any safe Rust code. + value: u32, + /// We handle types like these as if the associated lifetime was exclusive. + marker: PhantomData &'a mut T>, +} + +impl<'a, T> Ptr32Mut<'a, T> +where + T: ?Sized, +{ + /// Creates a new Wasm32 pointer for the given raw pointer value. + fn new(value: u32) -> Self { + Self { + value, + marker: Default::default(), + } + } +} + +impl<'a, T> Ptr32Mut<'a, [T]> { + /// Creates a new Wasm32 pointer from the given exclusive slice. + pub fn from_slice(slice: &'a mut [T]) -> Self { + Self::new(slice.as_ptr() as u32) + } +} + +impl<'a, T> Ptr32Mut<'a, T> +where + T: Sized, +{ + /// Creates a new Wasm32 pointer from the given exclusive reference. + pub fn from_ref(a_ref: &'a mut T) -> Self { + let a_ptr: *mut T = a_ref; + Self::new(a_ptr as u32) + } +} + +/// The raw return code returned by the host side. +#[repr(transparent)] +pub struct ReturnCode(u32); + +type Result = core::result::Result<(), Error>; mod sys { + use super::{ + Key, + Ptr32, + Ptr32Mut, + ReturnCode, + }; + + #[link(wasm_import_module = "seal0")] extern "C" { - pub fn ext_instantiate( - init_code_ptr: u32, + pub fn seal_instantiate( + init_code_ptr: Ptr32<[u8]>, init_code_len: u32, gas: u64, - value_ptr: u32, - value_len: u32, - input_data_ptr: u32, - input_data_len: u32, - ) -> u32; - - pub fn ext_call( - callee_ptr: u32, + endowment_ptr: Ptr32<[u8]>, + endowment_len: u32, + input_ptr: Ptr32<[u8]>, + input_len: u32, + address_ptr: Ptr32Mut<[u8]>, + address_len_ptr: Ptr32Mut, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ) -> ReturnCode; + + pub fn seal_call( + callee_ptr: Ptr32<[u8]>, callee_len: u32, gas: u64, - value_ptr: u32, - value_len: u32, - input_data_ptr: u32, - input_data_len: u32, - ) -> u32; - - pub fn ext_transfer( - account_id_ptr: u32, + transferred_value_ptr: Ptr32<[u8]>, + transferred_value_len: u32, + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ) -> ReturnCode; + + pub fn seal_transfer( + account_id_ptr: Ptr32<[u8]>, account_id_len: u32, - value_ptr: u32, - value_len: u32, - ) -> u32; + transferred_value_ptr: Ptr32<[u8]>, + transferred_value_len: u32, + ) -> ReturnCode; - pub fn ext_deposit_event( - topics_ptr: u32, + pub fn seal_deposit_event( + topics_ptr: Ptr32<[u8]>, topics_len: u32, - data_ptr: u32, + data_ptr: Ptr32<[u8]>, data_len: u32, ); - pub fn ext_set_storage(key_ptr: u32, value_ptr: u32, value_len: u32); - pub fn ext_clear_storage(key_ptr: u32); - pub fn ext_get_storage(key_ptr: u32) -> u32; - - pub fn ext_get_runtime_storage(key_ptr: u32, key_len: u32) -> u32; + pub fn seal_set_storage(key_ptr: Ptr32<[u8]>, value_ptr: Ptr32<[u8]>, value_len: u32); + pub fn seal_get_storage( + key_ptr: Ptr32<[u8]>, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ) -> ReturnCode; + pub fn seal_clear_storage(key_ptr: Ptr32<[u8]>); - pub fn ext_restore_to( - dest_ptr: u32, + pub fn seal_restore_to( + dest_ptr: Ptr32<[u8]>, dest_len: u32, - code_hash_ptr: u32, + code_hash_ptr: Ptr32<[u8]>, code_hash_len: u32, - rent_allowance_ptr: u32, + rent_allowance_ptr: Ptr32<[u8]>, rent_allowance_len: u32, - delta_ptr: u32, + delta_ptr: Ptr32<[Key]>, delta_count: u32, ); - pub fn ext_terminate(beneficiary_ptr: u32, beneficiary_len: u32) -> !; - - pub fn ext_dispatch_call(call_ptr: u32, call_len: u32); - - pub fn ext_scratch_size() -> u32; - pub fn ext_scratch_read(dst_ptr: u32, offset: u32, len: u32); - pub fn ext_scratch_write(src_ptr: u32, len: u32); - - pub fn ext_caller(); - pub fn ext_block_number(); - pub fn ext_address(); - pub fn ext_balance(); - pub fn ext_gas_price(gas: u64); - pub fn ext_gas_left(); - pub fn ext_value_transferred(); - pub fn ext_now(); - pub fn ext_rent_allowance(); - pub fn ext_minimum_balance(); - pub fn ext_tombstone_deposit(); - - pub fn ext_set_rent_allowance(value_ptr: u32, value_len: u32); - - pub fn ext_random_seed(subject_ptr: u32, subject_len: u32); - pub fn ext_println(str_ptr: u32, str_len: u32); - - pub fn ext_hash_keccak_256(input_ptr: u32, input_len: u32, output_ptr: u32); - pub fn ext_hash_blake2_256(input_ptr: u32, input_len: u32, output_ptr: u32); - pub fn ext_hash_blake2_128(input_ptr: u32, input_len: u32, output_ptr: u32); - pub fn ext_hash_sha2_256(input_ptr: u32, input_len: u32, output_ptr: u32); + pub fn seal_terminate(beneficiary_ptr: Ptr32<[u8]>, beneficiary_len: u32) -> !; + + #[cfg(feature = "ink-unstable-chain-extensions")] + pub fn seal_call_chain_extension( + func_id: u32, + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ) -> ReturnCode; + + pub fn seal_input(buf_ptr: Ptr32Mut<[u8]>, buf_len_ptr: Ptr32Mut); + pub fn seal_return(flags: u32, data_ptr: Ptr32<[u8]>, data_len: u32) -> !; + + pub fn seal_caller(output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut); + pub fn seal_block_number( + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_address(output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut); + pub fn seal_balance(output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut); + pub fn seal_weight_to_fee( + gas: u64, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_gas_left(output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut); + pub fn seal_value_transferred( + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_now(output_ptr: Ptr32Mut<[u8]>, output_len_ptr: Ptr32Mut); + pub fn seal_rent_allowance( + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_minimum_balance( + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_tombstone_deposit( + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + + pub fn seal_set_rent_allowance(value_ptr: Ptr32<[u8]>, value_len: u32); + + pub fn seal_random( + subject_ptr: Ptr32<[u8]>, + subject_len: u32, + output_ptr: Ptr32Mut<[u8]>, + output_len_ptr: Ptr32Mut, + ); + pub fn seal_println(str_ptr: Ptr32<[u8]>, str_len: u32); + + pub fn seal_hash_keccak_256( + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + ); + pub fn seal_hash_blake2_256( + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + ); + pub fn seal_hash_blake2_128( + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + ); + pub fn seal_hash_sha2_256( + input_ptr: Ptr32<[u8]>, + input_len: u32, + output_ptr: Ptr32Mut<[u8]>, + ); } } -pub fn create( +fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { + debug_assert!(new_len <= output.len()); + let tmp = core::mem::take(output); + *output = &mut tmp[..new_len]; +} + +pub fn instantiate( code_hash: &[u8], gas_limit: u64, - value: &[u8], - create_data: &[u8], -) -> Result<()> { - let ret_code = unsafe { - sys::ext_instantiate( - code_hash.as_ptr() as u32, - code_hash.len() as u32, - gas_limit, - value.as_ptr() as u32, - value.len() as u32, - create_data.as_ptr() as u32, - create_data.len() as u32, - ) + endowment: &[u8], + input: &[u8], + out_address: &mut &mut [u8], + out_return_value: &mut &mut [u8], +) -> Result { + let mut address_len = out_address.len() as u32; + let mut return_value_len = out_return_value.len() as u32; + let ret_code = { + unsafe { + sys::seal_instantiate( + Ptr32::from_slice(code_hash), + code_hash.len() as u32, + gas_limit, + Ptr32::from_slice(endowment), + endowment.len() as u32, + Ptr32::from_slice(input), + input.len() as u32, + Ptr32Mut::from_slice(out_address), + Ptr32Mut::from_ref(&mut address_len), + Ptr32Mut::from_slice(out_return_value), + Ptr32Mut::from_ref(&mut return_value_len), + ) + } }; - match ret_code { - 0 => Ok(()), - c if c == TRAP_RETURN_CODE => Err(EnvError::ContractInstantiationTrapped), - err if err <= 0xFF => Err(EnvError::ContractInstantiationFailState(err as u8)), - _unknown => panic!("encountered unknown error code upon contract call"), - } + extract_from_slice(out_address, address_len as usize); + extract_from_slice(out_return_value, return_value_len as usize); + ret_code.into() } -pub fn call(callee: &[u8], gas_limit: u64, value: &[u8], call_data: &[u8]) -> Result<()> { - let ret_code = unsafe { - sys::ext_call( - callee.as_ptr() as u32, - callee.len() as u32, - gas_limit, - value.as_ptr() as u32, - value.len() as u32, - call_data.as_ptr() as u32, - call_data.len() as u32, - ) +pub fn call( + callee: &[u8], + gas_limit: u64, + value: &[u8], + input: &[u8], + output: &mut &mut [u8], +) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::seal_call( + Ptr32::from_slice(callee), + callee.len() as u32, + gas_limit, + Ptr32::from_slice(value), + value.len() as u32, + Ptr32::from_slice(input), + input.len() as u32, + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + } }; - match ret_code { - 0 => Ok(()), - c if c == TRAP_RETURN_CODE => Err(EnvError::ContractInstantiationTrapped), - err if err <= 0xFF => Err(EnvError::ContractInstantiationFailState(err as u8)), - _unknown => panic!("encountered unknown error code upon contract call"), - } + extract_from_slice(output, output_len as usize); + ret_code.into() } -pub fn transfer(account_id: &[u8], value: &[u8]) -> Result<()> { +pub fn transfer(account_id: &[u8], value: &[u8]) -> Result { let ret_code = unsafe { - sys::ext_transfer( - account_id.as_ptr() as u32, + sys::seal_transfer( + Ptr32::from_slice(account_id), account_id.len() as u32, - value.as_ptr() as u32, + Ptr32::from_slice(value), value.len() as u32, ) }; - match ret_code { - 0 => Ok(()), - 1 => Err(EnvError::TransferCallFailed), - _unknown => panic!("encountered unknown error code upon transfer"), - } + ret_code.into() } pub fn deposit_event(topics: &[u8], data: &[u8]) { unsafe { - sys::ext_deposit_event( - topics.as_ptr() as u32, + sys::seal_deposit_event( + Ptr32::from_slice(topics), topics.len() as u32, - data.as_ptr() as u32, + Ptr32::from_slice(data), data.len() as u32, ) } @@ -183,39 +417,31 @@ pub fn deposit_event(topics: &[u8], data: &[u8]) { pub fn set_storage(key: &[u8], encoded_value: &[u8]) { unsafe { - sys::ext_set_storage( - key.as_ptr() as u32, - encoded_value.as_ptr() as u32, + sys::seal_set_storage( + Ptr32::from_slice(key), + Ptr32::from_slice(encoded_value), encoded_value.len() as u32, ) } } pub fn clear_storage(key: &[u8]) { - unsafe { sys::ext_clear_storage(key.as_ptr() as u32) } -} - -pub fn get_storage(key: &[u8]) -> Result<()> { - let ret_code = unsafe { sys::ext_get_storage(key.as_ptr() as u32) }; - match ret_code { - 0 => Ok(()), - 1 => Err(EnvError::MissingContractStorageEntry), - _unknown => panic!("encountered unexpected return code"), - } + unsafe { sys::seal_clear_storage(Ptr32::from_slice(key)) } } -pub fn get_runtime_storage(runtime_key: &[u8]) -> Result<()> { - let ret_code = unsafe { - sys::ext_get_runtime_storage( - runtime_key.as_ptr() as u32, - runtime_key.len() as u32, - ) +pub fn get_storage(key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::seal_get_storage( + Ptr32::from_slice(key), + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + } }; - match ret_code { - 0 => Ok(()), - 1 => Err(EnvError::MissingRuntimeStorageEntry), - _unknown => panic!("encountered unsupported return code"), - } + extract_from_slice(output, output_len as usize); + ret_code.into() } /// Restores a tombstone to the original smart contract. @@ -236,78 +462,137 @@ pub fn restore_to( filtered_keys: &[Key], ) { unsafe { - sys::ext_restore_to( - account_id.as_ptr() as u32, + sys::seal_restore_to( + Ptr32::from_slice(account_id), account_id.len() as u32, - code_hash.as_ptr() as u32, + Ptr32::from_slice(code_hash), code_hash.len() as u32, - rent_allowance.as_ptr() as u32, + Ptr32::from_slice(rent_allowance), rent_allowance.len() as u32, - filtered_keys.as_ptr() as u32, + Ptr32::from_slice(filtered_keys), filtered_keys.len() as u32, ) } } pub fn terminate(beneficiary: &[u8]) -> ! { - unsafe { sys::ext_terminate(beneficiary.as_ptr() as u32, beneficiary.len() as u32) } -} - -pub fn dispatch_call(call: &[u8]) { - unsafe { sys::ext_dispatch_call(call.as_ptr() as u32, call.len() as u32) } + unsafe { + sys::seal_terminate(Ptr32::from_slice(beneficiary), beneficiary.len() as u32) + } } -pub fn scratch_size() -> usize { - (unsafe { sys::ext_scratch_size() }) as usize +#[cfg(feature = "ink-unstable-chain-extensions")] +pub fn call_chain_extension( + func_id: u32, + input: &[u8], + output: &mut &mut [u8], +) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::seal_call_chain_extension( + func_id, + Ptr32::from_slice(input), + input.len() as u32, + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() } -pub fn scratch_read(dest: &mut [u8], offset: u32) { - unsafe { sys::ext_scratch_read(dest.as_mut_ptr() as u32, offset, dest.len() as u32) } +pub fn input(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::seal_input( + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + }; + } + extract_from_slice(output, output_len as usize); } -pub fn scratch_write(src: &[u8]) { - unsafe { sys::ext_scratch_write(src.as_ptr() as u32, src.len() as u32) } +pub fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + unsafe { + sys::seal_return( + flags.into_u32(), + Ptr32::from_slice(return_value), + return_value.len() as u32, + ) + } } -macro_rules! impl_ext_wrapper_for { - ( $( ($name:ident => $ext_name:ident), )* ) => { +macro_rules! impl_seal_wrapper_for { + ( $( ($name:ident => $seal_name:ident), )* ) => { $( - pub fn $name() { - unsafe { - sys::$ext_name() + pub fn $name(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::$seal_name( + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + }; } + extract_from_slice(output, output_len as usize); } )* } } -impl_ext_wrapper_for! { - (caller => ext_caller), - (block_number => ext_block_number), - (address => ext_address), - (balance => ext_balance), - (gas_left => ext_gas_left), - (value_transferred => ext_value_transferred), - (now => ext_now), - (rent_allowance => ext_rent_allowance), - (minimum_balance => ext_minimum_balance), - (tombstone_deposit => ext_tombstone_deposit), +impl_seal_wrapper_for! { + (caller => seal_caller), + (block_number => seal_block_number), + (address => seal_address), + (balance => seal_balance), + (gas_left => seal_gas_left), + (value_transferred => seal_value_transferred), + (now => seal_now), + (rent_allowance => seal_rent_allowance), + (minimum_balance => seal_minimum_balance), + (tombstone_deposit => seal_tombstone_deposit), } -pub fn gas_price(gas: u64) { - unsafe { sys::ext_gas_price(gas) } +pub fn weight_to_fee(gas: u64, output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::seal_weight_to_fee( + gas, + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + }; + } + extract_from_slice(output, output_len as usize); } pub fn set_rent_allowance(value: &[u8]) { - unsafe { sys::ext_set_rent_allowance(value.as_ptr() as u32, value.len() as u32) } + unsafe { sys::seal_set_rent_allowance(Ptr32::from_slice(value), value.len() as u32) } } -pub fn random_seed(subject: &[u8]) { - unsafe { sys::ext_random_seed(subject.as_ptr() as u32, subject.len() as u32) } +pub fn random(subject: &[u8], output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::seal_random( + Ptr32::from_slice(subject), + subject.len() as u32, + Ptr32Mut::from_slice(output), + Ptr32Mut::from_ref(&mut output_len), + ) + }; + } + extract_from_slice(output, output_len as usize); } pub fn println(content: &str) { let bytes = content.as_bytes(); - unsafe { sys::ext_println(bytes.as_ptr() as u32, bytes.len() as u32) } + unsafe { sys::seal_println(Ptr32::from_slice(bytes), bytes.len() as u32) } } macro_rules! impl_hash_fn { @@ -315,10 +600,10 @@ macro_rules! impl_hash_fn { paste::item! { pub fn [](input: &[u8], output: &mut [u8; $bytes_result]) { unsafe { - sys::[]( - input.as_ptr() as u32, + sys::[]( + Ptr32::from_slice(input), input.len() as u32, - output.as_ptr() as u32, + Ptr32Mut::from_slice(output), ) } } diff --git a/core/src/env/engine/on_chain/impls.rs b/core/src/env/engine/on_chain/impls.rs index ea09bec3275..7efec33f8d9 100644 --- a/core/src/env/engine/on_chain/impls.rs +++ b/core/src/env/engine/on_chain/impls.rs @@ -15,6 +15,8 @@ use super::{ ext, EnvInstance, + Error as ExtError, + ScopedBuffer, }; use crate::env::{ call::{ @@ -23,110 +25,72 @@ use crate::env::{ ReturnType, }, Env, + EnvError, EnvTypes, Result, + ReturnFlags, Topics, TypedEnv, }; use ink_primitives::Key; -impl EnvInstance { - /// Empties the contract-side scratch buffer. - /// - /// # Note - /// - /// This is useful to perform before invoking a series of - /// [`WasmEnv::append_encode_into_buffer`]. - fn reset_buffer(&mut self) { - self.buffer.clear(); - } - - /// Resizes the amount of used bytes of the internal buffer. - fn resize_buffer(&mut self, new_len: usize) { - self.buffer.resize(new_len); - } - - /// Reads the current scratch buffer into the contract-side buffer. - /// - /// Returns the amount of bytes read. - fn read_scratch_buffer(&mut self) -> usize { - let req_len = ext::scratch_size(); - self.resize_buffer(req_len); - ext::scratch_read(&mut self.buffer[0..req_len], 0); - req_len - } - - /// Reads from the scratch buffer and directly decodes into a value of `T`. - /// - /// # Errors - /// - /// If the decoding into a value of `T` failed. - fn decode_scratch_buffer(&mut self) -> Result - where - T: scale::Decode, - { - let req_len = self.read_scratch_buffer(); - scale::Decode::decode(&mut &self.buffer[0..req_len]).map_err(Into::into) - } - - /// Encodes the value into the contract-side scratch buffer. - fn encode_into_buffer(&mut self, value: T) - where - T: scale::Encode, - { - self.reset_buffer(); - scale::Encode::encode_to(&value, &mut self.buffer); +impl From for EnvError { + fn from(ext_error: ext::Error) -> Self { + match ext_error { + ext::Error::UnknownError => Self::UnknownError, + ext::Error::CalleeTrapped => Self::CalleeTrapped, + ext::Error::CalleeReverted => Self::CalleeReverted, + ext::Error::KeyNotFound => Self::KeyNotFound, + ext::Error::BelowSubsistenceThreshold => Self::BelowSubsistenceThreshold, + ext::Error::TransferFailed => Self::TransferFailed, + ext::Error::NewContractNotFunded => Self::NewContractNotFunded, + ext::Error::CodeNotFound => Self::CodeNotFound, + ext::Error::NotCallable => Self::NotCallable, + } } +} - /// Appends the encoded value into the contract-side scratch buffer - /// and returns the byte ranges into the encoded region. - fn append_encode_into_buffer(&mut self, value: T) -> core::ops::Range - where - T: scale::Encode, - { - let start = self.buffer.len(); - scale::Encode::encode_to(&value, &mut self.buffer); - let end = self.buffer.len(); - core::ops::Range { start, end } +impl EnvInstance { + /// Returns a new scoped buffer for the entire scope of the static 16kB buffer. + fn scoped_buffer(&mut self) -> ScopedBuffer { + ScopedBuffer::from(&mut self.buffer[..]) } /// Returns the contract property value. - fn get_property(&mut self, ext_fn: fn()) -> Result + fn get_property(&mut self, ext_fn: fn(output: &mut &mut [u8])) -> Result where T: scale::Decode, { - ext_fn(); - self.decode_scratch_buffer().map_err(Into::into) + let full_scope = &mut self.scoped_buffer().take_rest(); + ext_fn(full_scope); + scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) } /// Reusable implementation for invoking another contract message. - fn invoke_contract_impl( + fn invoke_contract_impl( &mut self, - call_params: &CallParams, - ) -> Result<()> + params: &CallParams, + ) -> Result where T: EnvTypes, Args: scale::Encode, + R: scale::Decode, { - // Reset the contract-side buffer to append onto clean slate. - self.reset_buffer(); - // Append the encoded `call_data`, `endowment` and `call_data` - // in order and remember their encoded regions within the buffer. - let callee = self.append_encode_into_buffer(call_params.callee()); - let transferred_value = - self.append_encode_into_buffer(call_params.transferred_value()); - let call_data = self.append_encode_into_buffer(call_params.input_data()); - // Resolve the encoded regions into actual byte slices. - let callee = &self.buffer[callee]; - let transferred_value = &self.buffer[transferred_value]; - let call_data = &self.buffer[call_data]; - // Perform the actual contract call. + let mut scope = self.scoped_buffer(); + let gas_limit = params.gas_limit(); + let enc_callee = scope.take_encoded(params.callee()); + let enc_transferred_value = scope.take_encoded(params.transferred_value()); + let enc_input = scope.take_encoded(params.input_data()); + let output = &mut scope.take_rest(); ext::call( - callee, - call_params.gas_limit(), - transferred_value, - call_data, - ) + enc_callee, + gas_limit, + enc_transferred_value, + enc_input, + output, + )?; + let decoded = scale::Decode::decode(&mut &output[..])?; + Ok(decoded) } } @@ -135,47 +99,42 @@ impl Env for EnvInstance { where V: scale::Encode, { - self.encode_into_buffer(value); - ext::set_storage(key.as_bytes(), &self.buffer[..]); + let buffer = self.scoped_buffer().take_encoded(value); + ext::set_storage(key.as_bytes(), &buffer[..]); } - fn get_contract_storage(&mut self, key: &Key) -> Option> + fn get_contract_storage(&mut self, key: &Key) -> Result> where R: scale::Decode, { - if ext::get_storage(key.as_bytes()).is_err() { - return None + let output = &mut self.scoped_buffer().take_rest(); + match ext::get_storage(key.as_bytes(), output) { + Ok(_) => (), + Err(ExtError::KeyNotFound) => return Ok(None), + Err(_) => panic!("encountered unexpected error"), } - Some(self.decode_scratch_buffer().map_err(Into::into)) + let decoded = scale::Decode::decode(&mut &output[..])?; + Ok(Some(decoded)) } fn clear_contract_storage(&mut self, key: &Key) { ext::clear_storage(key.as_bytes()) } - fn get_runtime_storage(&mut self, runtime_key: &[u8]) -> Option> - where - R: scale::Decode, - { - if ext::get_runtime_storage(runtime_key).is_err() { - return None - } - Some(self.decode_scratch_buffer().map_err(Into::into)) - } - fn decode_input(&mut self) -> Result where T: scale::Decode, { - self.get_property::(|| ()) + self.get_property::(ext::input) } - fn output(&mut self, return_value: &R) + fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! where R: scale::Encode, { - self.encode_into_buffer(return_value); - ext::scratch_write(&self.buffer[..]); + let mut scope = self.scoped_buffer(); + let enc_return_value = scope.take_encoded(return_value); + ext::return_value(flags, enc_return_value); } fn println(&mut self, content: &str) { @@ -197,6 +156,23 @@ impl Env for EnvInstance { fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) { ext::hash_sha2_256(input, output) } + + #[cfg(feature = "ink-unstable-chain-extensions")] + fn call_chain_extension( + &mut self, + func_id: u32, + input: &I, + ) -> Result + where + I: scale::Encode, + O: scale::Decode, + { + let mut scope = self.scoped_buffer(); + let enc_input = scope.take_encoded(input); + let output = &mut scope.take_rest(); + ext::call_chain_extension(func_id, enc_input, output)?; + scale::Decode::decode(&mut &output[..]).map_err(Into::into) + } } impl TypedEnv for EnvInstance { @@ -245,34 +221,18 @@ impl TypedEnv for EnvInstance { T: EnvTypes, Event: Topics + scale::Encode, { - // Reset the contract-side buffer to append onto clean slate. - self.reset_buffer(); - // Append the encoded `topics` and the raw encoded `data` - // in order and remember their encoded regions within the buffer. - let topics = self.append_encode_into_buffer(event.topics()); - let data = self.append_encode_into_buffer(event); - // Resolve the encoded regions into actual byte slices. - let topics = &self.buffer[topics]; - let data = &self.buffer[data]; - // Do the actual depositing of the event. - ext::deposit_event(topics, data); + let mut scope = self.scoped_buffer(); + let enc_topics = scope.take_encoded(&event.topics()); + let enc_data = scope.take_encoded(&event); + ext::deposit_event(enc_topics, enc_data); } fn set_rent_allowance(&mut self, new_value: T::Balance) where T: EnvTypes, { - self.encode_into_buffer(&new_value); - ext::set_rent_allowance(&self.buffer[..]) - } - - fn invoke_runtime(&mut self, call: &T::Call) -> Result<()> - where - T: EnvTypes, - { - self.encode_into_buffer(call); - ext::dispatch_call(&self.buffer[..]); - Ok(()) + let buffer = self.scoped_buffer().take_encoded(&new_value); + ext::set_rent_allowance(&buffer[..]) } fn invoke_contract( @@ -295,8 +255,7 @@ impl TypedEnv for EnvInstance { Args: scale::Encode, R: scale::Decode, { - self.invoke_contract_impl(call_params)?; - self.decode_scratch_buffer().map_err(Into::into) + self.invoke_contract_impl(call_params) } fn instantiate_contract( @@ -307,23 +266,30 @@ impl TypedEnv for EnvInstance { T: EnvTypes, Args: scale::Encode, { - // Reset the contract-side buffer to append onto clean slate. - self.reset_buffer(); - // Append the encoded `code_hash`, `endowment` and `create_data` - // in order and remember their encoded regions within the buffer. - let code_hash = self.append_encode_into_buffer(params.code_hash()); - let endowment = self.append_encode_into_buffer(params.endowment()); - let create_data = self.append_encode_into_buffer(params.input_data()); - // Resolve the encoded regions into actual byte slices. - let code_hash = &self.buffer[code_hash]; - let endowment = &self.buffer[endowment]; - let create_data = &self.buffer[create_data]; - // Do the actual contract instantiation. - ext::create(code_hash, params.gas_limit(), endowment, create_data)?; - // At this point our contract instantiation was successful - // and we can now fetch the returned data and decode it for - // the result value. - self.decode_scratch_buffer().map_err(Into::into) + let mut scoped = self.scoped_buffer(); + let gas_limit = params.gas_limit(); + let enc_code_hash = scoped.take_encoded(params.code_hash()); + let enc_endowment = scoped.take_encoded(params.endowment()); + let enc_input = scoped.take_encoded(params.input_data()); + // We support `AccountId` types with an encoding that requires up to + // 1024 bytes. Beyond that limit ink! contracts will trap for now. + // In the default configuration encoded `AccountId` require 32 bytes. + let out_address = &mut scoped.take(1024); + let out_return_value = &mut scoped.take_rest(); + // We currently do nothing with the `out_return_value` buffer. + // This should change in the future but for that we need to add support + // for constructors that may return values. + // This is useful to support fallible constructors for example. + ext::instantiate( + enc_code_hash, + gas_limit, + enc_endowment, + enc_input, + out_address, + out_return_value, + )?; + let account_id = scale::Decode::decode(&mut &out_address[..])?; + Ok(account_id) } fn restore_contract( @@ -335,57 +301,50 @@ impl TypedEnv for EnvInstance { ) where T: EnvTypes, { - // Reset the contract-side buffer to append onto clean slate. - self.reset_buffer(); - // Append the encoded `account_id`, `code_hash` and `rent_allowance` - // and `filtered_keys` in order and remember their encoded regions - // within the buffer. - let account_id = self.append_encode_into_buffer(account_id); - let code_hash = self.append_encode_into_buffer(code_hash); - let rent_allowance = self.append_encode_into_buffer(rent_allowance); - // Resolve the encoded regions into actual byte slices. - let account_id = &self.buffer[account_id]; - let code_hash = &self.buffer[code_hash]; - let rent_allowance = &self.buffer[rent_allowance]; - // Perform the actual contract restoration. - ext::restore_to(account_id, code_hash, rent_allowance, filtered_keys); + let mut scope = self.scoped_buffer(); + let enc_account_id = scope.take_encoded(&account_id); + let enc_code_hash = scope.take_encoded(&code_hash); + let enc_rent_allowance = scope.take_encoded(&rent_allowance); + ext::restore_to( + enc_account_id, + enc_code_hash, + enc_rent_allowance, + filtered_keys, + ); } fn terminate_contract(&mut self, beneficiary: T::AccountId) -> ! where T: EnvTypes, { - self.encode_into_buffer(beneficiary); - ext::terminate(&self.buffer[..]); + let buffer = self.scoped_buffer().take_encoded(&beneficiary); + ext::terminate(&buffer[..]); } fn transfer(&mut self, destination: T::AccountId, value: T::Balance) -> Result<()> where T: EnvTypes, { - // Reset the contract-side buffer to append onto clean slate. - self.reset_buffer(); - // Append the encoded `destination` and `value` in order and remember - // their encoded regions within the buffer. - let destination = self.append_encode_into_buffer(destination); - let value = self.append_encode_into_buffer(value); - // Resolve the encoded regions into actual byte slices. - let destination = &self.buffer[destination]; - let value = &self.buffer[value]; - // Perform the actual transfer call. - ext::transfer(destination, value) + let mut scope = self.scoped_buffer(); + let enc_destination = scope.take_encoded(&destination); + let enc_value = scope.take_encoded(&value); + ext::transfer(enc_destination, enc_value).map_err(Into::into) } - fn gas_price(&mut self, gas: u64) -> Result { - ext::gas_price(gas); - self.decode_scratch_buffer().map_err(Into::into) + fn weight_to_fee(&mut self, gas: u64) -> Result { + let output = &mut self.scoped_buffer().take_rest(); + ext::weight_to_fee(gas, output); + scale::Decode::decode(&mut &output[..]).map_err(Into::into) } fn random(&mut self, subject: &[u8]) -> Result where T: EnvTypes, { - ext::random_seed(subject); - self.decode_scratch_buffer().map_err(Into::into) + let mut scope = self.scoped_buffer(); + let enc_subject = scope.take_bytes(subject); + let output = &mut scope.take_rest(); + ext::random(enc_subject, output); + scale::Decode::decode(&mut &output[..]).map_err(Into::into) } } diff --git a/core/src/env/engine/on_chain/mod.rs b/core/src/env/engine/on_chain/mod.rs index c97706c2858..7fe591e8999 100644 --- a/core/src/env/engine/on_chain/mod.rs +++ b/core/src/env/engine/on_chain/mod.rs @@ -16,10 +16,15 @@ mod buffer; mod ext; mod impls; +use self::{ + buffer::{ + ScopedBuffer, + StaticBuffer, + }, + ext::Error, +}; use super::OnInstance; -use self::buffer::StaticBuffer; - /// The on-chain environment. pub struct EnvInstance { /// Encode & decode buffer with static size of 16kB. diff --git a/core/src/env/error.rs b/core/src/env/error.rs index 97875063ece..ee9850c4b52 100644 --- a/core/src/env/error.rs +++ b/core/src/env/error.rs @@ -26,21 +26,26 @@ pub enum EnvError { #[cfg(any(feature = "std", test, doc))] OffChain(OffChainError), /// The call to another contract has trapped. - ContractCallTrapped, - /// A called contract returned a custom error code. - #[from(ignore)] - ContractCallFailState(u8), - /// The instantiation of another contract has trapped. - ContractInstantiationTrapped, - /// The instantiated contract returned a custom error code. - #[from(ignore)] - ContractInstantiationFailState(u8), - /// The queried runtime storage entry is missing. - MissingRuntimeStorageEntry, + CalleeTrapped, + /// The call to another contract has been reverted. + CalleeReverted, /// The queried contract storage entry is missing. - MissingContractStorageEntry, - /// A call to transfer value from the contract failed. - TransferCallFailed, + KeyNotFound, + /// Transfer failed because it would have brought the sender's total balance + /// bwlow the subsistence threshold. + BelowSubsistenceThreshold, + /// Transfer failed for other not further specified reason. Most probably + /// reserved or locked balance of the sender that was preventing the transfer. + TransferFailed, + /// The newly created contract is below the subsistence threshold after executing + /// its constructor so no usable contract instance will be created. + NewContractNotFunded, + /// No code could be found at the supplied code hash. + CodeNotFound, + /// The account that was called is either no contract (e.g. user account) or is a tombstone. + NotCallable, + /// An unknown error has occured. + UnknownError, } /// A result of environmental operations. diff --git a/core/src/env/mod.rs b/core/src/env/mod.rs index 2b448c740b6..87740720e44 100644 --- a/core/src/env/mod.rs +++ b/core/src/env/mod.rs @@ -38,6 +38,7 @@ use self::backend::{ }; pub use self::{ api::*, + backend::ReturnFlags, error::{ EnvError, Result, diff --git a/core/src/lib.rs b/core/src/lib.rs index 4b34618b99c..818b89462fb 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -43,6 +43,17 @@ unused_extern_crates )] +#[cfg(all(not(feature = "std"), target_arch = "wasm32"))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // SAFETY: We only use this operation if we are guaranteed to be in Wasm32 compilation. + // This is used in order to make any panic a direct abort avoiding Rust's general + // panic infrastructure. + unsafe { + core::arch::wasm32::unreachable(); + } +} + // This extern crate definition is required since otherwise rustc // is not recognizing its allocator and panic handler definitions. #[cfg(not(feature = "std"))] diff --git a/core/src/storage2/traits/mod.rs b/core/src/storage2/traits/mod.rs index 90495ed31c1..1284a321353 100644 --- a/core/src/storage2/traits/mod.rs +++ b/core/src/storage2/traits/mod.rs @@ -137,8 +137,8 @@ where T: PackedLayout, { let mut entity = crate::env::get_contract_storage::(root_key) - .expect("storage entry was empty") - .expect("could not properly decode storage entry"); + .expect("could not properly decode storage entry") + .expect("storage entry was empty"); ::pull_packed(&mut entity, root_key); entity } diff --git a/core/src/storage2/traits/optspec.rs b/core/src/storage2/traits/optspec.rs index 8137f354c7c..a21fbf22002 100644 --- a/core/src/storage2/traits/optspec.rs +++ b/core/src/storage2/traits/optspec.rs @@ -33,6 +33,8 @@ where // In case the contract storage is occupied we handle // the Option as if it was a T. env::get_contract_storage::<()>(root_key) + .ok() + .flatten() .map(|_| super::pull_spread_root::(root_key)) } @@ -100,11 +102,12 @@ pub fn pull_packed_root_opt(root_key: &Key) -> Option where T: PackedLayout, { - match env::get_contract_storage::(root_key) { - Some(value) => { + match env::get_contract_storage::(root_key) + .expect("decoding does not match expected type") + { + Some(mut value) => { // In case the contract storage is occupied we handle // the Option as if it was a T. - let mut value = value.expect("decoding does not match expected type"); ::pull_packed(&mut value, root_key); Some(value) } diff --git a/examples/runtime-storage/.gitignore b/examples/runtime-storage/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/examples/runtime-storage/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/examples/runtime-storage/Cargo.toml b/examples/runtime-storage/Cargo.toml deleted file mode 100755 index b95575d3e33..00000000000 --- a/examples/runtime-storage/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "runtime_storage" -version = "2.1.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -ink_primitives = { version = "2.1.0", path = "../../primitives", default-features = false } -ink_prelude = { version = "2.1.0", path = "../../prelude", default-features = false } -ink_metadata = { version = "2.1.0", path = "../../metadata", default-features = false, features = ["derive"], optional = true } -ink_core = { version = "2.1.0", path = "../../core", default-features = false } -ink_lang = { version = "2.1.0", path = "../../lang", default-features = false } - -scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } -scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } - -[lib] -name = "runtime_storage" -path = "lib.rs" -crate-type = ["cdylib"] - -[features] -default = ["std"] -std = [ - "ink_primitives/std", - "ink_metadata", - "ink_metadata/std", - "ink_core/std", - "ink_lang/std", - "scale/std", - "scale-info", - "scale-info/std", -] -ink-as-dependency = [] diff --git a/examples/runtime-storage/lib.rs b/examples/runtime-storage/lib.rs deleted file mode 100755 index faabe9b3ba4..00000000000 --- a/examples/runtime-storage/lib.rs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2019-2020 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. - -#![cfg_attr(not(feature = "std"), no_std)] - -use ink_lang as ink; - -#[ink::contract(version = "0.1.0")] -mod runtime_storage { - use ink_core::{ - env, - hash::Blake2x128, - }; - use ink_prelude::{ - format, - vec, - }; - use scale::{ - Decode, - Encode, - }; - - /// All balance information for an account, mirroring the structure defined in the runtime. - /// Copied from [substrate](https://github.com/paritytech/substrate/blob/2c87fe171bc341755a43a3b32d67560469f8daac/frame/system/src/lib.rs#L307) - #[derive(Encode, Decode)] - pub struct AccountData { - free: Balance, - _reserved: Balance, - _misc_frozen: Balance, - _fee_frozen: Balance, - } - - /// Information of an account, mirroring the structure defined in the runtime - /// Copied from [substrate](https://github.com/paritytech/substrate/blob/2c87fe171bc341755a43a3b32d67560469f8daac/frame/system/src/lib.rs#L307) - #[derive(Encode, Decode)] - pub struct AccountInfo { - _nonce: u32, - _refcount: u8, - data: AccountData, - } - - /// This simple contract reads a value from runtime storage - #[ink(storage)] - #[derive(Default)] - struct RuntimeStorage {} - - impl RuntimeStorage { - #[ink(constructor)] - fn new() -> Self { - Self {} - } - - /// Returns an account's free balance, read directly from runtime storage - /// - /// # Key Scheme - /// - /// A key for the [substrate storage map] - /// (https://github.com/paritytech/substrate/blob/dd97b1478b31a4715df7e88a5ebc6664425fb6c6/frame/support/src/storage/generator/map.rs#L28) - /// is constructed with: - /// - /// ```nocompile - /// Twox128(module_prefix) ++ Twox128(storage_prefix) ++ Hasher(encode(key)) - /// ``` - /// - /// For the `System` module's `Account` map, the [hasher implementation] - /// (https://github.com/paritytech/substrate/blob/2c87fe171bc341755a43a3b32d67560469f8daac/frame/system/src/lib.rs#L349) - /// is `blake2_128_concat`. - #[ink(message)] - fn get_balance(&self, account: AccountId) -> Balance { - let mut key = vec![ - // Precomputed: Twox128("System") - 38, 170, 57, 78, 234, 86, 48, 224, 124, 72, 174, 12, 149, 88, 206, 247, - // Precomputed: Twox128("Account") - 185, 157, 136, 14, 198, 129, 121, 156, 12, 243, 14, 136, 134, 55, 29, 169, - ]; - - let encoded_account = account.encode(); - let hashed_account = ::hash_bytes(&encoded_account); - - // The hasher is `Blake2_128Concat` which appends the unhashed account to the hashed account - key.extend_from_slice(&hashed_account); - key.extend_from_slice(&encoded_account); - - // fetch from runtime storage - let result = self.env().get_runtime_storage::(&key[..]); - match result { - Some(Ok(account_info)) => account_info.data.free, - Some(Err(err)) => { - env::println(&format!("Error reading AccountInfo {:?}", err)); - 0 - } - None => { - env::println(&format!("No data at key {:?}", key)); - 0 - } - } - } - } - - #[cfg(all(test))] - mod tests { - use super::*; - use ink_core::env; - - /// Executes the given test through the off-chain environment. - fn run_test(test_fn: F) - where - F: FnOnce(), - { - env::test::run_test::(|_| { - test_fn(); - Ok(()) - }) - .unwrap() - } - - #[test] - fn non_existent_account_returns_zero() { - run_test(|| { - let contract = RuntimeStorage::new(); - let account: AccountId = [0u8; 32].into(); - assert_eq!(contract.get_balance(account), 0); - }) - } - - #[test] - fn returns_account_balance_from_storage() { - run_test(|| { - let contract = RuntimeStorage::new(); - let account: AccountId = [0u8; 32].into(); - let balance = 1_000_000; - - let account_info = AccountInfo { - data: AccountData { - free: balance, - _reserved: 0, - _fee_frozen: 0, - _misc_frozen: 0, - }, - _nonce: 0, - _refcount: 0, - }; - - let encoded_account = &account.encode(); - - let mut key = vec![ - // Precomputed: Twox128("System") - 38, 170, 57, 78, 234, 86, 48, 224, 124, 72, 174, 12, 149, 88, 206, - 247, // Precomputed: Twox128("Account") - 185, 157, 136, 14, 198, 129, 121, 156, 12, 243, 14, 136, 134, 55, 29, - 169, - ]; - - let hashed_account = ::hash_bytes(&encoded_account); - - key.extend_from_slice(&hashed_account); - key.extend_from_slice(&encoded_account); - - env::test::set_runtime_storage(&key, account_info); - assert_eq!(contract.get_balance(account), balance); - }) - } - } -} diff --git a/lang/ir/src/ir/item_impl/mod.rs b/lang/ir/src/ir/item_impl/mod.rs index ec7cc402348..0de5e63a5f2 100644 --- a/lang/ir/src/ir/item_impl/mod.rs +++ b/lang/ir/src/ir/item_impl/mod.rs @@ -297,11 +297,7 @@ impl TryFrom for ItemImpl { err.into_combine(format_err!(impl_block_span, "at this invokation",)) })?; normalized.ensure_no_conflicts(|arg| { - match arg.kind() { - ir::AttributeArgKind::Implementation - | ir::AttributeArgKind::Namespace(_) => false, - _ => true, - } + !matches!(arg.kind(), ir::AttributeArgKind::Implementation | ir::AttributeArgKind::Namespace(_)) })?; namespace = normalized.namespace(); } diff --git a/lang/macro/src/ir/data.rs b/lang/macro/src/ir/data.rs index 4cddcab256a..e33cd5bcee2 100644 --- a/lang/macro/src/ir/data.rs +++ b/lang/macro/src/ir/data.rs @@ -420,10 +420,7 @@ impl Function { /// Returns `true` if the function is a method. #[allow(unused)] pub fn is_method(&self) -> bool { - match self.kind() { - FunctionKind::Method => true, - _ => false, - } + matches!(self.kind(), FunctionKind::Method) } } diff --git a/lang/src/dispatcher.rs b/lang/src/dispatcher.rs index 07a0c9dfdc3..b55efede178 100644 --- a/lang/src/dispatcher.rs +++ b/lang/src/dispatcher.rs @@ -24,12 +24,15 @@ use core::{ any::TypeId, mem::ManuallyDrop, }; -use ink_core::storage2::{ - alloc, - alloc::ContractPhase, - traits::{ - pull_spread_root, - push_spread_root, +use ink_core::{ + env::ReturnFlags, + storage2::{ + alloc, + alloc::ContractPhase, + traits::{ + pull_spread_root, + push_spread_root, + }, }, }; use ink_primitives::Key; @@ -76,7 +79,10 @@ where let result = f(&state); alloc::finalize(); if TypeId::of::<::Output>() != TypeId::of::<()>() { - ink_core::env::output::<::Output>(&result) + ink_core::env::return_value::<::Output>( + ReturnFlags::default(), + &result, + ) } Ok(()) } @@ -100,7 +106,10 @@ where push_spread_root::<::State>(&state, &root_key); alloc::finalize(); if TypeId::of::<::Output>() != TypeId::of::<()>() { - ink_core::env::output::<::Output>(&result) + ink_core::env::return_value::<::Output>( + ReturnFlags::default(), + &result, + ) } Ok(()) } diff --git a/lang/src/env_access.rs b/lang/src/env_access.rs index 2e9793d46c0..8a92998ceaf 100644 --- a/lang/src/env_access.rs +++ b/lang/src/env_access.rs @@ -109,8 +109,8 @@ where /// # Note /// /// For more details visit: [`ink_core::env::gas_price`] - pub fn gas_price(self, gas: u64) -> T::Balance { - env::gas_price::(gas).expect("couldn't decode gas price") + pub fn weight_to_fee(self, gas: u64) -> T::Balance { + env::weight_to_fee::(gas).expect("couldn't decode weight fee") } /// Returns the amount of gas left for the contract execution. @@ -210,15 +210,6 @@ where env::set_rent_allowance::(new_value) } - /// Invokes a call to the runtime. - /// - /// # Note - /// - /// For more details visit: [`ink_core::env::invoke_runtime`] - pub fn invoke_runtime(self, params: &T::Call) -> Result<()> { - env::invoke_runtime::(params) - } - /// Invokes a contract message. /// /// # Note @@ -312,16 +303,4 @@ where { env::random::(subject).expect("couldn't decode randomized hash") } - - /// Returns the value from the *runtime* storage at the position of the key if any. - /// - /// # Note - /// - /// For more details visit: [`ink_core::env::get_runtime_storage`] - pub fn get_runtime_storage(self, runtime_key: &[u8]) -> Option> - where - R: scale::Decode, - { - env::get_runtime_storage::(runtime_key) - } }