From 178a52f7fefd57470d5f526d0f8a6f21a82cbe83 Mon Sep 17 00:00:00 2001 From: German Date: Tue, 24 Jan 2023 21:55:36 +0000 Subject: [PATCH] Chain Extension: Evaluation of method return type at compile time (#1569) * remove returns_result attr * generic return type based on const eval * working concept of const eval of result type * cargo fmt * code cleanup and docs * fix typos * uncomment other UI tests * clarify comment and fix typos * provide correct args to generics in doctest * Add entry to CHANGELOG * Fix punctuation Co-authored-by: Michael Mueller --- CHANGELOG.md | 15 +- crates/env/src/chain_extension.rs | 157 ++++++++++-------- .../codegen/src/generator/chain_extension.rs | 60 +++---- crates/ink/ir/src/ir/attrs.rs | 79 --------- crates/ink/ir/src/ir/chain_extension.rs | 27 +-- crates/ink/macro/src/lib.rs | 40 ++--- crates/ink/src/chain_extension.rs | 42 ++++- crates/ink/src/lib.rs | 2 + .../tests/ui/chain_extension/E-01-simple.rs | 6 +- examples/rand-extension/lib.rs | 2 +- 10 files changed, 186 insertions(+), 244 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 470a2c77f7..9e0e67c50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add E2E tests for `Mapping` functions - [#1492](https://github.com/paritytech/ink/pull/1492) - Make CallBuilder and CreateBuilder error handling optional - [#1602](https://github.com/paritytech/ink/pull/1602) - Rename `CallBuilder::fire()` method to `invoke()` - [#1604](https://github.com/paritytech/ink/pull/1604) +- Chain Extension: Evaluation of method return type at compile time - [#1569](https://github.com/paritytech/ink/pull/1569). ### Breaking Changes -With this release there are two breaking changes related to the `CallBuilder` and -`CreateBuilder`. +With this release there are three breaking changes related to the `CallBuilder` +`CreateBuilder` and the Chain Extension API. 1. The `invoke()` methods now unwrap the `Result` from `pallet-contracts` under the hood - ([#1602](https://github.com/paritytech/ink/pull/1602)) + ([#1602](https://github.com/paritytech/ink/pull/1602)). + If you wish to handle the error use the new `try_` variants of those methods instead. 1. The `CallBuilder::fire()` method has been renamed to `invoke()` ([#1604](https://github.com/paritytech/ink/pull/1604)) - -For (1), if you which to handle the the error use the new `try_` variants of those -methods instead. +1. The `returns_result` flag has been removed from the `#[ink(extension = …)]` attribute + ([#1569](https://github.com/paritytech/ink/pull/1569)). + We now infer this information at compile time. If `handle_status` is set to `true`, + the return type will still be wrapped into `Result` as before. ## Version 4.0.0-beta diff --git a/crates/env/src/chain_extension.rs b/crates/env/src/chain_extension.rs index cf4a6d8d83..968418da1d 100644 --- a/crates/env/src/chain_extension.rs +++ b/crates/env/src/chain_extension.rs @@ -58,30 +58,29 @@ pub trait FromStatusCode: Sized { /// All tuple types that may act as input parameters for the chain extension method are valid. /// Examples include `()`, `i32`, `(u8, [u8; 5], i32)`, etc. /// - `O` represents the return (or output) type of the chain extension method. -/// Only `Result` or `NoResult` generic types are allowed for `O`. -/// The `Result` type says that the chain extension method returns a `Result` type -/// whereas the `NoResult` type says that the chain extension method returns a non-`Result` value -/// of type `O`. /// - `ErrorCode` represents how the chain extension method handles the chain extension's error code. /// Only `HandleErrorCode` and `IgnoreErrorCode` types are allowed that each say to either properly /// handle or ignore the chain extension's error code respectively. +/// - `const IS_RESULT: bool` indicates if the `O` (output type) is of `Result` type. /// /// The type states for type parameter `O` and `ErrorCode` represent 4 different states: /// /// 1. The chain extension method makes use of the chain extension's error code: `HandleErrorCode(E)` -/// - **A:** The chain extension method returns a `Result` type. -/// - **B:** The chain extension method returns a type `T` that is not a `Result` type: `NoResult` +/// - **A:** The chain extension method returns a `Result` type, i.e. `IS_RESULT` is set to `true`. +/// - **B:** The chain extension method returns a type `O` that is not a `Result` type. +/// The return type is still wrapped into `Result` /// 2. The chain extension ignores the chain extension's error code: `IgnoreErrorCode` -/// - **A:** The chain extension method returns a `Result` type. -/// - **B:** The chain extension method returns a type `T` that is not a `Result` type: `NoResult` +/// - **A:** The chain extension method returns a `Result` type, i.e. `IS_RESULT` is set to `true`. +/// - **B:** The chain extension method returns a type `O` that is not a `Result` type. +/// The method just returns `O`. #[derive(Debug)] -pub struct ChainExtensionMethod { +pub struct ChainExtensionMethod { func_id: u32, #[allow(clippy::type_complexity)] state: PhantomData (I, O, ErrorCode)>, } -impl ChainExtensionMethod<(), (), ()> { +impl ChainExtensionMethod<(), (), (), false> { /// Creates a new chain extension method instance. #[inline] pub fn build(func_id: u32) -> Self { @@ -92,7 +91,9 @@ impl ChainExtensionMethod<(), (), ()> { } } -impl ChainExtensionMethod<(), O, ErrorCode> { +impl + ChainExtensionMethod<(), O, ErrorCode, IS_RESULT> +{ /// Sets the input types of the chain extension method call to `I`. /// /// # Note @@ -101,7 +102,7 @@ impl ChainExtensionMethod<(), O, ErrorCode> { /// All tuple types that may act as input parameters for the chain extension method are valid. /// Examples include `()`, `i32`, `(u8, [u8; 5], i32)`, etc. #[inline] - pub fn input(self) -> ChainExtensionMethod + pub fn input(self) -> ChainExtensionMethod where I: scale::Encode, { @@ -112,34 +113,20 @@ impl ChainExtensionMethod<(), O, ErrorCode> { } } -impl ChainExtensionMethod { - /// Sets the output type of the chain extension method call to `Result`. +impl ChainExtensionMethod { + /// Sets the output type, `O`, of the chain extension method call. /// - /// # Note - /// - /// This indicates that the chain extension method return value might represent a failure. - #[inline] - pub fn output_result(self) -> ChainExtensionMethod, ErrorCode> - where - Result: scale::Decode, - E: From, - { - ChainExtensionMethod { - func_id: self.func_id, - state: Default::default(), - } - } - - /// Sets the output type of the chain extension method call to `O`. + /// If `const IS_RESULT: bool` is set to `true`, + /// `O` is treated as `Result` /// /// # Note /// - /// The set returned type `O` must not be of type `Result`. - /// When using the `#[ink::chain_extension]` procedural macro to define - /// this chain extension method the above constraint is enforced at - /// compile time. + /// If `O` is incorrectly indicated as `Return`, + /// the type will not satisfy trait bounds later in method builder pipeline. #[inline] - pub fn output(self) -> ChainExtensionMethod, ErrorCode> + pub fn output( + self, + ) -> ChainExtensionMethod where O: scale::Decode, { @@ -150,7 +137,7 @@ impl ChainExtensionMethod { } } -impl ChainExtensionMethod { +impl ChainExtensionMethod { /// Makes the chain extension method call assume that the returned status code is always success. /// /// # Note @@ -161,7 +148,9 @@ impl ChainExtensionMethod { /// /// The output of the chain extension method call is always decoded and returned in this case. #[inline] - pub fn ignore_error_code(self) -> ChainExtensionMethod { + pub fn ignore_error_code( + self, + ) -> ChainExtensionMethod { ChainExtensionMethod { func_id: self.func_id, state: Default::default(), @@ -177,7 +166,7 @@ impl ChainExtensionMethod { #[inline] pub fn handle_error_code( self, - ) -> ChainExtensionMethod> + ) -> ChainExtensionMethod, IS_RESULT> where ErrorCode: FromStatusCode, { @@ -201,22 +190,14 @@ pub mod state { pub struct HandleErrorCode { error_code: PhantomData T>, } - - /// Type state meaning that the chain extension method deliberately does not return a `Result` type. - /// - /// Additionally this is enforced by the `#[ink::chain_extension]` procedural macro when used. - #[derive(Debug)] - pub struct NoResult { - no_result: PhantomData T>, - } } -impl - ChainExtensionMethod, state::HandleErrorCode> +impl ChainExtensionMethod, true> where + O: IsResultType, I: scale::Encode, - T: scale::Decode, - E: scale::Decode + From + From, + ::Ok: scale::Decode, + ::Err: scale::Decode + From + From, ErrorCode: FromStatusCode, { /// Calls the chain extension method for case 1.A described [here]. @@ -235,6 +216,7 @@ where /// /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32` /// as input parameters and returns a `Result` upon completion. + /// Note how we set const constant argument to `true` to indicate that return type is `Result`. /// It will handle the shared error code from the chain extension. /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input /// parameter respectively. @@ -245,7 +227,7 @@ where /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode}; /// let result = ChainExtensionMethod::build(5) /// .input::<(bool, i32)>() - /// .output_result::() + /// .output::, true>() /// .handle_error_code::() /// .call(&(true, 42)); /// # #[derive(scale::Encode, scale::Decode)] @@ -262,9 +244,19 @@ where /// # } /// ``` #[inline] - pub fn call(self, input: &I) -> Result { + pub fn call( + self, + input: &I, + ) -> Result<::Ok, ::Err> { ::on_instance(|instance| { - EnvBackend::call_chain_extension::( + EnvBackend::call_chain_extension::< + I, + ::Ok, + ::Err, + ErrorCode, + _, + _, + >( instance, self.func_id, input, @@ -275,11 +267,12 @@ where } } -impl ChainExtensionMethod, state::IgnoreErrorCode> +impl ChainExtensionMethod where + O: IsResultType, I: scale::Encode, - T: scale::Decode, - E: scale::Decode + From, + ::Ok: scale::Decode, + ::Err: scale::Decode + From, { /// Calls the chain extension method for case 2.A described [here]. /// @@ -296,6 +289,7 @@ where /// /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32` /// as input parameters and returns a `Result` upon completion. + /// Note how we set const constant argument to `true` to indicate that return type is `Result`. /// It will ignore the shared error code from the chain extension and assumes that the call succeeds. /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input /// parameter respectively. @@ -306,7 +300,7 @@ where /// # use ink_env::chain_extension::{ChainExtensionMethod}; /// let result = ChainExtensionMethod::build(5) /// .input::<(bool, i32)>() - /// .output_result::() + /// .output::, true>() /// .ignore_error_code() /// .call(&(true, 42)); /// # #[derive(scale::Encode, scale::Decode)] @@ -316,9 +310,19 @@ where /// # } /// ``` #[inline] - pub fn call(self, input: &I) -> Result { + pub fn call( + self, + input: &I, + ) -> Result<::Ok, ::Err> { ::on_instance(|instance| { - EnvBackend::call_chain_extension::( + EnvBackend::call_chain_extension::< + I, + ::Ok, + ::Err, + ::Err, + _, + _, + >( instance, self.func_id, input, @@ -329,8 +333,7 @@ where } } -impl - ChainExtensionMethod, state::HandleErrorCode> +impl ChainExtensionMethod, false> where I: scale::Encode, O: scale::Decode, @@ -353,7 +356,10 @@ where /// # Example /// /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32` - /// as input parameters and returns a `Result` upon completion. + /// as input parameters and returns a `Result` upon completion, + /// because `handle_status` flag is set. + /// We still need to indicate that the original type is not `Result`, so + /// `const IS_RESULT` is set `false`. /// It will handle the shared error code from the chain extension. /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input /// parameter respectively. @@ -364,7 +370,7 @@ where /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode}; /// let result = ChainExtensionMethod::build(5) /// .input::<(bool, i32)>() - /// .output::() + /// .output::() /// .handle_error_code::() /// .call(&(true, 42)); /// # pub struct MyErrorCode {} @@ -390,7 +396,7 @@ where } } -impl ChainExtensionMethod, state::IgnoreErrorCode> +impl ChainExtensionMethod where I: scale::Encode, O: scale::Decode, @@ -406,7 +412,7 @@ where /// # Example /// /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32` - /// as input parameters and returns a `Result` upon completion. + /// as input parameters and returns a `i32` upon completion. Hence, `const IS_RESULT` is set `false`. /// It will ignore the shared error code from the chain extension and assumes that the call succeeds. /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input /// parameter respectively. @@ -417,7 +423,7 @@ where /// # use ink_env::chain_extension::ChainExtensionMethod; /// let result = ChainExtensionMethod::build(5) /// .input::<(bool, i32)>() - /// .output::() + /// .output::() /// .ignore_error_code() /// .call(&(true, 42)); /// ``` @@ -438,3 +444,22 @@ where }) } } + +/// Extract `Ok` and `Err` variants from `Result` type. +pub trait IsResultType: private::IsResultTypeSealed { + /// The `T` type of the `Result`. + type Ok; + /// The `E` type of the `Result`. + type Err; +} + +impl private::IsResultTypeSealed for Result {} +impl IsResultType for Result { + type Ok = T; + type Err = E; +} + +mod private { + /// Seals the `IsResultType` trait so that it cannot be implemented outside this module. + pub trait IsResultTypeSealed {} +} diff --git a/crates/ink/codegen/src/generator/chain_extension.rs b/crates/ink/codegen/src/generator/chain_extension.rs index 2a4dfe3e26..8f36e838ee 100644 --- a/crates/ink/codegen/src/generator/chain_extension.rs +++ b/crates/ink/codegen/src/generator/chain_extension.rs @@ -73,67 +73,49 @@ impl ChainExtension<'_> { }; let handle_status = method.handle_status(); - let returns_result = method.returns_result(); - let error_code_handling = if handle_status { + let handle_status_token = if handle_status { quote_spanned!(span=> - .handle_error_code::<#error_code>() + true ) } else { quote_spanned!(span=> - .ignore_error_code() + false ) }; - let result_handling = if returns_result { + let error_code_handling = if handle_status { quote_spanned!(span=> - .output_result::< - <#output_type as ::ink::IsResultType>::Ok, - <#output_type as ::ink::IsResultType>::Err, - >() + .handle_error_code::<#error_code>() ) } else { quote_spanned!(span=> - .output::<#output_type>() + .ignore_error_code() ) }; - let returned_type = match (returns_result, handle_status) { - (false, true) => { - quote_spanned!(span=> - ::core::result::Result<#output_type, #error_code> - ) - } - _ => { - quote_spanned!(span=> - #output_type - ) - } - }; - - let where_output_is_result = Some(quote_spanned!(span=> - #output_type: ::ink::IsResultType, - )) - .filter(|_| returns_result); + let return_type = quote_spanned!(span => + <::ink::ValueReturned as ::ink::Output<{ ::ink::is_result_type!(#output_type) }, #handle_status_token, #output_type, #error_code>>::ReturnType + ); + // we only need to check if handle status is set to true to enable this type bound let where_output_impls_from_error_code = Some(quote_spanned!(span=> - <#output_type as ::ink::IsResultType>::Err: ::core::convert::From<#error_code>, - )).filter(|_| returns_result && handle_status); + <#return_type as ::ink::IsResultType>::Err: ::core::convert::From<#error_code>, + )).filter(|_| handle_status); quote_spanned!(span=> - #( #attrs )* - #[inline] - pub fn #ident(self, #inputs) -> #returned_type - where - #where_output_is_result - #where_output_impls_from_error_code - { - ::ink::env::chain_extension::ChainExtensionMethod::build(#func_id) + #( #attrs )* + #[inline] + pub fn #ident(self, #inputs) -> #return_type + where + #where_output_impls_from_error_code + { + ::ink::env::chain_extension::ChainExtensionMethod::build(#func_id) .input::<#compound_input_type>() - #result_handling + .output::<#output_type, {::ink::is_result_type!(#output_type)}>() #error_code_handling .call(&#compound_input_bindings) - } + } ) } } diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 707467e617..18c6815fdd 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -303,15 +303,6 @@ impl InkAttribute { .args() .any(|arg| matches!(arg.kind(), AttributeArg::HandleStatus(false))) } - - /// Returns `false` if the ink! attribute contains the `returns_result = false` argument. - /// - /// Otherwise returns `true`. - pub fn is_returns_result(&self) -> bool { - !self - .args() - .any(|arg| matches!(arg.kind(), AttributeArg::ReturnsResult(false))) - } } /// An ink! specific attribute argument. @@ -362,8 +353,6 @@ pub enum AttributeArgKind { Implementation, /// `#[ink(handle_status = flag: bool)]` HandleStatus, - /// `#[ink(returns_result = flag: bool)]` - ReturnsResult, } /// An ink! specific attribute flag. @@ -444,12 +433,6 @@ pub enum AttributeArg { /// /// Default value: `true` HandleStatus(bool), - /// `#[ink(returns_result = flag: bool)]` - /// - /// Used by the `#[ink::chain_extension]` procedural macro. - /// - /// Default value: `true` - ReturnsResult(bool), } impl core::fmt::Display for AttributeArgKind { @@ -473,7 +456,6 @@ impl core::fmt::Display for AttributeArgKind { } Self::Implementation => write!(f, "impl"), Self::HandleStatus => write!(f, "handle_status"), - Self::ReturnsResult => write!(f, "returns_result"), } } } @@ -494,7 +476,6 @@ impl AttributeArg { Self::Namespace(_) => AttributeArgKind::Namespace, Self::Implementation => AttributeArgKind::Implementation, Self::HandleStatus(_) => AttributeArgKind::HandleStatus, - Self::ReturnsResult(_) => AttributeArgKind::ReturnsResult, } } } @@ -518,7 +499,6 @@ impl core::fmt::Display for AttributeArg { } Self::Implementation => write!(f, "impl"), Self::HandleStatus(value) => write!(f, "handle_status = {:?}", value), - Self::ReturnsResult(value) => write!(f, "returns_result = {:?}", value), } } } @@ -978,16 +958,6 @@ impl TryFrom for AttributeFrag { } return Err(format_err!(name_value, "expected `bool` value type for `flag` in #[ink(handle_status = flag)]")) } - if name_value.path.is_ident("returns_result") { - if let syn::Lit::Bool(lit_bool) = &name_value.lit { - let value = lit_bool.value; - return Ok(AttributeFrag { - ast: meta, - arg: AttributeArg::ReturnsResult(value), - }) - } - return Err(format_err!(name_value, "expected `bool` value type for `flag` in #[ink(returns_result = flag)]")) - } Err(format_err_spanned!( meta, "unknown ink! attribute argument (name = value)", @@ -1027,11 +997,6 @@ impl TryFrom for AttributeFrag { "encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \ Did you mean #[ink(handle_status = flag: bool)] ?" )), - "returns_result" => Err(format_err!( - meta, - "encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \ - Did you mean #[ink(returns_result = flag: bool)] ?" - )), _ => Err(format_err_spanned!( meta, "unknown ink! attribute (path)" )) @@ -1411,50 +1376,6 @@ mod tests { ); } - #[test] - fn returns_result_works() { - fn expected_ok(value: bool) -> Result { - Ok(test::Attribute::Ink(vec![AttributeArg::ReturnsResult( - value, - )])) - } - assert_attribute_try_from( - syn::parse_quote! { - #[ink(returns_result = true)] - }, - expected_ok(true), - ); - assert_attribute_try_from( - syn::parse_quote! { - #[ink(returns_result = false)] - }, - expected_ok(false), - ); - } - - #[test] - fn returns_result_missing_parameter() { - assert_attribute_try_from( - syn::parse_quote! { - #[ink(returns_result)] - }, - Err( - "encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \ - Did you mean #[ink(returns_result = flag: bool)] ?", - ), - ); - } - - #[test] - fn returns_result_invalid_parameter_type() { - assert_attribute_try_from( - syn::parse_quote! { - #[ink(returns_result = "string")] - }, - Err("expected `bool` value type for `flag` in #[ink(returns_result = flag)]"), - ); - } - #[test] fn compound_mixed_works() { assert_attribute_try_from( diff --git a/crates/ink/ir/src/ir/chain_extension.rs b/crates/ink/ir/src/ir/chain_extension.rs index fe5fa9f0d6..01766058cb 100644 --- a/crates/ink/ir/src/ir/chain_extension.rs +++ b/crates/ink/ir/src/ir/chain_extension.rs @@ -88,13 +88,6 @@ pub struct ChainExtensionMethod { /// /// The default for this flag is `true`. handle_status: bool, - /// If `false` the procedural macro no longer tries to enforce that the returned type encoded - /// into the output buffer of the chain extension method call is of type `Result`. - /// Also `E` is no longer required to implement `From` in case `handle_status` - /// flag does not exist. - /// - /// The default for this flag is `true`. - returns_result: bool, } impl ChainExtensionMethod { @@ -138,11 +131,6 @@ impl ChainExtensionMethod { pub fn handle_status(&self) -> bool { self.handle_status } - - /// Returns `true` if the chain extension method was flagged with `#[ink(returns_result)]`. - pub fn returns_result(&self) -> bool { - self.returns_result - } } pub struct ChainExtensionMethodInputs<'a> { @@ -278,7 +266,7 @@ impl ChainExtension { item_type.ident, "chain extensions expect an associated type with name `ErrorCode` but found {}", item_type.ident, - )) + )); } if !item_type.generics.params.is_empty() { return Err(format_err_spanned!( @@ -412,7 +400,7 @@ impl ChainExtension { return Err(format_err_spanned!( default_impl, "ink! chain extension methods with default implementations are not supported" - )) + )); } if let Some(constness) = &method.sig.constness { return Err(format_err_spanned!( @@ -486,8 +474,7 @@ impl ChainExtension { |arg| { match arg.kind() { ir::AttributeArg::Extension(_) - | ir::AttributeArg::HandleStatus(_) - | ir::AttributeArg::ReturnsResult(_) => Ok(()), + | ir::AttributeArg::HandleStatus(_) => Ok(()), _ => Err(None), } }, @@ -502,7 +489,6 @@ impl ChainExtension { id: extension, item: item_method.clone(), handle_status: ink_attrs.is_handle_status(), - returns_result: ink_attrs.is_returns_result(), }; Ok(result) } @@ -932,20 +918,17 @@ mod tests { #[ink(extension = 1, handle_status = false)] fn extension_a(); - #[ink(extension = 2, returns_result = false)] + #[ink(extension = 2)] fn extension_b(); - #[ink(extension = 3, handle_status = false, returns_result = false)] + #[ink(extension = 3, handle_status = false)] fn extension_c(); - #[ink(extension = 4)] #[ink(handle_status = false)] fn extension_d(); #[ink(extension = 5)] - #[ink(returns_result = false)] fn extension_e(); #[ink(extension = 6)] #[ink(handle_status = false)] - #[ink(returns_result = false)] fn extension_f(); } }) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 27b657e56d..1fd44af662 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -863,7 +863,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// Chain extension methods must not have a `self` receiver such as `&self` or `&mut self` /// and must have inputs and output that implement the SCALE encoding and decoding. /// Their return value follows specific rules that can be altered using the `handle_status` -/// and `returns_result` attributes which are described in more detail below. +/// attribute which is described in more detail below. /// /// # Usage /// @@ -882,7 +882,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// |:----------|:--------:|:--------------|:-----------:| /// | `ink(extension = N: u32)` | Yes | - | Determines the unique function ID of the chain extension method. | /// | `ink(handle_status = flag: bool)` | Optional | `true` | Assumes that the returned status code of the chain extension method always indicates success and therefore always loads and decodes the output buffer of the call. | -/// | `ink(returns_result = flag: bool)` | Optional | `true` | By default chain extension methods are assumed to return a `Result` in the output buffer. Using `returns_result = false` this check is disabled and the chain extension method may return any other type. | /// /// As with all ink! attributes multiple of them can either appear in a contiguous list: /// ``` @@ -890,7 +889,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// # #[ink::chain_extension] /// # pub trait MyChainExtension { /// # type ErrorCode = i32; -/// #[ink(extension = 5, handle_status = false, returns_result = false)] +/// #[ink(extension = 5, handle_status = false)] /// fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; /// # } /// ``` @@ -902,7 +901,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// # type ErrorCode = i32; /// #[ink(extension = 5)] /// #[ink(handle_status = false)] -/// #[ink(returns_result = false)] /// fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; /// # } /// ``` @@ -911,7 +909,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// Default value: `true` /// -/// By default all chain extension methods return a `Result` where `E: From`. +/// By default all chain extension methods should return a `Result` where `E: From`. /// The `Self::ErrorCode` represents the error code of the chain extension. /// This means that a smart contract calling such a chain extension method first queries the returned /// status code of the chain extension method and only loads and decodes the output if the returned @@ -925,28 +923,20 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// will always indicate success. Therefore it will always load and decode the output buffer and loses /// the `E: From` constraint for the call. /// -/// ## Details: `returns_result` +/// Note that if a chain extension method does not return `Result` where `E: From` +/// but `handle_status = true` it will still return a value of type `Result`. /// -/// Default value: `true` -/// -/// By default chain extension methods are assumed to return a value of type `Result` through the -/// output buffer. Using `returns_result = false` this check is disabled and the chain extension method may return -/// any other type. -/// -/// Note that if a chain extension method is attributed with `returns_result = false` -/// and with `handle_status = true` it will still return a value of type `Result`. -/// -/// ## Usage: `handle_status` and `returns_result` +/// ## Usage: `handle_status` /// -/// Use both `handle_status = false` and `returns_result = false` for the same chain extension method +/// Use both `handle_status = false` and non-`Result` return type for the same chain extension method /// if a call to it may never fail and never returns a `Result` type. /// /// # Combinations /// -/// Due to the possibility to flag a chain extension method with `handle_status` and `returns_result` +/// Due to the possibility to flag a chain extension method with `handle_status` and return or not `Result` /// there are 4 different cases with slightly varying semantics: /// -/// | `handle_status` | `returns_result` | Effects | +/// | `handle_status` | Returns `Result` | Effects | /// |:---------------:|:----------------:|:--------| /// | `true` | `true` | The chain extension method is required to return a value of type `Result` where `E: From`. A call will always check if the returned status code indicates success and only then will load and decode the value in the output buffer. | /// | `true` | `false` | The chain extension method may return any non-`Result` type. A call will always check if the returned status code indicates success and only then will load and decode the value in the output buffer. The actual return type of the chain extension method is still `Result` when the chain extension method was defined to return a value of type `T`. | @@ -989,7 +979,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Note /// /// /// /// Actually returns a value of type `Result, Self::ErrorCode>`. -/// #[ink(extension = 1, returns_result = false)] +/// #[ink(extension = 1)] /// fn read(key: &[u8]) -> Vec; /// /// /// Reads from runtime storage. @@ -1014,7 +1004,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Note /// /// /// /// Actually returns a value of type `Result<(), Self::ErrorCode>`. -/// #[ink(extension = 3, returns_result = false)] +/// #[ink(extension = 3)] /// fn write(key: &[u8], value: &[u8]); /// /// /// Returns the access allowed for the key for the caller. @@ -1022,7 +1012,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Note /// /// /// /// Assumes to never fail the call and therefore always returns `Option`. -/// #[ink(extension = 4, returns_result = false, handle_status = false)] +/// #[ink(extension = 4, handle_status = false)] /// fn access(key: &[u8]) -> Option; /// /// /// Unlocks previously aquired permission to access key. @@ -1187,13 +1177,13 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// # #[ink::chain_extension] /// # pub trait RuntimeReadWrite { /// # type ErrorCode = ReadWriteErrorCode; -/// # #[ink(extension = 1, returns_result = false)] +/// # #[ink(extension = 1)] /// # fn read(key: &[u8]) -> Vec; /// # #[ink(extension = 2)] /// # fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; -/// # #[ink(extension = 3, returns_result = false)] +/// # #[ink(extension = 3)] /// # fn write(key: &[u8], value: &[u8]); -/// # #[ink(extension = 4, returns_result = false, handle_status = false)] +/// # #[ink(extension = 4, handle_status = false)] /// # fn access(key: &[u8]) -> Option; /// # #[ink(extension = 5, handle_status = false)] /// # fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>; diff --git a/crates/ink/src/chain_extension.rs b/crates/ink/src/chain_extension.rs index 2ad6c7b1d1..ef0b126be6 100644 --- a/crates/ink/src/chain_extension.rs +++ b/crates/ink/src/chain_extension.rs @@ -41,20 +41,56 @@ pub trait ChainExtension { /// Used to check at compile time if the chain extension method return type /// is a `Result` type using the type system instead of the syntactic structure. #[doc(hidden)] -pub trait IsResultType: private::Sealed { +pub trait IsResultType: private::IsResultSealed { /// The `T` type of the `Result`. type Ok; /// The `E` type of the `Result`. type Err; } -impl private::Sealed for Result {} +impl private::IsResultSealed for Result {} impl IsResultType for Result { type Ok = T; type Err = E; } +/// Only implemented for [`ValueReturned`]. +/// +/// Used to deduce the correct return type of a chain extension method at compile time +/// based on 2 flags: `const IS_RESULT: bool` and `const HANDLE_STATUS: bool`. +/// +/// If `IS_RESULT` is set to `false` and `HANDLE_STATUS` is `true`, then +/// `type ReturnType = Result`. Otherwise `type ReturnType = T`. +pub trait Output: + private::OutputSealed +{ + type ReturnType; +} + +/// Represents some abstract value that is returned by a function. +/// Currently acts as a placeholder. +pub struct ValueReturned; +impl private::OutputSealed for ValueReturned {} + +impl Output for ValueReturned { + type ReturnType = T; +} + +impl Output for ValueReturned { + type ReturnType = core::result::Result; +} + +impl Output for ValueReturned { + type ReturnType = T; +} + +impl Output for ValueReturned { + type ReturnType = T; +} + mod private { /// Seals the `IsResultType` trait so that it cannot be implemented outside this module. - pub trait Sealed {} + pub trait IsResultSealed {} + /// Seals the `Output` trait so that it cannot be implemented outside this module. + pub trait OutputSealed {} } diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 1422416741..ca0b8d4b69 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -57,6 +57,8 @@ pub use self::{ chain_extension::{ ChainExtensionInstance, IsResultType, + Output, + ValueReturned, }, contract_ref::ToAccountId, env_access::EnvAccess, diff --git a/crates/ink/tests/ui/chain_extension/E-01-simple.rs b/crates/ink/tests/ui/chain_extension/E-01-simple.rs index c32a3e9335..c47a17150a 100644 --- a/crates/ink/tests/ui/chain_extension/E-01-simple.rs +++ b/crates/ink/tests/ui/chain_extension/E-01-simple.rs @@ -6,7 +6,7 @@ pub trait RuntimeReadWrite { type ErrorCode = ReadWriteErrorCode; /// Reads from runtime storage. - #[ink(extension = 1, returns_result = false)] + #[ink(extension = 1)] fn read(key: &[u8]) -> Vec; /// Reads from runtime storage. @@ -22,11 +22,11 @@ pub trait RuntimeReadWrite { fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; /// Writes into runtime storage. - #[ink(extension = 3, returns_result = false)] + #[ink(extension = 3)] fn write(key: &[u8], value: &[u8]); /// Returns the access allowed for the key for the caller. - #[ink(extension = 4, returns_result = false, handle_status = false)] + #[ink(extension = 4, handle_status = false)] fn access(key: &[u8]) -> Option; /// Unlocks previously aquired permission to access key. diff --git a/examples/rand-extension/lib.rs b/examples/rand-extension/lib.rs index 8585f4cf30..55b15d1235 100755 --- a/examples/rand-extension/lib.rs +++ b/examples/rand-extension/lib.rs @@ -13,7 +13,7 @@ pub trait FetchRandom { /// Note: this gives the operation a corresponding `func_id` (1101 in this case), /// and the chain-side chain extension will get the `func_id` to do further operations. - #[ink(extension = 1101, returns_result = false)] + #[ink(extension = 1101)] fn fetch_random(subject: [u8; 32]) -> [u8; 32]; }