Unverified Commit f5b3c481 authored by Hero Bird's avatar Hero Bird Committed by GitHub

Avoid heap allocations for contract dispatch (#449)

* [core] add Env::decode_input

* [lang/macro] remove unneeded crate dependencies

# Conflicts:
#	lang/macro/Cargo.toml

* [lang, lang/macro] add message dispatcher type code generation

* [lang/macro] add constructor dispatch enum code generation

* [lang] add executor functions and Execute trait

* [core] add decode_input to public env API

* [lang] fix incorrect trait bound in execute_message

* [lang/macro] add code generation for ink_lang::Execute

* [lang/macro] integrate Execute trait impls into contract dispatch

* [lang/macro] fix bug with constructors and messages with multiple inputs

* [lang] apply rustfmt

* [lang/macro] apply rustfmt + fix clippy warnings

* [lang] remove testable module

* [lang] clean-up lang module after implementing new dispatch codegen

# Conflicts:
#	lang/src/dispatcher.rs

* [lang] re-enable alloc init and finalize

* [lang] apply rustfmt

* [core] remove Env::input function

It has been deprecated. Users should use Env::decode_input function instead.

# Conflicts:
#	core/src/env/engine/off_chain/mod.rs

* [core, examples] fix move of CallData to env::test API

* [core] apply rustfmt

* [examples] fix examples Selector::from_str -> new

* [examples] fix multisig_plain doc-test import

* [examples] apply rustfmt to multisig_plain example

* [lang/macro] resolve some code dupes
parent af1ab521
Pipeline #97222 failed with stages
in 7 minutes and 40 seconds
......@@ -20,7 +20,6 @@ use crate::env::{
TypedEnv,
},
call::{
CallData,
CallParams,
InstantiateParams,
ReturnType,
......@@ -437,24 +436,34 @@ where
})
}
/// Returns the input to the executed contract.
/// Returns the execution input to the executed contract and decodes it as `T`.
///
/// # Note
///
/// - The input is the 4-bytes selector followed by the arguments
/// of the called function in their SCALE encoded representation.
/// - This property must be received as the first action an executed
/// contract to its environment and can only be queried once.
/// The environment access asserts this guarantee.
/// - No prior interaction with the environment must take place before
/// calling this procedure.
///
/// # Usage
///
/// Normally contracts define their own `enum` dispatch types respective
/// to their exported contructors and messages that implement `scale::Decode`
/// according to the contructors or messages selectors and their arguments.
/// These `enum` dispatch types are then given to this procedure as the `T`.
///
/// When using ink! users do not have to construct those enum dispatch types
/// themselves as they are normally generated by the ink! code generation
/// automatically.
///
/// # Errors
///
/// - If the call to `input` is not the first call to the environment.
/// - If the input failed to decode into call data.
/// - This happens only if the host runtime provides less than 4 bytes for
/// the function selector upon this query.
pub fn input() -> Result<CallData> {
<EnvInstance as OnInstance>::on_instance(|instance| Env::input(instance))
/// If the given `T` cannot be properly decoded from the expected input.
pub fn decode_input<T>() -> Result<T>
where
T: scale::Decode,
{
<EnvInstance as OnInstance>::on_instance(|instance| Env::decode_input::<T>(instance))
}
/// Returns the value back to the caller of the executed contract.
......
......@@ -14,7 +14,6 @@
use crate::env::{
call::{
CallData,
CallParams,
InstantiateParams,
ReturnType,
......@@ -53,16 +52,32 @@ pub trait Env {
where
R: scale::Decode;
/// Returns the input to the executed contract.
/// Returns the execution input to the executed contract and decodes it as `T`.
///
/// # Note
///
/// - The input is the 4-bytes selector followed by the arguments
/// of the called function in their SCALE encoded representation.
/// - This property must be received as the first action an executed
/// contract to its environment and can only be queried once.
/// The environment access asserts this guarantee.
fn input(&mut self) -> Result<CallData>;
/// - No prior interaction with the environment must take place before
/// calling this procedure.
///
/// # Usage
///
/// Normally contracts define their own `enum` dispatch types respective
/// to their exported contructors and messages that implement `scale::Decode`
/// according to the contructors or messages selectors and their arguments.
/// These `enum` dispatch types are then given to this procedure as the `T`.
///
/// When using ink! users do not have to construct those enum dispatch types
/// themselves as they are normally generated by the ink! code generation
/// automatically.
///
/// # Errors
///
/// If the given `T` cannot be properly decoded from the expected input.
fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode;
/// Returns the value back to the caller of the executed contract.
///
......
......@@ -53,8 +53,5 @@ pub use self::{
InstantiateBuilder,
InstantiateParams,
},
utils::{
CallData,
Selector,
},
utils::Selector,
};
......@@ -13,10 +13,6 @@
// limitations under the License.
use derive_more::From;
use ink_prelude::{
vec,
vec::Vec,
};
/// Seals to guard pushing arguments to already satisfied parameter builders.
pub mod seal {
......@@ -44,82 +40,3 @@ impl Selector {
self.bytes
}
}
/// The raw ABI respecting input data to a call.
///
/// # Note
///
/// The first four bytes are the function selector and the rest are SCALE encoded inputs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallData {
/// Already encoded function selector and inputs.
///
/// # Note
///
/// Has the invariant of always holding at least 4 bytes (the selector).
bytes: Vec<u8>,
}
impl CallData {
/// Creates new call ABI data for the given selector.
pub fn new(selector: Selector) -> Self {
let bytes = selector.to_bytes();
Self {
bytes: vec![bytes[0], bytes[1], bytes[2], bytes[3]],
}
}
/// Pushes the given argument onto the call ABI data in encoded form.
pub fn push_arg<A>(&mut self, arg: &A)
where
A: scale::Encode,
{
arg.encode_to(&mut self.bytes)
}
/// Returns the selector of `self`.
pub fn selector(&self) -> Selector {
debug_assert!(self.bytes.len() >= 4);
let bytes = [self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3]];
bytes.into()
}
/// Returns the underlying bytes of the encoded input parameters.
pub fn params(&self) -> &[u8] {
debug_assert!(self.bytes.len() >= 4);
&self.bytes[4..]
}
/// Returns the underlying byte representation.
pub fn to_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl scale::Encode for CallData {
fn size_hint(&self) -> usize {
self.bytes.len()
}
fn encode_to<T: scale::Output>(&self, dest: &mut T) {
dest.write(self.bytes.as_slice());
}
}
impl scale::Decode for CallData {
fn decode<I: scale::Input>(
input: &mut I,
) -> core::result::Result<Self, scale::Error> {
let remaining_len = input.remaining_len().unwrap_or(None).unwrap_or(0);
let mut bytes = Vec::with_capacity(remaining_len);
while let Ok(byte) = input.read_byte() {
bytes.push(byte);
}
if bytes.len() < 4 {
return Err(scale::Error::from(
"require at least 4 bytes for input data",
))
}
Ok(Self { bytes })
}
}
use crate::env::call::Selector;
use ink_prelude::{
vec,
vec::Vec,
};
/// The raw ABI respecting input data to a call.
///
/// # Note
///
/// The first four bytes are the function selector and the rest are SCALE encoded inputs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallData {
/// Already encoded function selector and inputs.
///
/// # Note
///
/// Has the invariant of always holding at least 4 bytes (the selector).
bytes: Vec<u8>,
}
impl CallData {
/// Creates new call ABI data for the given selector.
pub fn new(selector: Selector) -> Self {
let bytes = selector.to_bytes();
Self {
bytes: vec![bytes[0], bytes[1], bytes[2], bytes[3]],
}
}
/// Pushes the given argument onto the call ABI data in encoded form.
pub fn push_arg<A>(&mut self, arg: &A)
where
A: scale::Encode,
{
arg.encode_to(&mut self.bytes)
}
/// Returns the selector of `self`.
pub fn selector(&self) -> Selector {
debug_assert!(self.bytes.len() >= 4);
let bytes = [self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3]];
bytes.into()
}
/// Returns the underlying bytes of the encoded input parameters.
pub fn params(&self) -> &[u8] {
debug_assert!(self.bytes.len() >= 4);
&self.bytes[4..]
}
/// Returns the underlying byte representation.
pub fn to_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl scale::Encode for CallData {
fn size_hint(&self) -> usize {
self.bytes.len()
}
fn encode_to<T: scale::Output>(&self, dest: &mut T) {
dest.write(self.bytes.as_slice());
}
}
impl scale::Decode for CallData {
fn decode<I: scale::Input>(
input: &mut I,
) -> core::result::Result<Self, scale::Error> {
let remaining_len = input.remaining_len().unwrap_or(None).unwrap_or(0);
let mut bytes = Vec::with_capacity(remaining_len);
while let Ok(byte) = input.read_byte() {
bytes.push(byte);
}
if bytes.len() < 4 {
return Err(scale::Error::from(
"require at least 4 bytes for input data",
))
}
Ok(Self { bytes })
}
}
......@@ -14,16 +14,14 @@
use super::{
super::{
CallData,
Result,
TypedEncoded,
},
OffAccountId,
OffBalance,
};
use crate::env::{
call::CallData,
EnvTypes,
};
use crate::env::EnvTypes;
use ink_prelude::vec::Vec;
pub type Bytes = Vec<u8>;
......
......@@ -19,7 +19,6 @@ use super::{
};
use crate::env::{
call::{
CallData,
CallParams,
InstantiateParams,
ReturnType,
......@@ -91,12 +90,19 @@ impl Env for EnvInstance {
self.runtime_storage.load::<R>(runtime_key)
}
fn input(&mut self) -> Result<CallData> {
fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode,
{
self.exec_context()
.map(|exec_ctx| &exec_ctx.call_data)
.map(Clone::clone)
.map_err(|_| scale::Error::from("could not decode input call data"))
.map(|call_data| scale::Encode::encode(call_data))
.map_err(Into::into)
.and_then(|encoded| {
<T as scale::Decode>::decode(&mut &encoded[..])
.map_err(|_| scale::Error::from("could not decode input call data"))
.map_err(Into::into)
})
}
fn output<R>(&mut self, return_value: &R)
......
......@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod call_data;
mod db;
mod hashing;
mod impls;
......@@ -24,6 +25,14 @@ mod types;
#[cfg(test)]
mod tests;
pub use self::{
call_data::CallData,
db::{
AccountError,
PastPrints,
},
typed_encoded::TypedEncodedError,
};
use self::{
db::{
Account,
......@@ -47,13 +56,6 @@ use self::{
OffTimestamp,
},
};
pub use self::{
db::{
AccountError,
PastPrints,
},
typed_encoded::TypedEncodedError,
};
use super::OnInstance;
use crate::env::EnvTypes;
use core::cell::RefCell;
......@@ -198,10 +200,7 @@ impl EnvInstance {
T::Balance::from(20),
);
// Initialize the execution context for the first contract execution.
use crate::env::call::{
CallData,
Selector,
};
use crate::env::call::Selector;
// The below selector bytes are incorrect but since calling doesn't work
// yet we do not have to fix this now.
let selector_bytes_for_call = [0x00; 4];
......
......@@ -14,6 +14,7 @@
//! Operations on the off-chain testing environment.
pub use super::CallData;
use super::{
db::ExecContext,
AccountError,
......@@ -22,7 +23,6 @@ use super::{
OnInstance,
};
use crate::env::{
call::CallData,
EnvTypes,
Result,
};
......
......@@ -18,7 +18,6 @@ use super::{
};
use crate::env::{
call::{
CallData,
CallParams,
InstantiateParams,
ReturnType,
......@@ -164,8 +163,11 @@ impl Env for EnvInstance {
Some(self.decode_scratch_buffer().map_err(Into::into))
}
fn input(&mut self) -> Result<CallData> {
self.get_property::<CallData>(|| ())
fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode,
{
self.get_property::<T>(|| ())
}
fn output<R>(&mut self, return_value: &R)
......
......@@ -213,7 +213,7 @@ mod dns {
AccountId::from(DEFAULT_CALLEE_HASH),
DEFAULT_ENDOWMENT,
DEFAULT_GAS_LIMIT,
env::call::CallData::new(env::call::Selector::new([0x00; 4])),
env::test::CallData::new(env::call::Selector::new([0x00; 4])),
)
}
......
......@@ -260,7 +260,7 @@ mod erc20 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
......@@ -306,7 +306,7 @@ mod erc20 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call.
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller.
assert_eq!(
......
......@@ -525,7 +525,7 @@ mod erc721 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
......@@ -561,7 +561,7 @@ mod erc721 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
......@@ -615,7 +615,7 @@ mod erc721 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Bob as caller
assert_eq!(
......@@ -678,7 +678,7 @@ mod erc721 {
env::account_id::<env::DefaultEnvTypes>().unwrap_or([0x0; 32].into());
// Create call
let mut data =
env::call::CallData::new(env::call::Selector::new([0x00; 4]));
env::test::CallData::new(env::call::Selector::new([0x00; 4])); // balance_of
data.push_arg(&accounts.bob);
// Push the new execution context to set Eve as caller
assert_eq!(
......
......@@ -293,7 +293,7 @@ mod multisig_plain {
/// Since this message must be send by the wallet itself it has to be build as a
/// `Transaction` and dispatched through `submit_transaction` + `invoke_transaction`:
/// ```no_run
/// use ink_core::env::{DefaultEnvTypes as Env, AccountId, call::{CallData, CallParams, Selector}};
/// use ink_core::env::{DefaultEnvTypes as Env, AccountId, call::{CallParams, Selector}, test::CallData};
/// use multisig_plain::{Transaction, ConfirmationStatus};
///
/// // address of an existing MultiSigPlain contract
......@@ -672,7 +672,7 @@ mod multisig_plain {
impl Transaction {
fn change_requirement(requirement: u32) -> Self {
let mut call = call::CallData::new(call::Selector::new([0x00; 4])); // change_requirement
let mut call = test::CallData::new(call::Selector::new([0x00; 4])); // change_requirement
call.push_arg(&requirement);
Self {
callee: WALLET.into(),
......@@ -690,7 +690,7 @@ mod multisig_plain {
WALLET.into(),
1000000,
1000000,
call::CallData::new(call::Selector::new([0x00; 4])),
test::CallData::new(call::Selector::new([0x00; 4])), // dummy
);
}
......
......@@ -21,11 +21,9 @@ scale = { package = "parity-scale-codec", version = "1.3", default-features = fa
quote = "1"
syn = { version = "1.0", features = ["parsing", "full", "extra-traits"] }
proc-macro2 = "1.0"
heck = "0.3"
itertools = { version = "0.9", default-features = false }
either = { version = "1.5", default-features = false }
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = "1.0"
derive_more = { version = "0.99", default-features = false, features = ["from"] }
regex = "1.3"
sha3 = "0.8"
......
This diff is collapsed.
......@@ -12,24 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
dispatcher::{
Dispatch,
DispatchList,
EmptyDispatchList,
MsgCon,
MsgMut,
MsgRef,
},
traits::{
Constructor,
MessageMut,
MessageRef,
},
DispatchError,
};
use core::marker::PhantomData;
use ink_core::env::call::CallData;
use crate::DispatchError;
/// The contract dispatch mode.
///
......@@ -48,162 +31,3 @@ pub enum DispatchMode {
pub trait DispatchUsingMode {
fn dispatch_using_mode(mode: DispatchMode) -> Result<(), DispatchError>;
}
/// Placeholder for the given type.
#[derive(Debug)]
pub struct Placeholder<T>(PhantomData<fn() -> T>);
impl<T> Default for Placeholder<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> Clone for Placeholder<T> {
fn clone(&self) -> Self {
Default::default()
}
}
impl<T> Copy for Placeholder<T> {}
/// The contract definition.
///
/// ## Dispatch
///
/// ### Constructor Dispatch
///
/// 1. Retrieve function selector `s`.
/// 1. Find selected constructor `C` given `s`:
/// - If `C` was found:
/// 1. Decode input arguments `i` according to `C`'s requirements
/// 1. Initialize dynamic storage allocator for instantiation
/// 1. Call `C(i)`, returns storage instance `S`
/// 1. Push state of `S` to contract storage
/// 1. Finalize dynamic storage allocator
/// 1. Exit
/// - Otherwise:
/// 1. Panic that the provided constructor selector was invalid
///
/// ### Message Dispatch
///
/// 1. Retrieve function selector `s`.
/// 1. Find selected message `M` given `s`:
/// - If `M` was found:
/// 1. Decode input arguments `i` according to `M`'s requirements
/// 1. Initialize dynamic storage allocator for calls
/// 1. Pull storage instance `S` from contract storage
/// 1. Call `M(s, i)`, returns return value `R`
/// 1. If `M` has mutable access to the contract storage:
/// 1. push mutated state of `S` back to contract storage
/// 1. Finalize dynamic storage allocator
/// 1. If `R` is not of type `()`:
/// 1. return `R`
/// 1. Exit
/// - Otherwise:
/// 1. Panic that the provided message selector was invalid
#[derive(Debug)]
pub struct Contract<Constrs, Msgs, Phase> {
/// The current type state of the contract.
state: Placeholder<(Constrs, Msgs, Phase)>,
}
impl<Constrs, Msgs, Phase> Default for Contract<Constrs, Msgs, Phase> {
#[inline(always)]
fn default() -> Self {
Self {
state: Default::default(),
}
}
}
impl<Constrs, Msgs, Phase> Clone for Contract<Constrs, Msgs, Phase> {
fn clone(&self) -> Self {
Default::default()
}
}
impl<Constrs, Msgs, Phase> Copy for Contract<Constrs, Msgs, Phase> {}
/// Phase to start the building process of a contract.
#[derive(Debug)]
pub enum EmptyPhase {}
/// Building phase of a contract construction.
#[derive(Debug)]
pub enum BuildPhase {}
/// Final phase of a fully constructed contract.
#[derive(Debug)]
pub enum FinalPhase {}
impl Contract<(), (), EmptyPhase> {
/// Creates a new contract builder.
#[inline(always)]
pub fn build() -> Contract<EmptyDispatchList, EmptyDispatchList, BuildPhase> {
Default::default()
}
}
impl<Constrs> Contract<Constrs, EmptyDispatchList, BuildPhase> {
/// Registers a new constructor for the contract.
#[inline(always)]
pub fn register_constructor<C>(
self,
) -> Contract<DispatchList<MsgCon<C>, Constrs>, EmptyDispatchList, BuildPhase>
where
C: Constructor,
{
Default::default()
}
}
impl<Constrs, Messages> Contract<Constrs, Messages, BuildPhase> {
/// Registers a new `&self` message for the contract.
#[inline(always)]
pub fn register_message<M>(
self,
) -> Contract<Constrs, DispatchList<MsgRef<M>, Messages>, BuildPhase>
where