diff --git a/core/src/env/api.rs b/core/src/env/api.rs index de252ea34d..153a080e99 100644 --- a/core/src/env/api.rs +++ b/core/src/env/api.rs @@ -17,12 +17,16 @@ use super::ContractEnvStorage; use crate::{ env::{ - traits::Env, + traits::{ + Env, + EnvTypes, + }, EnvStorage as _, }, memory::vec::Vec, storage::Key, }; +use parity_codec::Encode; /// Stores the given value under the specified key in the contract storage. /// @@ -64,8 +68,21 @@ pub unsafe fn load(key: Key) -> Option> { /// own to always encode the expected type. pub unsafe fn r#return(value: T) -> ! where - T: parity_codec::Encode, + T: Encode, E: Env, { E::r#return(&value.encode()[..]) } + +/// Dispatches a Call into the runtime, for invoking other substrate +/// modules. Dispatched only after successful contract execution. +/// +/// The encoded Call MUST be decodable by the target substrate runtime. +/// If decoding fails, then the smart contract execution will fail. +pub fn dispatch_call(call: C) +where + T: Env, + C: Into<::Call>, +{ + T::dispatch_raw_call(&call.into().encode()[..]) +} diff --git a/core/src/env/srml/srml_only/impls.rs b/core/src/env/srml/srml_only/impls.rs index 3743e5d8ed..811e4f2211 100644 --- a/core/src/env/srml/srml_only/impls.rs +++ b/core/src/env/srml/srml_only/impls.rs @@ -86,6 +86,7 @@ where type Hash = ::Hash; type Moment = ::Moment; type BlockNumber = ::BlockNumber; + type Call = ::Call; } macro_rules! impl_getters_for_srml_env { @@ -151,4 +152,8 @@ where ) } } + + fn dispatch_raw_call(data: &[u8]) { + unsafe { sys::ext_dispatch_call(data.as_ptr() as u32, data.len() as u32) } + } } diff --git a/core/src/env/srml/srml_only/sys.rs b/core/src/env/srml/srml_only/sys.rs index 9d0cc9a4ae..df4ac4602b 100644 --- a/core/src/env/srml/srml_only/sys.rs +++ b/core/src/env/srml/srml_only/sys.rs @@ -72,6 +72,12 @@ extern "C" { value_len: u32, ); + /// Dispatches a Call into the runtime, for invocation of substrate modules + /// + /// Call data is written to the scratch buffer, and it MUST be decodable into the host chain + /// runtime `Call` type. + pub fn ext_dispatch_call(call_ptr: u32, call_len: u32); + /// Tells the execution environment to load the contents /// stored at the given key into the scratch buffer. pub fn ext_get_storage(key_ptr: u32) -> u32; diff --git a/core/src/env/srml/types.rs b/core/src/env/srml/types.rs index ee93489c63..853005afcf 100644 --- a/core/src/env/srml/types.rs +++ b/core/src/env/srml/types.rs @@ -31,14 +31,38 @@ use parity_codec::{ /// The SRML fundamental types. #[allow(unused)] +#[cfg_attr(feature = "test-env", derive(Debug, Clone, PartialEq, Eq))] pub enum DefaultSrmlTypes {} +/// Empty enum for default Call type, so it cannot be constructed. +/// For calling into the runtime, a user defined Call type required. +/// See https://github.com/paritytech/ink-types-node-runtime. +/// +/// # Note +/// +/// Some traits are only implemented to satisfy the constraints of the test +/// environment, in order to keep the code size small. +#[cfg_attr(feature = "test-env", derive(Debug, Clone, PartialEq, Eq))] +pub enum Call {} +impl parity_codec::Encode for Call {} + +/// This implementation is only to satisfy the Decode constraint in the +/// test environment. Since Call cannot be constructed then just return +/// None, but this should never be called. +#[cfg(feature = "test-env")] +impl parity_codec::Decode for Call { + fn decode(_value: &mut I) -> Option { + None + } +} + impl EnvTypes for DefaultSrmlTypes { type AccountId = AccountId; type Balance = Balance; type Hash = Hash; type Moment = Moment; type BlockNumber = BlockNumber; + type Call = Call; } /// The default SRML address type. @@ -61,7 +85,7 @@ impl<'a> TryFrom<&'a [u8]> for AccountId { } /// The default SRML balance type. -pub type Balance = u64; +pub type Balance = u128; /// The default SRML hash type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)] diff --git a/core/src/env/test_env.rs b/core/src/env/test_env.rs index 8730271524..5ed50d7813 100644 --- a/core/src/env/test_env.rs +++ b/core/src/env/test_env.rs @@ -171,6 +171,8 @@ pub struct TestEnvData { total_writes: u64, /// Deposited events of the contract invocation. events: Vec, + /// Calls dispatched to the runtime + dispatched_calls: Vec>, /// The current gas price. gas_price: Vec, /// The remaining gas. @@ -197,6 +199,7 @@ impl Default for TestEnvData { gas_price: Vec::new(), gas_left: Vec::new(), value_transferred: Vec::new(), + dispatched_calls: Vec::new(), } } } @@ -216,6 +219,7 @@ impl TestEnvData { self.total_reads.set(0); self.total_writes = 0; self.events.clear(); + self.dispatched_calls.clear(); } /// Increments the total number of reads from the storage. @@ -282,6 +286,11 @@ impl TestEnvData { self.events.push(new_event); } + /// Appends a dispatched call to the runtime + pub fn add_dispatched_call(&mut self, call: &[u8]) { + self.dispatched_calls.push(call.to_vec()); + } + /// Sets the random seed for the next contract invocation. pub fn set_random_seed(&mut self, random_seed_hash: Vec) { self.random_seed = random_seed_hash.to_vec(); @@ -303,6 +312,11 @@ impl TestEnvData { .iter() .map(|event_data| event_data.data_as_bytes()) } + + /// Returns an iterator over all dispatched calls + pub fn dispatched_calls(&self) -> impl DoubleEndedIterator { + self.dispatched_calls.iter().map(Vec::as_slice) + } } impl TestEnvData { @@ -399,6 +413,10 @@ impl TestEnvData { pub fn deposit_raw_event(&mut self, topics: &[Vec], data: &[u8]) { self.add_event(topics, data); } + + pub fn dispatch_call(&mut self, call: &[u8]) { + self.add_dispatched_call(call); + } } thread_local! { @@ -477,6 +495,18 @@ where .into_iter() }) } + + /// Returns an iterator over all dispatched calls. + pub fn dispatched_calls() -> impl DoubleEndedIterator { + TEST_ENV_DATA.with(|test_env| { + test_env + .borrow() + .dispatched_calls() + .map(|call| Decode::decode(&mut &call[..]).expect("Valid encoded Call")) + .collect::>() + .into_iter() + }) + } } macro_rules! impl_env_getters_for_test_env { @@ -499,6 +529,7 @@ where type Hash = ::Hash; type Moment = ::Moment; type BlockNumber = ::BlockNumber; + type Call = ::Call; } impl Env for TestEnv @@ -532,6 +563,10 @@ where test_env.borrow_mut().deposit_raw_event(&topics, data) }) } + + fn dispatch_raw_call(data: &[u8]) { + TEST_ENV_DATA.with(|test_env| test_env.borrow_mut().dispatch_call(data)) + } } pub enum TestEnvStorage {} diff --git a/core/src/env/traits.rs b/core/src/env/traits.rs index d08edabcc1..3d2560fd33 100644 --- a/core/src/env/traits.rs +++ b/core/src/env/traits.rs @@ -33,10 +33,13 @@ pub trait EnvTypes { type Moment: Codec + Clone + PartialEq + Eq; /// The type of block number. type BlockNumber: Codec + Clone + PartialEq + Eq; + /// The type of a call into the runtime + type Call: parity_codec::Encode; } #[cfg(feature = "test-env")] /// The environmental types usable by contracts defined with ink!. +/// For the test environment extra trait bounds are required for using the types in unit tests. pub trait EnvTypes { /// The type of an address. type AccountId: Codec + Clone + PartialEq + Eq + core::fmt::Debug; @@ -48,6 +51,9 @@ pub trait EnvTypes { type Moment: Codec + Clone + PartialEq + Eq + core::fmt::Debug; /// The type of block number. type BlockNumber: Codec + Clone + PartialEq + Eq + core::fmt::Debug; + /// The type of a call into the runtime. + /// Requires Decode for inspecting raw dispatched calls in the test environment. + type Call: Codec + Clone + PartialEq + Eq + core::fmt::Debug; } /// Types implementing this can act as contract storage. @@ -127,4 +133,7 @@ pub trait Env: EnvTypes { /// Deposits raw event data through Contracts module. fn deposit_raw_event(topics: &[::Hash], data: &[u8]); + + /// Dispatches a call into the Runtime. + fn dispatch_raw_call(data: &[u8]); } diff --git a/examples/lang/events/Cargo.toml b/examples/lang/events/Cargo.toml index b804c07fc3..be69de8d14 100644 --- a/examples/lang/events/Cargo.toml +++ b/examples/lang/events/Cargo.toml @@ -28,4 +28,4 @@ generate-api-description = [ [profile.release] panic = "abort" lto = true -opt-level = "z" \ No newline at end of file +opt-level = "z" diff --git a/examples/lang/flipper/Cargo.toml b/examples/lang/flipper/Cargo.toml index ade9deb006..9ed1b5849f 100644 --- a/examples/lang/flipper/Cargo.toml +++ b/examples/lang/flipper/Cargo.toml @@ -28,4 +28,4 @@ generate-api-description = [ [profile.release] panic = "abort" lto = true -opt-level = "z" \ No newline at end of file +opt-level = "z" diff --git a/examples/lang/incrementer/Cargo.toml b/examples/lang/incrementer/Cargo.toml index cc15850efc..529fbd6961 100644 --- a/examples/lang/incrementer/Cargo.toml +++ b/examples/lang/incrementer/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" ink_core = { path = "../../../core" } ink_model = { path = "../../../model" } ink_lang = { path = "../../../lang" } -parity-codec = { version = "3.2", default-features = false, features = ["derive"] } +parity-codec = { version = "4.1", default-features = false, features = ["derive"] } [lib] name = "incrementer" @@ -28,4 +28,4 @@ generate-api-description = [ [profile.release] panic = "abort" lto = true -opt-level = "z" \ No newline at end of file +opt-level = "z" diff --git a/examples/lang/shared_vec/Cargo.toml b/examples/lang/shared_vec/Cargo.toml index 7d4783c7bd..bfea660eef 100644 --- a/examples/lang/shared_vec/Cargo.toml +++ b/examples/lang/shared_vec/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" ink_core = { path = "../../../core" } ink_model = { path = "../../../model" } ink_lang = { path = "../../../lang" } -parity-codec = { version = "3.2", default-features = false, features = ["derive"] } +parity-codec = { version = "4.1", default-features = false, features = ["derive"] } [lib] name = "shared_vec" @@ -28,4 +28,4 @@ generate-api-description = [ [profile.release] panic = "abort" lto = true -opt-level = "z" \ No newline at end of file +opt-level = "z" diff --git a/model/src/exec_env.rs b/model/src/exec_env.rs index 37c4917c53..78c3b63514 100644 --- a/model/src/exec_env.rs +++ b/model/src/exec_env.rs @@ -28,6 +28,7 @@ use ink_core::{ Initialize, }, }; +use parity_codec::Encode as _; /// Provides a safe interface to an environment given a contract state. pub struct ExecutionEnv { @@ -177,7 +178,16 @@ impl EnvHandler { T::now() } + /// Returns the latest block number. pub fn block_number(&self) -> T::BlockNumber { T::block_number() } + + /// Dispatches a call into the runtime. + pub fn dispatch_call(&self, call: C) + where + C: Into, + { + T::dispatch_raw_call(call.into().encode().as_slice()) + } }