diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 24c8e826f2b..8fe739184d8 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -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 /// @@ -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() -> Result -where - T: scale::Decode, -{ - ::on_instance(|instance| { - EnvBackend::decode_input::(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] { + >::on_instance(|instance| { + EnvBackend::input_bytes(instance) }) } diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 733bce851b7..3a7dd5f3a8f 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -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 } @@ -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 /// @@ -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(&mut self) -> Result - 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. /// diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 87501d3e27e..de681409b1c 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -209,20 +209,15 @@ impl EnvBackend for EnvInstance { self.engine.clear_storage(key.as_ref()) } - fn decode_input(&mut self) -> Result - where - T: scale::Decode, - { + fn input_bytes(&mut self) -> &[u8] { unimplemented!("the experimental off chain env does not implement `seal_input`") } - fn return_value(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! + fn return_value(&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) { diff --git a/crates/env/src/engine/experimental_off_chain/mod.rs b/crates/env/src/engine/experimental_off_chain/mod.rs index c97b9d69b0a..70622a257b4 100644 --- a/crates/env/src/engine/experimental_off_chain/mod.rs +++ b/crates/env/src/engine/experimental_off_chain/mod.rs @@ -30,10 +30,10 @@ pub struct EnvInstance { engine: Engine, } -impl OnInstance for EnvInstance { +impl<'a> OnInstance<'a> for EnvInstance { fn on_instance(f: F) -> R where - F: FnOnce(&mut Self) -> R, + F: FnOnce(&'a mut Self) -> R, { use core::cell::RefCell; thread_local!( @@ -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) + }) } } diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 129820a1751..7bd268efb78 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -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: F) -> R where - F: FnOnce(&mut Self) -> R; + F: FnOnce(&'a mut Self) -> R; } cfg_if! { diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 98817c2cc3b..931b9947d74 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -147,27 +147,23 @@ impl EnvBackend for EnvInstance { } } - fn decode_input(&mut self) -> Result - 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| { - ::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(&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) } diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs index c962239fc44..3086c0680e0 100644 --- a/crates/env/src/engine/off_chain/mod.rs +++ b/crates/env/src/engine/off_chain/mod.rs @@ -256,16 +256,22 @@ impl EnvInstance { } } -impl OnInstance for EnvInstance { +impl<'a> OnInstance<'a> for EnvInstance { fn on_instance(f: F) -> R where - F: FnOnce(&mut Self) -> R, + F: FnOnce(&'a mut Self) -> R, { thread_local!( static INSTANCE: RefCell = 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) + }) } } diff --git a/crates/env/src/engine/on_chain/buffer.rs b/crates/env/src/engine/on_chain/buffer.rs index 469238f2524..dcb55c02a2f 100644 --- a/crates/env/src/engine/on_chain/buffer.rs +++ b/crates/env/src/engine/on_chain/buffer.rs @@ -44,10 +44,24 @@ impl core::ops::IndexMut for StaticBuffer { } } +impl core::ops::Index> for StaticBuffer { + type Output = [u8]; + + fn index(&self, index: core::ops::RangeTo) -> &Self::Output { + core::ops::Index::index(&self.buffer[..], index) + } +} + +impl core::ops::IndexMut> for StaticBuffer { + fn index_mut(&mut self, index: core::ops::RangeTo) -> &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, } @@ -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(&mut self, value: &T) where @@ -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); diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 2a2e585a77a..4411c2709cb 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -14,6 +14,7 @@ use super::{ ext, + EncodeScope, EnvInstance, Error as ExtError, ScopedBuffer, @@ -206,16 +207,6 @@ impl EnvInstance { ::from_le_bytes(result) } - /// Returns the contract property value. - fn get_property(&mut self, ext_fn: fn(output: &mut &mut [u8])) -> Result - 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( &mut self, @@ -283,20 +274,22 @@ impl EnvBackend for EnvInstance { ext::clear_storage(key.as_ref()) } - fn decode_input(&mut self) -> Result - where - T: scale::Decode, - { - self.get_property::(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(&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) { diff --git a/crates/env/src/engine/on_chain/mod.rs b/crates/env/src/engine/on_chain/mod.rs index 2bee4c0785b..90cef5aefea 100644 --- a/crates/env/src/engine/on_chain/mod.rs +++ b/crates/env/src/engine/on_chain/mod.rs @@ -18,6 +18,7 @@ mod impls; use self::{ buffer::{ + EncodeScope, ScopedBuffer, StaticBuffer, }, @@ -25,6 +26,18 @@ use self::{ }; 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. @@ -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: 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 }) } diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index ec927257112..a43019434a3 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -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. diff --git a/crates/lang/codegen/src/generator/arg_list.rs b/crates/lang/codegen/src/generator/arg_list.rs index bcc771a46cb..441393ed15c 100644 --- a/crates/lang/codegen/src/generator/arg_list.rs +++ b/crates/lang/codegen/src/generator/arg_list.rs @@ -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 diff --git a/crates/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs index 45138d8d05f..bbdc367e812 100644 --- a/crates/lang/codegen/src/generator/dispatch.rs +++ b/crates/lang/codegen/src/generator/dispatch.rs @@ -37,11 +37,6 @@ use syn::spanned::Spanned as _; /// by inspecting the first four bytes (selector) of the given input bytes. /// /// As this happens on every contract execution this code must be highly optimized. -/// For that purpose a so-called dispatch enum is being generated that has a -/// specialized `scale::Decode` implementation taking the first four bytes of -/// the input stream in order to identify the enum variant that it is going to -/// produce out of the rest of the input buffer. -/// /// The rest of the input buffer is then automatically decoded directly into the /// expected input types of the respective ink! constructor or message. #[derive(From)] @@ -69,9 +64,9 @@ impl GenerateCode for Dispatch<'_> { self.generate_dispatchable_constructor_infos(); let contract_dispatchable_messages_infos = self.generate_dispatchable_message_infos(); - let constructor_decoder_type = - self.generate_constructor_decoder_type(&constructor_spans); - let message_decoder_type = self.generate_message_decoder_type(&message_spans); + let constructor_executor_type = + self.generate_constructor_executor_type(&constructor_spans); + let message_executor_type = self.generate_message_executor_type(&message_spans); let entry_points = self.generate_entry_points(&constructor_spans, &message_spans); quote! { #amount_dispatchables @@ -79,8 +74,8 @@ impl GenerateCode for Dispatch<'_> { #contract_dispatchable_constructors #contract_dispatchable_constructor_infos #contract_dispatchable_messages_infos - #constructor_decoder_type - #message_decoder_type + #constructor_executor_type + #message_executor_type #[cfg(not(test))] #cfg_not_as_dependency @@ -270,17 +265,28 @@ impl Dispatch<'_> { let payable = constructor.is_payable(); let selector_id = constructor.composed_selector().into_be_u32().hex_padded_suffixed(); let selector_bytes = constructor.composed_selector().hex_lits(); - let input_bindings = generator::input_bindings(constructor.inputs()); + let input_bindings = constructor.inputs() + .map(|arg| { + let arg_span = arg.span(); + quote_spanned! (arg_span => + ::ink_lang::codegen::utils::unwrap_constructor( + ::scale::Decode::decode(&mut _input), + ::ink_lang::reflect::DispatchError::InvalidParameters, + ) + ) + }) + .collect::>(); let input_tuple_type = generator::input_types_tuple(constructor.inputs()); - let input_tuple_bindings = generator::input_bindings_tuple(constructor.inputs()); quote_spanned!(constructor_span=> impl ::ink_lang::reflect::DispatchableConstructorInfo<#selector_id> for #storage_ident { type Input = #input_tuple_type; type Storage = #storage_ident; - const CALLABLE: fn(Self::Input) -> Self::Storage = |#input_tuple_bindings| { - #storage_ident::#constructor_ident( #( #input_bindings ),* ) - }; + const CALLABLE: fn(&[u8]) -> ::ink_lang::reflect::Result = + |mut _input| { + let result = #storage_ident::#constructor_ident( #( #input_bindings ),* ); + ::core::result::Result::Ok(result) + }; const PAYABLE: ::core::primitive::bool = #payable; const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; const LABEL: &'static ::core::primitive::str = ::core::stringify!(#constructor_ident); @@ -316,18 +322,29 @@ impl Dispatch<'_> { .output() .map(quote::ToTokens::to_token_stream) .unwrap_or_else(|| quote! { () }); - let input_bindings = generator::input_bindings(message.inputs()); + let input_bindings = message.inputs() + .map(|arg| { + let arg_span = arg.span(); + quote_spanned! (arg_span => + ::ink_lang::codegen::utils::unwrap_message( + ::scale::Decode::decode(&mut _input), + ::ink_lang::reflect::DispatchError::InvalidParameters, + ) + ) + }) + .collect::>(); let input_tuple_type = generator::input_types_tuple(message.inputs()); - let input_tuple_bindings = generator::input_bindings_tuple(message.inputs()); quote_spanned!(message_span=> impl ::ink_lang::reflect::DispatchableMessageInfo<#selector_id> for #storage_ident { type Input = #input_tuple_type; type Output = #output_tuple_type; type Storage = #storage_ident; - const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output = - |storage, #input_tuple_bindings| { - #storage_ident::#message_ident( storage #( , #input_bindings )* ) + const CALLABLE: fn(&mut Self::Storage, &[u8]) + -> ::ink_lang::reflect::Result = + |storage, mut _input| { + let result = #storage_ident::#message_ident( storage #( , #input_bindings )* ); + ::core::result::Result::Ok(result) }; const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; const PAYABLE: ::core::primitive::bool = #payable; @@ -373,9 +390,18 @@ impl Dispatch<'_> { .output() .map(quote::ToTokens::to_token_stream) .unwrap_or_else(|| quote! { () }); - let input_bindings = generator::input_bindings(message.inputs()); + let input_bindings = message.inputs() + .map(|arg| { + let arg_span = arg.span(); + quote_spanned! (arg_span => + ::ink_lang::codegen::utils::unwrap_message( + ::scale::Decode::decode(&mut _input), + ::ink_lang::reflect::DispatchError::InvalidParameters, + ) + ) + }) + .collect::>(); let input_tuple_type = generator::input_types_tuple(message.inputs()); - let input_tuple_bindings = generator::input_bindings_tuple(message.inputs()); let label = format!("{}::{}", trait_ident, message_ident); quote_spanned!(message_span=> impl ::ink_lang::reflect::DispatchableMessageInfo<#selector_id> for #storage_ident { @@ -383,9 +409,11 @@ impl Dispatch<'_> { type Output = #output_tuple_type; type Storage = #storage_ident; - const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output = - |storage, #input_tuple_bindings| { - #storage_ident::#message_ident( storage #( , #input_bindings )* ) + const CALLABLE: fn(&mut Self::Storage, &[u8]) + -> ::ink_lang::reflect::Result = + |storage, mut _input| { + let result = #storage_ident::#message_ident( storage #( , #input_bindings )* ); + ::core::result::Result::Ok(result) }; const SELECTOR: [::core::primitive::u8; 4usize] = #selector; const PAYABLE: ::core::primitive::bool = #payable; @@ -420,137 +448,60 @@ impl Dispatch<'_> { #[no_mangle] #[allow(clippy::nonminimal_bool)] fn deploy() { - if !#any_constructor_accept_payment { + if !(#any_constructor_accept_payment) { ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } - ::ink_env::decode_input::< - <#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type>() - .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) - .and_then(|decoder| { - <<#storage_ident as ::ink_lang::reflect::ContractConstructorDecoder>::Type - as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) - }) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! constructor failed: {}", error) - }) + let input = ::ink_env::input_bytes(); + <<#storage_ident as ::ink_lang::reflect::ContractConstructorExecutor>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(input).unwrap() } #[cfg(not(test))] #[no_mangle] #[allow(clippy::nonminimal_bool)] fn call() { - if !#any_message_accept_payment { + if !(#any_message_accept_payment) { ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } - - ::ink_env::decode_input::< - <#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type>() - .map_err(|_| ::ink_lang::reflect::DispatchError::CouldNotReadInput) - .and_then(|decoder| { - <<#storage_ident as ::ink_lang::reflect::ContractMessageDecoder>::Type - as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) - }) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! message failed: {}", error) - }) + let input = ::ink_env::input_bytes(); + <<#storage_ident as ::ink_lang::reflect::ContractMessageExecutor>::Type + as ::ink_lang::reflect::ExecuteDispatchable>::execute_dispatchable(input).unwrap() } ) } - /// Generates code for the ink! constructor decoder type of the ink! smart contract. + /// Generates code for the ink! constructor executor type of the ink! smart contract. /// - /// This type can be used in order to decode the input bytes received by a call to `deploy` + /// This type can be used in order to execute the input bytes received by a call to `deploy` /// into one of the available dispatchable ink! constructors and their arguments. - fn generate_constructor_decoder_type( + fn generate_constructor_executor_type( &self, constructor_spans: &[proc_macro2::Span], ) -> TokenStream2 { assert_eq!(constructor_spans.len(), self.query_amount_constructors()); - /// Expands into the token sequence to represent the - /// input type of the ink! constructor at the given index. - fn expand_constructor_input( - span: proc_macro2::Span, - storage_ident: &syn::Ident, - constructor_index: usize, - ) -> TokenStream2 { - quote_spanned!(span=> - <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ - <#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{ - <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS - }>>::IDS[#constructor_index] - }>>::Input - ) - } - - /// Returns the n-th ink! constructor identifier for the decoder type. - fn constructor_variant_ident(n: usize) -> syn::Ident { - quote::format_ident!("Constructor{}", n) - } + let any_constructor_accept_payment = + self.any_constructor_accepts_payment_expr(constructor_spans); let span = self.contract.module().storage().span(); let storage_ident = self.contract.module().storage().ident(); let count_constructors = self.query_amount_constructors(); - let constructors_variants = (0..count_constructors).map(|index| { - let constructor_span = constructor_spans[index]; - let constructor_ident = constructor_variant_ident(index); - let constructor_input = - expand_constructor_input(constructor_span, storage_ident, index); - quote_spanned!(constructor_span=> - #constructor_ident(#constructor_input) - ) - }); - let constructor_match = (0..count_constructors).map(|index| { + + let constructor_body = |index: usize| -> TokenStream2 { let constructor_span = constructor_spans[index]; - let constructor_ident = constructor_variant_ident(index); - let constructor_selector = quote_spanned!(span=> - <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ + + let deny_payment = quote_spanned!(constructor_span=> + #any_constructor_accept_payment && + !<#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ <#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{ <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS }>>::IDS[#index] - }>>::SELECTOR + }>>::PAYABLE ); - let constructor_input = expand_constructor_input(constructor_span, storage_ident, index); - quote_spanned!(constructor_span=> - #constructor_selector => { - ::core::result::Result::Ok(Self::#constructor_ident( - <#constructor_input as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)? - )) - } - ) - }); - let possibly_wildcard_selector_constructor = match self - .query_wildcard_constructor() - { - Some(wildcard_index) => { - let constructor_span = constructor_spans[wildcard_index]; - let constructor_ident = constructor_variant_ident(wildcard_index); - let constructor_input = expand_constructor_input( - constructor_span, - storage_ident, - wildcard_index, - ); - quote! { - ::core::result::Result::Ok(Self::#constructor_ident( - <#constructor_input as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)? - )) - } - } - None => { - quote! { - ::core::result::Result::Err(::ink_lang::reflect::DispatchError::UnknownSelector) - } - } - }; - let constructor_execute = (0..count_constructors).map(|index| { - let constructor_span = constructor_spans[index]; - let constructor_ident = constructor_variant_ident(index); let constructor_callable = quote_spanned!(constructor_span=> <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ <#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{ @@ -558,164 +509,100 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::CALLABLE ); - let accepts_payment = quote_spanned!(constructor_span=> - false || - <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ - <#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{ - <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS - }>>::IDS[#index] - }>>::PAYABLE - ); let is_dynamic_storage_allocation_enabled = self .contract .config() .is_dynamic_storage_allocator_enabled(); + quote_spanned!(constructor_span=> { + if #deny_payment { + ::ink_lang::codegen::utils::unwrap_message( + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>(), + ::ink_lang::reflect::DispatchError::PaidUnpayableMessage, + ) + } - quote_spanned!(constructor_span=> - Self::#constructor_ident(input) => { ::ink_lang::codegen::execute_constructor::<#storage_ident, _, _>( ::ink_lang::codegen::ExecuteConstructorConfig { - payable: #accepts_payment, dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled }, move || { #constructor_callable(input) } ) } ) + }; + + let possibly_wildcard_selector_constructor = match self + .query_wildcard_constructor() + { + Some(wildcard_index) => constructor_body(wildcard_index), + None => { + quote! { + ::ink_lang::codegen::utils::unwrap_constructor( + ::core::result::Result::Err(::ink_lang::reflect::DispatchError::UnknownSelector), + ::ink_lang::reflect::DispatchError::UnknownSelector, + ) + } + } + }; + + let constructor_execute = (0..count_constructors).map(|index| { + let constructor_span = constructor_spans[index]; + let constructor_selector = quote_spanned!(constructor_span=> + <#storage_ident as ::ink_lang::reflect::DispatchableConstructorInfo<{ + <#storage_ident as ::ink_lang::reflect::ContractDispatchableConstructors<{ + <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::CONSTRUCTORS + }>>::IDS[#index] + }>>::SELECTOR + ); + let body = constructor_body(index); + quote_spanned!(constructor_span=> + #constructor_selector => #body + ) }); quote_spanned!(span=> const _: () = { #[allow(non_camel_case_types)] - pub enum __ink_ConstructorDecoder { - #( #constructors_variants ),* - } - - impl ::ink_lang::reflect::DecodeDispatch for __ink_ConstructorDecoder { - fn decode_dispatch(input: &mut I) - -> ::core::result::Result - where - I: ::scale::Input, - { - match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)? - { - #( #constructor_match , )* - _invalid => #possibly_wildcard_selector_constructor - } - } - } - - impl ::scale::Decode for __ink_ConstructorDecoder { - fn decode(input: &mut I) -> ::core::result::Result - where - I: ::scale::Input, - { - ::decode_dispatch(input) - .map_err(::core::convert::Into::into) - } - } + pub struct __ink_ConstructorExecutor; - impl ::ink_lang::reflect::ExecuteDispatchable for __ink_ConstructorDecoder { + impl ::ink_lang::reflect::ExecuteDispatchable for __ink_ConstructorExecutor { #[allow(clippy::nonminimal_bool)] - fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink_lang::reflect::DispatchError> { - match self { - #( #constructor_execute ),* + fn execute_dispatchable(mut input: &[u8]) -> ::core::result::Result<(), ::ink_lang::reflect::DispatchError> { + match ::ink_lang::codegen::utils::unwrap_constructor( + <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(&mut input), + ::ink_lang::reflect::DispatchError::InvalidSelector, + ) + { + #( #constructor_execute , )* + _invalid => #possibly_wildcard_selector_constructor, } } } - impl ::ink_lang::reflect::ContractConstructorDecoder for #storage_ident { - type Type = __ink_ConstructorDecoder; + impl ::ink_lang::reflect::ContractConstructorExecutor for #storage_ident { + type Type = __ink_ConstructorExecutor; } }; ) } - /// Generates code for the ink! message decoder type of the ink! smart contract. + /// Generates code for the ink! message executor type of the ink! smart contract. /// - /// This type can be used in order to decode the input bytes received by a call to `call` + /// This type can be used in order to execute the input bytes received by a call to `call` /// into one of the available dispatchable ink! messages and their arguments. - fn generate_message_decoder_type( + fn generate_message_executor_type( &self, message_spans: &[proc_macro2::Span], ) -> TokenStream2 { assert_eq!(message_spans.len(), self.query_amount_messages()); - /// Expands into the token sequence to represent the - /// input type of the ink! message at the given index. - fn expand_message_input( - span: proc_macro2::Span, - storage_ident: &syn::Ident, - message_index: usize, - ) -> TokenStream2 { - quote_spanned!(span=> - <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ - <#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{ - <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES - }>>::IDS[#message_index] - }>>::Input - ) - } - - /// Returns the n-th ink! message identifier for the decoder type. - fn message_variant_ident(n: usize) -> syn::Ident { - quote::format_ident!("Message{}", n) - } - let span = self.contract.module().storage().span(); let storage_ident = self.contract.module().storage().ident(); let count_messages = self.query_amount_messages(); - let message_variants = (0..count_messages).map(|index| { - let message_span = message_spans[index]; - let message_ident = message_variant_ident(index); - let message_input = expand_message_input(message_span, storage_ident, index); - quote_spanned!(message_span=> - #message_ident(#message_input) - ) - }); - let message_match = (0..count_messages).map(|index| { - let message_span = message_spans[index]; - let message_ident = message_variant_ident(index); - let message_selector = quote_spanned!(span=> - <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ - <#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{ - <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES - }>>::IDS[#index] - }>>::SELECTOR - ); - let message_input = expand_message_input(message_span, storage_ident, index); - quote_spanned!(message_span=> - #message_selector => { - ::core::result::Result::Ok(Self::#message_ident( - <#message_input as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)? - )) - } - ) - }); - let possibly_wildcard_selector_message = match self.query_wildcard_message() { - Some(wildcard_index) => { - let message_span = message_spans[wildcard_index]; - let message_ident = message_variant_ident(wildcard_index); - let message_input = - expand_message_input(message_span, storage_ident, wildcard_index); - quote! { - ::core::result::Result::Ok(Self::#message_ident( - <#message_input as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidParameters)? - )) - } - } - None => { - quote! { - ::core::result::Result::Err(::ink_lang::reflect::DispatchError::UnknownSelector) - } - } - }; + let any_message_accept_payment = + self.any_message_accepts_payment_expr(message_spans); - let message_execute = (0..count_messages).map(|index| { + let message_body = |index: usize| -> TokenStream2 { let message_span = message_spans[index]; - let message_ident = message_variant_ident(index); let message_callable = quote_spanned!(message_span=> <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ <#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{ @@ -730,9 +617,9 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::Output ); - let accepts_payment = quote_spanned!(message_span=> - false || - <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ + let deny_payment = quote_spanned!(message_span=> + #any_message_accept_payment && + !<#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ <#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{ <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES }>>::IDS[#index] @@ -745,79 +632,105 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::MUTATES ); - let is_dynamic_storage_allocation_enabled = self - .contract - .config() - .is_dynamic_storage_allocator_enabled(); - - quote_spanned!(message_span=> - Self::#message_ident(input) => { - let config = ::ink_lang::codegen::ExecuteMessageConfig { - payable: #accepts_payment, - mutates: #mutates_storage, - dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled, - }; - let mut contract: ::core::mem::ManuallyDrop<#storage_ident> = - ::core::mem::ManuallyDrop::new( - ::ink_lang::codegen::initiate_message::<#storage_ident>(config)? - ); - let result: #message_output = #message_callable(&mut contract, input); + quote_spanned!(message_span=> { + if #deny_payment { + ::ink_lang::codegen::utils::unwrap_message( + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>(), + ::ink_lang::reflect::DispatchError::PaidUnpayableMessage, + ) + } + let result: #message_output = #message_callable(&mut contract, input)?; let failure = ::ink_lang::is_result_type!(#message_output) && ::ink_lang::is_result_err!(result); - ::ink_lang::codegen::finalize_message::<#storage_ident, #message_output>( - !failure, - &contract, - config, - &result, - )?; - ::core::result::Result::Ok(()) + + use ::core::default::Default; + + if failure { + // if result is error it stops execution and returns an error to caller + ::ink_env::return_value(::ink_env::ReturnFlags::default().set_reverted(true), &result); + } + + maybe_push(#mutates_storage, &contract, &root_key); + ::ink_env::return_value(::ink_env::ReturnFlags::default(), &result); + } + ) + }; + + let message_execute = (0..count_messages).map(|index| { + let message_span = message_spans[index]; + let message_selector = quote_spanned!(message_span=> + <#storage_ident as ::ink_lang::reflect::DispatchableMessageInfo<{ + <#storage_ident as ::ink_lang::reflect::ContractDispatchableMessages<{ + <#storage_ident as ::ink_lang::reflect::ContractAmountDispatchables>::MESSAGES + }>>::IDS[#index] + }>>::SELECTOR + ); + let body = message_body(index); + quote_spanned!(message_span=> + #message_selector => { + #body } ) }); + let possibly_wildcard_selector_message = match self.query_wildcard_message() { + Some(wildcard_index) => message_body(wildcard_index), + None => { + quote! { + ::ink_lang::codegen::utils::unwrap_message( + ::core::result::Result::Err(::ink_lang::reflect::DispatchError::UnknownSelector), + ::ink_lang::reflect::DispatchError::UnknownSelector, + ) + } + } + }; + + let is_dynamic_storage_allocation_enabled = self + .contract + .config() + .is_dynamic_storage_allocator_enabled(); + quote_spanned!(span=> const _: () = { #[allow(non_camel_case_types)] - pub enum __ink_MessageDecoder { - #( #message_variants ),* - } + // #[derive(::core::fmt::Debug, ::core::cmp::PartialEq)] + pub struct __ink_MessageExecutor; - impl ::ink_lang::reflect::DecodeDispatch for __ink_MessageDecoder { - fn decode_dispatch(input: &mut I) - -> ::core::result::Result - where - I: ::scale::Input, - { - match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input) - .map_err(|_| ::ink_lang::reflect::DispatchError::InvalidSelector)? - { - #( #message_match , )* - _invalid => #possibly_wildcard_selector_message + impl ::ink_lang::reflect::ExecuteDispatchable for __ink_MessageExecutor { + #[allow(clippy::nonminimal_bool)] + fn execute_dispatchable(mut input: &[u8]) -> ::core::result::Result<(), ::ink_lang::reflect::DispatchError> { + fn maybe_push(mutates: bool, contract: &::core::mem::ManuallyDrop<#storage_ident>, root_key: &::ink_primitives::Key) { + if mutates { + ::ink_storage::traits::push_spread_root::<#storage_ident>(contract, root_key); + } + if #is_dynamic_storage_allocation_enabled { + ::ink_storage::alloc::finalize(); + } } - } - } - impl ::scale::Decode for __ink_MessageDecoder { - fn decode(input: &mut I) -> ::core::result::Result - where - I: ::scale::Input, - { - ::decode_dispatch(input) - .map_err(::core::convert::Into::into) - } - } + if #is_dynamic_storage_allocation_enabled { + ::ink_storage::alloc::initialize(::ink_storage::alloc::ContractPhase::Call); + } - impl ::ink_lang::reflect::ExecuteDispatchable for __ink_MessageDecoder { - #[allow(clippy::nonminimal_bool)] - fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink_lang::reflect::DispatchError> { - match self { + let root_key = <#storage_ident as ::ink_lang::codegen::ContractRootKey>::ROOT_KEY; + let mut contract: ::core::mem::ManuallyDrop<#storage_ident> = + ::core::mem::ManuallyDrop::new( + ::ink_storage::traits::pull_spread_root::<#storage_ident>(&root_key) + ); + + match ::ink_lang::codegen::utils::unwrap_message( + <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(&mut input), + ::ink_lang::reflect::DispatchError::InvalidSelector, + ) + { #( #message_execute ),* + _invalid => #possibly_wildcard_selector_message, } } } - impl ::ink_lang::reflect::ContractMessageDecoder for #storage_ident { - type Type = __ink_MessageDecoder; + impl ::ink_lang::reflect::ContractMessageExecutor for #storage_ident { + type Type = __ink_MessageExecutor; } }; ) diff --git a/crates/lang/codegen/src/generator/mod.rs b/crates/lang/codegen/src/generator/mod.rs index def2f2d3c83..adbe1745cd8 100644 --- a/crates/lang/codegen/src/generator/mod.rs +++ b/crates/lang/codegen/src/generator/mod.rs @@ -46,7 +46,6 @@ pub use self::{ generate_argument_list, generate_reference_to_trait_info, input_bindings, - input_bindings_tuple, input_types, input_types_tuple, output_ident, diff --git a/crates/lang/src/codegen/dispatch/execution.rs b/crates/lang/src/codegen/dispatch/execution.rs index 0e1a871c695..160f0a68a74 100644 --- a/crates/lang/src/codegen/dispatch/execution.rs +++ b/crates/lang/src/codegen/dispatch/execution.rs @@ -17,7 +17,6 @@ use crate::reflect::{ DispatchError, }; use core::{ - any::TypeId, convert::Infallible, mem::ManuallyDrop, }; @@ -33,7 +32,6 @@ use ink_storage::{ alloc, alloc::ContractPhase, traits::{ - pull_spread_root, push_spread_root, SpreadAllocate, SpreadLayout, @@ -73,8 +71,6 @@ where /// Configuration for execution of ink! constructor. #[derive(Debug, Copy, Clone)] pub struct ExecuteConstructorConfig { - /// Yields `true` if the ink! constructor accepts payment. - pub payable: bool, /// Yields `true` if the dynamic storage allocator has been enabled. /// /// # Note @@ -96,17 +92,14 @@ pub fn execute_constructor( ) -> Result<(), DispatchError> where Contract: SpreadLayout + ContractRootKey + ContractEnv, - F: FnOnce() -> R, + F: FnOnce() -> Result, as ConstructorReturnType>::ReturnValue: scale::Encode, private::Seal: ConstructorReturnType, { - if !config.payable { - deny_payment::<::Env>()?; - } if config.dynamic_storage_alloc { alloc::initialize(ContractPhase::Deploy); } - let result = ManuallyDrop::new(private::Seal(f())); + let result = ManuallyDrop::new(private::Seal(f()?)); match result.as_result() { Ok(contract) => { // Constructor is infallible or is fallible but succeeded. @@ -299,97 +292,3 @@ pub struct ExecuteMessageConfig { /// Authors can enable it via `#[ink::contract(dynamic_storage_allocator = true)]`. pub dynamic_storage_alloc: bool, } - -/// Initiates an ink! message call with the given configuration. -/// -/// Returns the contract state pulled from the root storage region upon success. -/// -/// # Note -/// -/// This work around that splits executing an ink! message into initiate -/// and finalize phases was needed due to the fact that `is_result_type` -/// and `is_result_err` macros do not work in generic contexts. -#[inline] -pub fn initiate_message( - config: ExecuteMessageConfig, -) -> Result -where - Contract: SpreadLayout + ContractEnv, -{ - if !config.payable { - deny_payment::<::Env>()?; - } - if config.dynamic_storage_alloc { - alloc::initialize(ContractPhase::Call); - } - let root_key = Key::from([0x00; 32]); - let contract = pull_spread_root::(&root_key); - Ok(contract) -} - -/// Finalizes an ink! message call with the given configuration. -/// -/// This dispatches into fallible and infallible message finalization -/// depending on the given `success` state. -/// -/// - If the message call was successful the return value is simply returned -/// and cached storage is pushed back to the contract storage. -/// - If the message call failed the return value result is returned instead -/// and the transaction is signalled to be reverted. -/// -/// # Note -/// -/// This work around that splits executing an ink! message into initiate -/// and finalize phases was needed due to the fact that `is_result_type` -/// and `is_result_err` macros do not work in generic contexts. -#[inline] -pub fn finalize_message( - success: bool, - contract: &Contract, - config: ExecuteMessageConfig, - result: &R, -) -> Result<(), DispatchError> -where - Contract: SpreadLayout, - R: scale::Encode + 'static, -{ - if success { - finalize_infallible_message(contract, config, result) - } else { - finalize_fallible_message(result) - } -} - -#[inline] -fn finalize_infallible_message( - contract: &Contract, - config: ExecuteMessageConfig, - result: &R, -) -> Result<(), DispatchError> -where - Contract: SpreadLayout, - R: scale::Encode + 'static, -{ - if config.mutates { - let root_key = Key::from([0x00; 32]); - push_spread_root::(contract, &root_key); - } - if config.dynamic_storage_alloc { - alloc::finalize(); - } - if TypeId::of::() != TypeId::of::<()>() { - // In case the return type is `()` we do not return a value. - ink_env::return_value::(ReturnFlags::default(), result) - } - Ok(()) -} - -#[inline] -fn finalize_fallible_message(result: &R) -> ! -where - R: scale::Encode + 'static, -{ - // There is no need to push back the intermediate results of the - // contract since the transaction is going to be reverted. - ink_env::return_value::(ReturnFlags::default().set_reverted(true), result) -} diff --git a/crates/lang/src/codegen/dispatch/mod.rs b/crates/lang/src/codegen/dispatch/mod.rs index abf7d792471..d6979955205 100644 --- a/crates/lang/src/codegen/dispatch/mod.rs +++ b/crates/lang/src/codegen/dispatch/mod.rs @@ -20,9 +20,7 @@ pub use self::{ execution::{ deny_payment, execute_constructor, - finalize_message, initialize_contract, - initiate_message, ContractRootKey, ExecuteConstructorConfig, ExecuteMessageConfig, diff --git a/crates/lang/src/codegen/mod.rs b/crates/lang/src/codegen/mod.rs index 59db390da57..ed41b089ce4 100644 --- a/crates/lang/src/codegen/mod.rs +++ b/crates/lang/src/codegen/mod.rs @@ -25,9 +25,7 @@ pub use self::{ dispatch::{ deny_payment, execute_constructor, - finalize_message, initialize_contract, - initiate_message, ContractCallBuilder, ContractRootKey, DispatchInput, diff --git a/crates/lang/src/codegen/utils/mod.rs b/crates/lang/src/codegen/utils/mod.rs index c99a70b5026..ef40bd1ca7c 100644 --- a/crates/lang/src/codegen/utils/mod.rs +++ b/crates/lang/src/codegen/utils/mod.rs @@ -14,6 +14,11 @@ //! Utility types and definitions used by the ink! codegen. +use core::fmt::{ + Debug, + Display, +}; + mod identity_type; mod same_type; @@ -21,3 +26,35 @@ pub use self::{ identity_type::consume_type, same_type::IsSameType, }; + +#[cfg(any(feature = "ink-debug", feature = "std"))] +pub fn unwrap_constructor( + res: Result, + err: DisplayError, +) -> T { + res.unwrap_or_else(|_| ::core::panic!("dispatching ink! constructor failed: {}", err)) +} + +#[cfg(any(feature = "ink-debug", feature = "std"))] +pub fn unwrap_message( + res: Result, + err: DisplayError, +) -> T { + res.unwrap_or_else(|_| ::core::panic!("dispatching ink! message failed: {}", err)) +} + +#[cfg(not(any(feature = "ink-debug", feature = "std")))] +pub fn unwrap_constructor( + res: Result, + _: DisplayError, +) -> T { + res.unwrap() +} + +#[cfg(not(any(feature = "ink-debug", feature = "std")))] +pub fn unwrap_message( + res: Result, + _: DisplayError, +) -> T { + res.unwrap() +} diff --git a/crates/lang/src/reflect/dispatch.rs b/crates/lang/src/reflect/dispatch.rs index e18e5212214..892dc9e8303 100644 --- a/crates/lang/src/reflect/dispatch.rs +++ b/crates/lang/src/reflect/dispatch.rs @@ -251,7 +251,7 @@ pub trait DispatchableMessageInfo { /// We unify `&self` and `&mut self` ink! messages here and always take a `&mut self`. /// This is mainly done for simplification but also because we can easily convert from /// `&mut self` to `&self` with our current dispatch codegen architecture. - const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output; + const CALLABLE: fn(&mut Self::Storage, &[u8]) -> Result; /// Yields `true` if the dispatchable ink! message mutates the ink! storage. const MUTATES: bool; @@ -337,7 +337,7 @@ pub trait DispatchableConstructorInfo { type Storage; /// The closure that can be used to dispatch into the dispatchable ink! constructor. - const CALLABLE: fn(Self::Input) -> Self::Storage; + const CALLABLE: fn(&[u8]) -> Result; /// Yields `true` if the dispatchable ink! constructor is payable. const PAYABLE: bool; @@ -349,20 +349,20 @@ pub trait DispatchableConstructorInfo { const LABEL: &'static str; } -/// Generated type used to decode all dispatchable ink! messages of the ink! smart contract. +/// Generated type used to execute all dispatchable ink! messages of the ink! smart contract. /// /// # Note /// -/// The decoder follows the ink! calling ABI where all ink! message calls start with +/// The executor follows the ink! calling ABI where all ink! message calls start with /// 4 bytes dedicated to the ink! message selector followed by the SCALE encoded parameters. /// /// # Usage /// /// ``` /// use ink_lang as ink; -/// # use ink_lang::reflect::ContractMessageDecoder; +/// # use ink_lang::reflect::{ContractMessageExecutor, ExecuteDispatchable}; /// # use ink_lang::selector_bytes; -/// # use scale::{Encode, Decode}; +/// # use scale::{Encode}; /// /// #[ink::contract] /// pub mod contract { @@ -389,8 +389,8 @@ pub trait DispatchableConstructorInfo { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("message1")); /// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_ok() +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]).is_ok() /// ); /// } /// // Call to `message2` with 2 parameters. @@ -400,49 +400,51 @@ pub trait DispatchableConstructorInfo { /// input_bytes.extend(true.encode()); /// input_bytes.extend(42i32.encode()); /// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_ok() +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]).is_ok() /// ); /// } /// // Call with invalid ink! message selector. /// { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("non_existing_message")); -/// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_err() -/// ); +/// let result = std::panic::catch_unwind(|| { +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]) +/// }); +/// assert!(result.is_err()); /// } /// // Call with invalid ink! message parameters. /// { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("message2")); -/// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_err() -/// ); +/// let result = std::panic::catch_unwind(|| { +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]) +/// }); +/// assert!(result.is_err()); /// } /// } /// ``` -pub trait ContractMessageDecoder { - /// The ink! smart contract message decoder type. - type Type: scale::Decode + ExecuteDispatchable; +pub trait ContractMessageExecutor { + /// The ink! smart contract message executor type. + type Type: ExecuteDispatchable; } -/// Generated type used to decode all dispatchable ink! constructors of the ink! smart contract. +/// Generated type used to execute all dispatchable ink! constructors of the ink! smart contract. /// /// # Note /// -/// The decoder follows the ink! calling ABI where all ink! constructor calls start with +/// The executor follows the ink! calling ABI where all ink! constructor calls start with /// 4 bytes dedicated to the ink! constructor selector followed by the SCALE encoded parameters. /// /// # Usage /// /// ``` /// use ink_lang as ink; -/// # use ink_lang::reflect::ContractConstructorDecoder; +/// # use ink_lang::reflect::{ContractConstructorExecutor, ExecuteDispatchable}; /// # use ink_lang::selector_bytes; -/// # use scale::{Encode, Decode}; +/// # use scale::{Encode}; /// /// #[ink::contract] /// pub mod contract { @@ -469,8 +471,8 @@ pub trait ContractMessageDecoder { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("constructor1")); /// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_ok() +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]).is_ok() /// ); /// } /// // Call to `constructor2` with 2 parameters. @@ -480,33 +482,35 @@ pub trait ContractMessageDecoder { /// input_bytes.extend(true.encode()); /// input_bytes.extend(42i32.encode()); /// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_ok() +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]).is_ok() /// ); /// } /// // Call with invalid ink! constructor selector. /// { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("non_existing_constructor")); -/// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_err() -/// ); +/// let result = std::panic::catch_unwind(|| { +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]) +/// }); +/// assert!(result.is_err()); /// } /// // Call with invalid ink! constructor parameters. /// { /// let mut input_bytes = Vec::new(); /// input_bytes.extend(selector_bytes!("constructor2")); -/// assert!( -/// <::Type as Decode>::decode( -/// &mut &input_bytes[..]).is_err() -/// ); +/// let result = std::panic::catch_unwind(|| { +/// <::Type as ExecuteDispatchable> +/// ::execute_dispatchable(&input_bytes[..]) +/// }); +/// assert!(result.is_err()); /// } /// } /// ``` -pub trait ContractConstructorDecoder { - /// The ink! smart contract constructor decoder type. - type Type: DecodeDispatch + ExecuteDispatchable; +pub trait ContractConstructorExecutor { + /// The ink! smart contract constructor executor type. + type Type: ExecuteDispatchable; } /// Starts the execution of the respective ink! message or constructor call. @@ -516,7 +520,7 @@ pub trait ContractConstructorDecoder { /// Implemented by the ink! smart contract message or constructor decoder. pub trait ExecuteDispatchable { /// Executes the ink! smart contract message or constructor. - fn execute_dispatchable(self) -> Result<(), DispatchError>; + fn execute_dispatchable(input: &[u8]) -> Result<()>; } /// An error that can occur during dispatch of ink! dispatchables. @@ -528,12 +532,12 @@ pub enum DispatchError { UnknownSelector, /// Failed to decode the parameters for the selected dispatchable. InvalidParameters, - /// Failed to read execution input for the dispatchable. - CouldNotReadInput, /// Invalidly paid an unpayable dispatchable. PaidUnpayableMessage, } +pub type Result = ::core::result::Result; + impl Display for DispatchError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.as_str()) @@ -548,7 +552,6 @@ impl DispatchError { Self::InvalidSelector => "unable to decode selector", Self::UnknownSelector => "encountered unknown selector", Self::InvalidParameters => "unable to decode input", - Self::CouldNotReadInput => "could not read input", Self::PaidUnpayableMessage => "paid an unpayable message", } } @@ -560,96 +563,3 @@ impl From for scale::Error { Self::from(error.as_str()) } } - -/// Decodes an ink! dispatch input into a known selector and its expected parameters. -/// -/// # Note -/// -/// This trait is automatically implemented for ink! message and constructor decoders. -/// -/// # Errors -/// -/// Returns an error if any of the decode steps failed: -/// -/// - `InvalidSelector`: The first four bytes could not properly decoded into the selector. -/// - `UnknownSelector`: The decoded selector did not match any of the expected ones. -/// - `InvalidParameters`: Failed to decoded the parameters for the selected dispatchable. -/// -/// The other dispatch errors are handled by other structures usually. -/// -/// # Usage -/// -/// ``` -/// use ink_lang as ink; -/// # use ink_lang::reflect::{ContractMessageDecoder, DecodeDispatch, DispatchError}; -/// # use ink_lang::selector_bytes; -/// # use scale::Encode; -/// -/// #[ink::contract] -/// pub mod contract { -/// #[ink(storage)] -/// pub struct Contract {} -/// -/// impl Contract { -/// #[ink(constructor)] -/// pub fn constructor() -> Self { Self {} } -/// -/// #[ink(message)] -/// pub fn message(&self, input_1: bool, input_2: i32) {} -/// } -/// } -/// -/// use contract::Contract; -/// -/// fn main() { -/// // Valid call to `message`: -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("message")); -/// input_bytes.extend(true.encode()); -/// input_bytes.extend(42i32.encode()); -/// assert!( -/// <::Type as DecodeDispatch>::decode_dispatch( -/// &mut &input_bytes[..]).is_ok() -/// ); -/// } -/// // Invalid call with invalid selector (or empty input). -/// { -/// let mut input_bytes = Vec::new(); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::InvalidSelector, -/// ); -/// } -/// // Invalid call to `message` with unknown selector. -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("unknown_selector")); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::UnknownSelector, -/// ); -/// } -/// // Invalid call to `message` with invalid (or missing) parameters. -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("message")); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::InvalidParameters, -/// ); -/// } -/// } -/// ``` -pub trait DecodeDispatch: scale::Decode { - fn decode_dispatch(input: &mut I) -> Result; -} diff --git a/crates/lang/src/reflect/mod.rs b/crates/lang/src/reflect/mod.rs index bca4f0aca01..398953283b4 100644 --- a/crates/lang/src/reflect/mod.rs +++ b/crates/lang/src/reflect/mod.rs @@ -36,15 +36,15 @@ pub use self::{ }, dispatch::{ ContractAmountDispatchables, - ContractConstructorDecoder, + ContractConstructorExecutor, ContractDispatchableConstructors, ContractDispatchableMessages, - ContractMessageDecoder, - DecodeDispatch, + ContractMessageExecutor, DispatchError, DispatchableConstructorInfo, DispatchableMessageInfo, ExecuteDispatchable, + Result, }, event::ContractEventBase, trait_def::{ diff --git a/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr index bb2671f71fd..0914e4d299d 100644 --- a/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr @@ -12,12 +12,10 @@ note: required by a bound in `DispatchInput` | ^^^^^^^^^^^^^ required by this bound in `DispatchInput` error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied - --> tests/ui/contract/fail/constructor-input-non-codec.rs:13:9 + --> tests/ui/contract/fail/constructor-input-non-codec.rs:13:28 | -13 | / pub fn constructor(_input: NonCodecType) -> Self { -14 | | Self {} -15 | | } - | |_________^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` +13 | pub fn constructor(_input: NonCodecType) -> Self { + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` diff --git a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr index 9299baed8f3..f7639e717fe 100644 --- a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr @@ -12,10 +12,10 @@ note: required by a bound in `DispatchInput` | ^^^^^^^^^^^^^ required by this bound in `DispatchInput` error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied - --> tests/ui/contract/fail/message-input-non-codec.rs:18:9 + --> tests/ui/contract/fail/message-input-non-codec.rs:18:31 | 18 | pub fn message(&self, _input: NonCodecType) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` diff --git a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr index 44cf44c34bb..ec1922e3a2d 100644 --- a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -20,11 +20,11 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied | |_________^ the trait `WrapperTypeEncode` is not implemented for `NonCodecType` | = note: required because of the requirements on the impl of `Encode` for `NonCodecType` -note: required by a bound in `finalize_message` - --> src/codegen/dispatch/execution.rs +note: required by a bound in `return_value` + --> $WORKSPACE/crates/env/src/api.rs | - | R: scale::Encode + 'static, - | ^^^^^^^^^^^^^ required by this bound in `finalize_message` + | R: scale::Encode, + | ^^^^^^^^^^^^^ required by this bound in `return_value` error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder, Unset, Unset, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:18:9 diff --git a/crates/lang/tests/ui/contract/pass/dispatch-decoder-works.rs b/crates/lang/tests/ui/contract/pass/dispatch-decoder-works.rs deleted file mode 100644 index db374f94c16..00000000000 --- a/crates/lang/tests/ui/contract/pass/dispatch-decoder-works.rs +++ /dev/null @@ -1,132 +0,0 @@ -use ink_lang as ink; -use ink_lang::{ - reflect::{ - ContractConstructorDecoder, - ContractMessageDecoder, - DecodeDispatch, - DispatchError, - }, - selector_bytes, -}; -use scale::Encode; - -#[ink::contract] -pub mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor(_input_1: bool, _input_2: i32) -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self, _input_1: bool, _input_2: i32) {} - } -} - -use contract::Contract; - -fn main() { - constructor_decoder_works(); - message_decoder_works(); -} - -fn constructor_decoder_works() { - // Valid call to `constructor`: - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("constructor")); - input_bytes.extend(true.encode()); - input_bytes.extend(42i32.encode()); - assert!( - <::Type as DecodeDispatch>::decode_dispatch( - &mut &input_bytes[..]).is_ok() - ); - } - // Invalid call with invalid selector (or empty input). - { - let input_bytes = Vec::new(); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::InvalidSelector, - ); - } - // Invalid call to `message` with unknown selector. - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("unknown_selector")); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::UnknownSelector, - ); - } - // Invalid call to `message` with invalid (or missing) parameters. - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("constructor")); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::InvalidParameters, - ); - } -} - -fn message_decoder_works() { - // Valid call to `message`: - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("message")); - input_bytes.extend(true.encode()); - input_bytes.extend(42i32.encode()); - assert!( - <::Type as DecodeDispatch>::decode_dispatch( - &mut &input_bytes[..]).is_ok() - ); - } - // Invalid call with invalid selector (or empty input). - { - let input_bytes = Vec::new(); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::InvalidSelector, - ); - } - // Invalid call to `message` with unknown selector. - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("unknown_selector")); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::UnknownSelector, - ); - } - // Invalid call to `message` with invalid (or missing) parameters. - { - let mut input_bytes = Vec::new(); - input_bytes.extend(selector_bytes!("message")); - assert_eq!( - <::Type - as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) - .map(|_| ()) - .unwrap_err(), - DispatchError::InvalidParameters, - ); - } -} diff --git a/crates/lang/tests/ui/contract/pass/dispatch-executor-works.rs b/crates/lang/tests/ui/contract/pass/dispatch-executor-works.rs new file mode 100644 index 00000000000..e17a3105a4d --- /dev/null +++ b/crates/lang/tests/ui/contract/pass/dispatch-executor-works.rs @@ -0,0 +1,150 @@ +use ink_lang as ink; +use ink_lang::{ + reflect::{ + ContractConstructorExecutor, + ContractMessageExecutor, + ExecuteDispatchable, + }, + selector_bytes, +}; +use scale::Encode; + +#[ink::contract] +pub mod contract { + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor)] + pub fn constructor(_input_1: bool, _input_2: i32) -> Self { + Self {} + } + + #[ink(message)] + pub fn message(&self, _input_1: bool, _input_2: i32) {} + } +} + +use contract::Contract; +use std::panic; + +fn panic_eq R + panic::UnwindSafe, R>(f: F, message: &'static str) { + let prev_hook = panic::take_hook(); + panic::set_hook(Box::new(move |panic_info| { + if let Some(s) = panic_info.payload().downcast_ref::() { + assert_eq!(s.as_str(), message); + } else if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + assert_eq!(s, &message); + } else { + panic!("Unknown panic"); + } + })); + let result = panic::catch_unwind(f); + assert!(result.is_err()); + panic::set_hook(prev_hook); +} + +fn main() { + constructor_executor_works(); + message_executor_works() +} + +fn constructor_executor_works() { + // Valid call to `constructor`: + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("constructor")); + input_bytes.extend(true.encode()); + input_bytes.extend(42i32.encode()); + assert!( + <::Type as ExecuteDispatchable> + ::execute_dispatchable(&input_bytes[..]).is_ok() + ); + } + // Invalid call with invalid selector (or empty input). + { + let input_bytes = Vec::new(); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! constructor failed: unable to decode selector", + ); + } + // Invalid call to `message` with unknown selector. + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("unknown_selector")); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! constructor failed: encountered unknown selector", + ); + } + // Invalid call to `message` with invalid (or missing) parameters. + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("constructor")); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! constructor failed: unable to decode input", + ); + } +} + +fn message_executor_works() { + // Valid case at the end of the function because it will exit from the program + + // Invalid call with invalid selector (or empty input). + { + let input_bytes = Vec::new(); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! message failed: unable to decode selector", + ); + } + // Invalid call to `message` with unknown selector. + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("unknown_selector")); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! message failed: encountered unknown selector", + ); + } + // Invalid call to `message` with invalid (or missing) parameters. + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("message")); + panic_eq( + || { + <::Type + as ExecuteDispatchable>::execute_dispatchable(&input_bytes[..]) + }, + "dispatching ink! message failed: unable to decode input", + ); + } + // Valid call to `message`: + { + let mut input_bytes = Vec::new(); + input_bytes.extend(selector_bytes!("message")); + input_bytes.extend(true.encode()); + input_bytes.extend(42i32.encode()); + assert!( + <::Type as ExecuteDispatchable> + ::execute_dispatchable(&input_bytes[..]).is_ok() + ); + } +} diff --git a/examples/erc20/lib.rs b/examples/erc20/lib.rs index 288b36b3512..d8d70458e88 100644 --- a/examples/erc20/lib.rs +++ b/examples/erc20/lib.rs @@ -789,8 +789,7 @@ mod erc20 { ink_env::test::set_caller::(accounts.bob); // Bob tries to transfer tokens from Alice to Eve. - let emitted_events_before = - ink_env::test::recorded_events().collect::>(); + let emitted_events_before = ink_env::test::recorded_events(); assert_eq!( erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), Err(Error::InsufficientBalance) @@ -801,9 +800,8 @@ mod erc20 { initial_allowance ); // No more events must have been emitted - let emitted_events_after = - ink_env::test::recorded_events().collect::>(); - assert_eq!(emitted_events_before.len(), emitted_events_after.len()); + let emitted_events_after = ink_env::test::recorded_events(); + assert_eq!(emitted_events_before.count(), emitted_events_after.count()); } }