Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked message dispatch logic #1017

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 8 additions & 18 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ where
})
}

/// Returns the execution input to the executed contract and decodes it as `T`.
/// Returns the execution input to the executed contract as a byte slice.
///
/// # Note
///
Expand All @@ -349,24 +349,14 @@ where
///
/// # Usage
///
/// Normally contracts define their own `enum` dispatch types respective
/// to their exported constructors and messages that implement `scale::Decode`
/// according to the constructors or messages selectors and their arguments.
/// These `enum` dispatch types are then given to this procedure as the `T`.
/// Normally contracts decoding(`scale::Decode`) their arguments according to the
/// constructors or messages.
///
/// 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.
pub fn decode_input<T>() -> Result<T>
where
T: scale::Decode,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
EnvBackend::decode_input::<T>(instance)
/// When using ink! users do not have to decode input themselves as they are
/// normally decoded by the ink! code generation automatically.
pub fn input_bytes() -> &'static [u8] {
<EnvInstance as OnInstance<'static>>::on_instance(|instance| {
EnvBackend::input_bytes(instance)
})
}

Expand Down
22 changes: 6 additions & 16 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ impl ReturnFlags {
}

/// Returns the underlying `u32` representation.
#[cfg(not(feature = "ink-experimental-engine"))]
pub(crate) fn into_u32(self) -> u32 {
self.value
}
Expand Down Expand Up @@ -178,7 +177,7 @@ pub trait EnvBackend {
/// Clears the contract's storage key entry.
fn clear_contract_storage(&mut self, key: &Key);

/// Returns the execution input to the executed contract and decodes it as `T`.
/// Returns the execution input to the executed contract as a byte slice.
///
/// # Note
///
Expand All @@ -189,21 +188,12 @@ pub trait EnvBackend {
///
/// # Usage
///
/// Normally contracts define their own `enum` dispatch types respective
/// to their exported constructors and messages that implement `scale::Decode`
/// according to the constructors or messages selectors and their arguments.
/// These `enum` dispatch types are then given to this procedure as the `T`.
/// Normally contracts decoding(`scale::Decode`) their arguments according to the
/// constructors or messages.
///
/// 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;
/// When using ink! users do not have to decode input themselves as they are
/// normally decoded by the ink! code generation automatically.
fn input_bytes(&mut self) -> &[u8];

/// Returns the value back to the caller of the executed contract.
///
Expand Down
11 changes: 3 additions & 8 deletions crates/env/src/engine/experimental_off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,15 @@ impl EnvBackend for EnvInstance {
self.engine.clear_storage(key.as_ref())
}

fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode,
{
fn input_bytes(&mut self) -> &[u8] {
unimplemented!("the experimental off chain env does not implement `seal_input`")
}

fn return_value<R>(&mut self, _flags: ReturnFlags, _return_value: &R) -> !
fn return_value<R>(&mut self, flags: ReturnFlags, _return_value: &R) -> !
where
R: scale::Encode,
{
unimplemented!(
"the experimental off chain env does not implement `seal_return_value`"
)
std::process::exit(flags.into_u32() as i32)
}

fn debug_message(&mut self, message: &str) {
Expand Down
12 changes: 9 additions & 3 deletions crates/env/src/engine/experimental_off_chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ pub struct EnvInstance {
engine: Engine,
}

impl OnInstance for EnvInstance {
impl<'a> OnInstance<'a> for EnvInstance {
fn on_instance<F, R>(f: F) -> R
where
F: FnOnce(&mut Self) -> R,
F: FnOnce(&'a mut Self) -> R,
{
use core::cell::RefCell;
thread_local!(
Expand All @@ -43,7 +43,13 @@ impl OnInstance for EnvInstance {
}
)
);
INSTANCE.with(|instance| f(&mut instance.borrow_mut()))
INSTANCE.with(|instance| {
// SAFETY: The value of `RefCell` will be no change,
// so the lifetime can be extended to `a`(it can be `static`).
let env: &'a mut EnvInstance =
unsafe { core::mem::transmute(&mut *instance.borrow_mut()) };
f(env)
})
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/env/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use crate::backend::{
};
use cfg_if::cfg_if;

pub trait OnInstance: EnvBackend + TypedEnvBackend {
pub trait OnInstance<'a>: 'a + EnvBackend + TypedEnvBackend {
fn on_instance<F, R>(f: F) -> R
where
F: FnOnce(&mut Self) -> R;
F: FnOnce(&'a mut Self) -> R;
}

cfg_if! {
Expand Down
26 changes: 11 additions & 15 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,27 +147,23 @@ impl EnvBackend for EnvInstance {
}
}

fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode,
{
self.exec_context()
.map(|exec_ctx| &exec_ctx.call_data)
.map(scale::Encode::encode)
.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 input_bytes(&mut self) -> &[u8] {
let encoded = self
.exec_context()
.map(|exec_ctx| exec_ctx.call_data.to_bytes())
.unwrap();
encoded
}

fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R) -> !
where
R: scale::Encode,
{
let ctx = self.exec_context_mut().expect(UNINITIALIZED_EXEC_CONTEXT);
ctx.output = Some(return_value.encode());
// Some UI tests use `return_value`, but they don't use `#[ink::test]`
// In that case only exit from the process to avoid panic that context is uninitialized
if let Ok(ctx) = self.exec_context_mut() {
ctx.output = Some(return_value.encode());
}
std::process::exit(flags.into_u32() as i32)
}

Expand Down
12 changes: 9 additions & 3 deletions crates/env/src/engine/off_chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,16 +256,22 @@ impl EnvInstance {
}
}

impl OnInstance for EnvInstance {
impl<'a> OnInstance<'a> for EnvInstance {
fn on_instance<F, R>(f: F) -> R
where
F: FnOnce(&mut Self) -> R,
F: FnOnce(&'a mut Self) -> R,
{
thread_local!(
static INSTANCE: RefCell<EnvInstance> = RefCell::new(
EnvInstance::uninitialized()
)
);
INSTANCE.with(|instance| f(&mut instance.borrow_mut()))
INSTANCE.with(|instance| {
// SAFETY: The value of `RefCell` will be no change,
// so the lifetime can be extended to `a`(it can be `static`).
let env: &'a mut EnvInstance =
unsafe { core::mem::transmute(&mut *instance.borrow_mut()) };
f(env)
})
}
}
20 changes: 17 additions & 3 deletions crates/env/src/engine/on_chain/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,24 @@ impl core::ops::IndexMut<core::ops::RangeFull> for StaticBuffer {
}
}

impl core::ops::Index<core::ops::RangeTo<usize>> for StaticBuffer {
type Output = [u8];

fn index(&self, index: core::ops::RangeTo<usize>) -> &Self::Output {
core::ops::Index::index(&self.buffer[..], index)
}
}

impl core::ops::IndexMut<core::ops::RangeTo<usize>> for StaticBuffer {
fn index_mut(&mut self, index: core::ops::RangeTo<usize>) -> &mut Self::Output {
core::ops::IndexMut::index_mut(&mut self.buffer[..], index)
}
}

/// Utility to allow for non-heap allocating encoding into a static buffer.
///
/// Required by `ScopedBuffer` internals.
struct EncodeScope<'a> {
pub struct EncodeScope<'a> {
buffer: &'a mut [u8],
len: usize,
}
Expand Down Expand Up @@ -170,7 +184,7 @@ impl<'a> ScopedBuffer<'a> {
/// Appends the encoding of `value` to the scoped buffer.
///
/// Does not return the buffer immediately so that other values can be appended
/// afterwards. The [`take_appended`] method shall be used to return the buffer
/// afterwards. The `take_appended` method shall be used to return the buffer
/// that includes all appended encodings as a single buffer.
pub fn append_encoded<T>(&mut self, value: &T)
where
Expand All @@ -185,7 +199,7 @@ impl<'a> ScopedBuffer<'a> {
let _ = core::mem::replace(&mut self.buffer, buffer);
}

/// Returns the buffer containing all encodings appended via [`append_encoded`]
/// Returns the buffer containing all encodings appended via `append_encoded`
/// in a single byte buffer.
pub fn take_appended(&mut self) -> &'a mut [u8] {
debug_assert_ne!(self.offset, 0);
Expand Down
29 changes: 11 additions & 18 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use super::{
ext,
EncodeScope,
EnvInstance,
Error as ExtError,
ScopedBuffer,
Expand Down Expand Up @@ -206,16 +207,6 @@ impl EnvInstance {
<T as FromLittleEndian>::from_le_bytes(result)
}

/// Returns the contract property value.
fn get_property<T>(&mut self, ext_fn: fn(output: &mut &mut [u8])) -> Result<T>
where
T: scale::Decode,
{
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<T, Args, RetType, R>(
&mut self,
Expand Down Expand Up @@ -283,20 +274,22 @@ impl EnvBackend for EnvInstance {
ext::clear_storage(key.as_ref())
}

fn decode_input<T>(&mut self) -> Result<T>
where
T: scale::Decode,
{
self.get_property::<T>(ext::input)
fn input_bytes(&mut self) -> &[u8] {
if !self.input_buffer.initialized {
ext::input(&mut &mut self.input_buffer.buffer[..]);
self.input_buffer.initialized = true;
}
&self.input_buffer.buffer[..]
}

fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R) -> !
where
R: scale::Encode,
{
let mut scope = self.scoped_buffer();
let enc_return_value = scope.take_encoded(return_value);
ext::return_value(flags, enc_return_value);
let mut scope = EncodeScope::from(&mut self.buffer[..]);
scale::Encode::encode_to(&return_value, &mut scope);
let size = scope.len();
ext::return_value(flags, &self.buffer[..size]);
}

fn debug_message(&mut self, content: &str) {
Expand Down
25 changes: 23 additions & 2 deletions crates/env/src/engine/on_chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,26 @@ mod impls;

use self::{
buffer::{
EncodeScope,
ScopedBuffer,
StaticBuffer,
},
ext::Error,
};
use super::OnInstance;

/// The buffer with static size of 16 kB for the input of the contract.
///
/// If input requires more than that they will fail.
///
/// Please note that this is still an implementation detail and
/// might change. Users should generally avoid storing too big values
/// into single storage entries.
struct InputBuffer {
initialized: bool,
buffer: StaticBuffer,
}

/// The on-chain environment.
pub struct EnvInstance {
/// Encode & decode buffer with static size of 16 kB.
Expand All @@ -37,15 +50,23 @@ pub struct EnvInstance {
/// might change. Users should generally avoid storing too big values
/// into single storage entries.
buffer: StaticBuffer,

/// Please note that this variable should be initialized only one time.
/// After it should be used only for read only.
input_buffer: InputBuffer,
}

impl OnInstance for EnvInstance {
impl<'a> OnInstance<'a> for EnvInstance {
fn on_instance<F, R>(f: F) -> R
where
F: FnOnce(&mut Self) -> R,
F: FnOnce(&'a mut Self) -> R,
{
static mut INSTANCE: EnvInstance = EnvInstance {
buffer: StaticBuffer::new(),
input_buffer: InputBuffer {
initialized: false,
buffer: StaticBuffer::new(),
},
};
f(unsafe { &mut INSTANCE })
}
Expand Down
3 changes: 3 additions & 0 deletions crates/lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ std = [
]
show-codegen-docs = []

# Enable debugging during decoding and execution of messages or constructors.
ink-debug = []

# Due to https://github.com/rust-lang/cargo/issues/6915 features that affect a dev-dependency
# currently can't be enabled by a parent crate, hence `["ink_env/ink-experimental-engine"]` does
# not work.
Expand Down
10 changes: 0 additions & 10 deletions crates/lang/codegen/src/generator/arg_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ pub fn input_types_tuple(inputs: ir::InputsIter) -> TokenStream2 {
}
}

/// Returns a tuple expression representing the bindings yielded by the inputs.
pub fn input_bindings_tuple(inputs: ir::InputsIter) -> TokenStream2 {
let input_bindings = input_bindings(inputs);
match input_bindings.len() {
0 => quote! { _ },
1 => quote! { #( #input_bindings ),* },
_ => quote! { ( #( #input_bindings ),* ) },
}
}

/// Builds up the `ink_env::call::utils::ArgumentList` type structure for the given types.
pub fn generate_argument_list<'b, Args>(args: Args) -> TokenStream2
where
Expand Down
Loading