Commit 49f221bd authored by Andrew Jones's avatar Andrew Jones Committed by Hero Bird

Implement `ext_dispatch_call` (#124)

* [core] Add AccountId to EnvTypes

* [core] Add calls mod, plus WIP tests

* [core] Balance transfer call roundtrip works

* [core] Add ext_dispatch_call

* [core] Implement ext_dispatch_call

* [examples] Add example lang contract for ext_dispatch_call

* [examples] WIP: implement example lang contract for ext_dispatch_call

* [examples] complete basic implementation of dispatching Balances Call

* srml-contracts -> srml-contract

* [core] remove unsafe from dispatch_call api fn

* [core] Add docs to dispatch_call

* [core] Add missing trait bound to account index

* [core] Some todos

* [core] Rename dispatch_call to dispatch_raw_call on Env trait

* [lang] Add AccountIndex type alias

* [model] add strongly type dispatch call to EnvHandler

* [*] specify latest parity-codec version, same as substrate

* [core] fix call roundtrip tests

* [lang] fix tests

* [core] fix wasm build

* [core] fix std build

* [core] move call type serialization tests

* [core] test Call serialization roundtrip

* [core] use node_runtime for Call serialization roundtrip

* [core] remove unused dependencies

* [core] remove unused substrate dependencies

* [core] change default Balance to u128, matching substrate

* [core] introduce Address type for balance calls

* [core] fix Balances transfer serialization test

* [core] add Address serialization tests

* [ci] install wasm-gc for building substrate dev deps

* [ci] move wasm-gc installation after wasm target added

* [examples] get the calls example compiling

* [CI] add temporary check for debugging ci build

* [CI] restore wasm-gc to install section and remove temp version check

* [CI] temporarily depend on substrate branch to test build fix

* [CI] remove temp substrate branch, build issue fixed

* [core] use std feature instead of test-env for EnvTypes

* [core] remove Call types, moved to ink-types-node-runtime

* [core] remove AccountIndex from EnvTypes

* [core] remove AccountIndex from lang codegen

* [core] fix unused for std

* [CI] remove wasm-gc from travis build

* [CI] remove AccountIndex type alias from codegen

* parity-codec version 4.1.1 -> 4.1

* [core] ext_dispatch_call docs

* [core] describe in comment what will happen if decoding fails

* [model] implement suggestion of Into<Call>

* rustfmt

* rustfmt again

* rustfmt again again

* [examples] deleted calls example - moved to ink_types_node_runtime

* [core] use into in api::dispatch_call

* [core] make Call empty enum and use test-env feature for EnvTypes

* [core] remove unused Call enum

* [core] add comments explaining rationale for test-env

* [core] missing period

* [core] add comments

* [core] add space in comment

* [core] add comment to Call decode impl

* [core] make dispatched_calls a DoubleEndedIterator

* [core] add missing doc comment

* [core] add comment explaining Decode requirement

* [core] doc comment and DoubleEndedIterator
parent 77c8e452
......@@ -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<Vec<u8>> {
/// own to always encode the expected type.
pub unsafe fn r#return<T, E>(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<T, C>(call: C)
where
T: Env,
C: Into<<T as EnvTypes>::Call>,
{
T::dispatch_raw_call(&call.into().encode()[..])
}
......@@ -86,6 +86,7 @@ where
type Hash = <T as EnvTypes>::Hash;
type Moment = <T as EnvTypes>::Moment;
type BlockNumber = <T as EnvTypes>::BlockNumber;
type Call = <T as EnvTypes>::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) }
}
}
......@@ -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;
......
......@@ -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<I: parity_codec::Input>(_value: &mut I) -> Option<Self> {
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)]
......
......@@ -171,6 +171,8 @@ pub struct TestEnvData {
total_writes: u64,
/// Deposited events of the contract invocation.
events: Vec<EventData>,
/// Calls dispatched to the runtime
dispatched_calls: Vec<Vec<u8>>,
/// The current gas price.
gas_price: Vec<u8>,
/// 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<u8>) {
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<Item = &[u8]> {
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<u8>], 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<Item = T::Call> {
TEST_ENV_DATA.with(|test_env| {
test_env
.borrow()
.dispatched_calls()
.map(|call| Decode::decode(&mut &call[..]).expect("Valid encoded Call"))
.collect::<Vec<_>>()
.into_iter()
})
}
}
macro_rules! impl_env_getters_for_test_env {
......@@ -499,6 +529,7 @@ where
type Hash = <T as EnvTypes>::Hash;
type Moment = <T as EnvTypes>::Moment;
type BlockNumber = <T as EnvTypes>::BlockNumber;
type Call = <T as EnvTypes>::Call;
}
impl<T> Env for TestEnv<T>
......@@ -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 {}
......
......@@ -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: &[<Self as EnvTypes>::Hash], data: &[u8]);
/// Dispatches a call into the Runtime.
fn dispatch_raw_call(data: &[u8]);
}
......@@ -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"
......@@ -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"
......@@ -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"
......@@ -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"
......@@ -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<State, Env> {
......@@ -177,7 +178,16 @@ impl<T: Env> EnvHandler<T> {
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<C>(&self, call: C)
where
C: Into<T::Call>,
{
T::dispatch_raw_call(call.into().encode().as_slice())
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment