diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index d9a4bcc28df..dc9b9282a83 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -526,6 +526,17 @@ where }) } +/// Returns the buffer back to the caller of the executed contract. +/// +/// # Note +/// +/// This function stops the execution of the contract immediately. +pub fn return_value_scoped(return_flags: ReturnFlags, return_value: &[u8]) -> ! { + ::on_instance(|instance| { + EnvBackend::return_value_scoped(instance, return_flags, return_value) + }) +} + /// Returns a random hash seed and the block number since which it was determinable /// by chain observers. /// diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 743ecdfc356..adb18ba384e 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -220,6 +220,17 @@ pub trait EnvBackend { where R: scale::Encode; + /// Returns the buffer to the caller of the executed contract. + /// + /// # Note + /// + /// Calling this method will end contract execution immediately. + /// It will return the given return value back to its caller. + /// + /// The `flags` parameter can be used to revert the state changes of the + /// entire execution if necessary. + fn return_value_scoped(&mut self, flags: ReturnFlags, return_value: &[u8]) -> !; + /// Emit a custom debug message. /// /// The message is appended to the debug buffer which is then supplied to the calling RPC diff --git a/crates/env/src/engine/on_chain/buffer.rs b/crates/env/src/buffer.rs similarity index 97% rename from crates/env/src/engine/on_chain/buffer.rs rename to crates/env/src/buffer.rs index 0ad13729c3c..d769ec0c3eb 100644 --- a/crates/env/src/engine/on_chain/buffer.rs +++ b/crates/env/src/buffer.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Utilities to encode value into buffer + /// A static buffer with 16 kB of capacity. pub struct StaticBuffer { /// The static buffer with a total capacity of 16 kB. @@ -170,7 +172,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 +187,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/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 98f754a3523..66603fa1870 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -227,6 +227,12 @@ impl EnvBackend for EnvInstance { ) } + fn return_value_scoped(&mut self, _flags: ReturnFlags, _return_value: &[u8]) -> ! { + unimplemented!( + "the experimental off chain env does not implement `seal_return_value`" + ) + } + fn debug_message(&mut self, message: &str) { self.engine.debug_message(message) } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 2b25b45ddf5..d6b4458627e 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -175,6 +175,12 @@ impl EnvBackend for EnvInstance { std::process::exit(flags.into_u32() as i32) } + fn return_value_scoped(&mut self, flags: ReturnFlags, return_value: &[u8]) -> ! { + let ctx = self.exec_context_mut().expect(UNINITIALIZED_EXEC_CONTEXT); + ctx.output = Some(return_value.to_vec()); + std::process::exit(flags.into_u32() as i32) + } + fn debug_message(&mut self, message: &str) { self.debug_buf.debug_message(message) } diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 31a9cd5230e..29ff8720d5e 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -298,6 +298,10 @@ impl EnvBackend for EnvInstance { ext::return_value(flags, enc_return_value); } + fn return_value_scoped(&mut self, flags: ReturnFlags, return_value: &[u8]) -> ! { + ext::return_value(flags, return_value); + } + fn debug_message(&mut self, content: &str) { ext::debug_message(content) } diff --git a/crates/env/src/engine/on_chain/mod.rs b/crates/env/src/engine/on_chain/mod.rs index 18302d8a76e..0ce2baefdee 100644 --- a/crates/env/src/engine/on_chain/mod.rs +++ b/crates/env/src/engine/on_chain/mod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod buffer; mod ext; mod impls; @@ -24,6 +23,7 @@ use self::{ ext::Error, }; use super::OnInstance; +use crate::buffer; /// The on-chain environment. pub struct EnvInstance { diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index 609cd35c622..c671f5d01ee 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -64,6 +64,7 @@ extern crate ink_allocator; mod api; mod arithmetic; mod backend; +pub mod buffer; pub mod call; pub mod chain_extension; mod engine; diff --git a/crates/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs index 3937f214550..402daea79fe 100644 --- a/crates/lang/codegen/src/generator/dispatch.rs +++ b/crates/lang/codegen/src/generator/dispatch.rs @@ -650,10 +650,9 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::Output ); - let accepts_payment = quote_spanned!(message_span=> - false || - !#any_message_accept_payment || - <#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] @@ -666,34 +665,31 @@ 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)? - ); + mutates = #mutates_storage; + + if #deny_payment { + ::ink_lang::codegen::deny_payment::<<#storage_ident as ::ink_lang::reflect::ContractEnv>::Env>()?; + } 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, - )?; + enc_return_value = scoped_buffer.take_encoded(&result); + + if failure { + // if result is error it stops execution and returns an error to caller + ::ink_env::return_value_scoped(::ink_env::ReturnFlags::default().set_reverted(true), enc_return_value); + } + ::core::result::Result::Ok(()) } ) }); + let is_dynamic_storage_allocation_enabled = self + .contract + .config() + .is_dynamic_storage_allocator_enabled(); quote_spanned!(span=> const _: () = { @@ -733,9 +729,35 @@ impl Dispatch<'_> { impl ::ink_lang::reflect::ExecuteDispatchable for __ink_MessageDecoder { #[allow(clippy::nonminimal_bool)] fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink_lang::reflect::DispatchError> { + use ::core::convert::From; + use ::core::default::Default; + + if #is_dynamic_storage_allocation_enabled { + ::ink_storage::alloc::initialize(::ink_storage::alloc::ContractPhase::Call); + } + + const CAPACITY: usize = 1 << 14; + let mut local_buffer: [u8; CAPACITY] = [0; CAPACITY]; + let mut scoped_buffer = ::ink_env::buffer::ScopedBuffer::from(&mut local_buffer[..]); + let enc_return_value; + + 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) + ); + let mutates; match self { #( #message_execute ),* + }?; + + if mutates { + ::ink_storage::traits::push_spread_root::<#storage_ident>(&contract, &root_key); + } + if #is_dynamic_storage_allocation_enabled { + ::ink_storage::alloc::finalize(); } + ::ink_env::return_value_scoped(::ink_env::ReturnFlags::default(), enc_return_value); } } diff --git a/crates/lang/src/codegen/dispatch/execution.rs b/crates/lang/src/codegen/dispatch/execution.rs index 28d28e0879b..c43c8cb3a0f 100644 --- a/crates/lang/src/codegen/dispatch/execution.rs +++ b/crates/lang/src/codegen/dispatch/execution.rs @@ -12,12 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::reflect::{ - ContractEnv, - DispatchError, -}; +use crate::reflect::DispatchError; use core::{ - any::TypeId, convert::Infallible, mem::ManuallyDrop, }; @@ -33,7 +29,6 @@ use ink_storage::{ alloc, alloc::ContractPhase, traits::{ - pull_spread_root, push_spread_root, SpreadAllocate, SpreadLayout, @@ -300,97 +295,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 89e912dda40..b3196cec06b 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 d4d317050bb..5ec29154f50 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/tests/ui/contract/fail/constructor-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr index d9258096cb6..31ed5b29c3a 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 @@ -6,9 +6,9 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied | = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` note: required by a bound in `DispatchInput` - --> src/codegen/dispatch/type_check.rs:41:8 + --> src/codegen/dispatch/type_check.rs | -41 | T: scale::Decode + 'static; + | T: scale::Decode + 'static; | ^^^^^^^^^^^^^ required by this bound in `DispatchInput` error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied 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..3f88dccbf37 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 @@ -12,19 +12,14 @@ note: required by a bound in `DispatchOutput` | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied - --> tests/ui/contract/fail/message-returns-non-codec.rs:18:9 - | -18 | / pub fn message(&self) -> NonCodecType { -19 | | NonCodecType -20 | | } - | |_________^ 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 - | - | R: scale::Encode + 'static, - | ^^^^^^^^^^^^^ required by this bound in `finalize_message` + --> tests/ui/contract/fail/message-returns-non-codec.rs:18:9 + | +18 | / pub fn message(&self) -> NonCodecType { +19 | | NonCodecType +20 | | } + | |_________^ the trait `WrapperTypeEncode` is not implemented for `NonCodecType` + | + = note: required because of the requirements on the impl of `Encode` for `NonCodecType` 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