From 83d5b4370fab3452e4b92e3d99994ae4a988c30e Mon Sep 17 00:00:00 2001 From: Felix <106395723+ilovehackathons@users.noreply.github.com> Date: Fri, 30 Dec 2022 00:01:54 +0100 Subject: [PATCH 01/18] README fixes (#1570) * Remove 'the' to correct the grammar Have to choose between 'the' and 'our'. * Remove the word 'file' to correct the grammar It was repeated twice. * Add '+nightly' to the build command Otherwise it fails with 'ERROR: cargo-contract cannot build using the "stable" channel.'. * Make the word 'examples' into a link This is especially useful when viewing on https://paritytech.github.io/ink/. * Add '+nightly' to another build command They all need it. * Add another missing '+nightly' Let's keep it consistent. * Use fields (plural) instead of field (singular) This is grammatically-correct. * Convert the Riot link into an Element link Riot has been renamed to Element. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cf04ca722d7..fd0d2e66d4a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ [j1]: https://img.shields.io/badge/click-blue.svg [j2]: https://paritytech.github.io/ink/ink [k1]: https://img.shields.io/badge/matrix-chat-brightgreen.svg?style=flat -[k2]: https://riot.im/app/#/room/#ink:matrix.parity.io +[k2]: https://app.element.io/#/room/#ink:matrix.parity.io [l1]: https://img.shields.io/discord/722223075629727774?style=flat-square&label=discord [l2]: https://discord.com/invite/wGUDt2p [s1]: https://img.shields.io/badge/click-white.svg?logo=StackExchange&label=ink!%20Support%20on%20StackExchange&labelColor=white&color=blue @@ -74,7 +74,7 @@ It's a simple Substrate blockchain which includes the Substrate module for smart We also have a live testnet on [Rococo](https://github.com/paritytech/cumulus/#rococo-). Rococo is a Substrate based parachain which supports ink! smart contracts. For further instructions on using this -testnet, follow the instructions in the +testnet, follow the instructions in [our documentation](https://use.ink/testnet). For both types of chains the [Contracts UI](https://contracts-ui.substrate.io/) @@ -109,7 +109,7 @@ In order to build the contract just execute this command in the `flipper` folder cargo +nightly contract build ``` -As a result you'll get a file `target/flipper.wasm` file, a `metadata.json` file and a `.contract` file in the `target` folder of your contract. +As a result you'll get a `target/flipper.wasm` file, a `metadata.json` file and a `.contract` file in the `target` folder of your contract. The `.contract` file combines the Wasm and metadata into one file and needs to be used when instantiating the contract. @@ -175,12 +175,12 @@ mod flipper { ``` The [`flipper/src/lib.rs`](https://github.com/paritytech/ink/blob/master/examples/flipper/lib.rs) -file in our examples folder contains exactly this code. Run `cargo contract build` to build your +file in our examples folder contains exactly this code. Run `cargo +nightly contract build` to build your first ink! smart contract. ## Examples -In the `examples` folder you'll find a number of examples written in ink!. +In the [`examples`](https://github.com/paritytech/ink/tree/master/examples) folder you'll find a number of examples written in ink!. Some of the most interesting ones: @@ -192,7 +192,7 @@ Some of the most interesting ones: To build a single example navigate to the root of the example and run: ``` -cargo contract build +cargo contract +nightly build ``` You should now have an `.contract` file in the `target` folder of the contract. @@ -208,7 +208,7 @@ This module is called the `contracts` pallet, * The `contracts` pallet requires smart contracts to be uploaded to the blockchain as a Wasm blob. * ink! is a smart contract language which targets the API exposed by `contracts`. Hence ink! contracts are compiled to Wasm. -* When executing `cargo contract build` an additional file `metadata.json` is created. +* When executing `cargo contract +nightly build` an additional file `metadata.json` is created. It contains information about e.g. what methods the contract provides for others to call. ## ink! Macros & Attributes Overview @@ -224,7 +224,7 @@ In a module annotated with `#[ink::contract]` these attributes are available: | `#[ink(constructor)]` | Applicable to method. | Flags a method for the ink! storage struct as constructor making it available to the API for instantiating the contract. | | `#[ink(event)]` | On `struct` definitions. | Defines an ink! event. A contract can define multiple such ink! events. | | `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. | -| `#[ink(topic)]` | Applicable on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic field. Similar semantics as to indexed event arguments in Solidity. | +| `#[ink(topic)]` | Applicable on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic fields. Similar semantics as to indexed event arguments in Solidity. | | `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. | | `#[ink(selector = S:u32)]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. | | `#[ink(selector = _)]` | Applicable to ink! messages. | Specifies a fallback message that is invoked if no other ink! message matches a selector. | From dda44c6d09538a2d378e76fcc2931efdd1ddcd79 Mon Sep 17 00:00:00 2001 From: Green Baneling Date: Tue, 3 Jan 2023 12:39:14 +0000 Subject: [PATCH 02/18] Added missed `WhereClosure` for the generics. Tested this case via documentation (#1536) --- crates/ink/ir/src/ir/storage_item/mod.rs | 9 +++++++-- crates/ink/macro/src/lib.rs | 14 ++++++++++++-- examples/erc1155/lib.rs | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/ink/ir/src/ir/storage_item/mod.rs b/crates/ink/ir/src/ir/storage_item/mod.rs index bcf18dfde91..361a59acb04 100644 --- a/crates/ink/ir/src/ir/storage_item/mod.rs +++ b/crates/ink/ir/src/ir/storage_item/mod.rs @@ -113,8 +113,13 @@ impl StorageItem { } /// Returns the generics of the storage. - pub fn generics(&self) -> &syn::Generics { - &self.ast.generics + pub fn generics(&self) -> TokenStream2 { + let types = self.ast.generics.clone(); + // `where_closure` is not included into `types`, so add it manually. + let (_, _, where_closure) = self.ast.generics.split_for_impl(); + quote! { + #types #where_closure + } } /// Returns data of the storage. diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 04b7deed98c..27b657e56d7 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -697,6 +697,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// feature = "std", /// derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) /// )] +/// #[derive(Default, Debug)] /// struct Packed { /// s1: u128, /// s2: Vec, @@ -710,6 +711,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// feature = "std", /// derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) /// )] +/// #[derive(Default, Debug)] /// struct PackedGeneric { /// s1: (u128, bool), /// s2: Vec, @@ -718,6 +720,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// // Example of how to define the non-packed type. /// #[ink::storage_item] +/// #[derive(Default, Debug)] /// struct NonPacked { /// s1: Mapping, /// s2: Lazy, @@ -730,7 +733,12 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// feature = "std", /// derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) /// )] -/// struct NonPackedGeneric { +/// #[derive(Default, Debug)] +/// struct NonPackedGeneric +/// where +/// T: Default + core::fmt::Debug, +/// T: ink::storage::traits::Packed, +/// { /// s1: u32, /// s2: T, /// s3: Mapping, @@ -742,6 +750,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// feature = "std", /// derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) /// )] +/// #[derive(Default, Debug)] /// struct PackedComplex { /// s1: u128, /// s2: Vec, @@ -750,6 +759,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// // Example of how to define a complex non-packed type. /// #[ink::storage_item] +/// #[derive(Default, Debug)] /// struct NonPackedComplex { /// s1: (String, u128, Packed), /// s2: Mapping, @@ -779,8 +789,8 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// use ink::storage::traits::{ /// StorableHint, /// StorageKey, +/// Storable, /// }; -/// use ink::storage::traits::Storable; /// /// #[ink::storage_item(derive = false)] /// #[derive(StorableHint, Storable, StorageKey)] diff --git a/examples/erc1155/lib.rs b/examples/erc1155/lib.rs index 8dd12c800d6..9140fa21f80 100644 --- a/examples/erc1155/lib.rs +++ b/examples/erc1155/lib.rs @@ -331,7 +331,7 @@ mod erc1155 { self.env().emit_event(TransferSingle { operator: Some(caller), from: Some(from), - to: Some(from), + to: Some(to), token_id, value, }); From 8c5ee37a54ddca54afc30d60e3cecaa1fd97582d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 4 Jan 2023 14:36:42 +0000 Subject: [PATCH 03/18] Migrate `flipper` waterfall test to e2e (#1573) * Add basic flip test * Add default constructor test * Rename e2e tests to match unit tests --- examples/flipper/Cargo.toml | 4 +++ examples/flipper/lib.rs | 70 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/examples/flipper/Cargo.toml b/examples/flipper/Cargo.toml index 61fc563bab7..466e72e5519 100644 --- a/examples/flipper/Cargo.toml +++ b/examples/flipper/Cargo.toml @@ -11,6 +11,9 @@ ink = { path = "../../crates/ink", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + [lib] name = "flipper" path = "lib.rs" @@ -24,3 +27,4 @@ std = [ "scale-info/std", ] ink-as-dependency = [] +e2e-tests = [] diff --git a/examples/flipper/lib.rs b/examples/flipper/lib.rs index 14f4f39b042..d4d0c82b2ac 100644 --- a/examples/flipper/lib.rs +++ b/examples/flipper/lib.rs @@ -51,4 +51,74 @@ pub mod flipper { assert!(flipper.get()); } } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::build_message; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn it_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // given + let constructor = FlipperRef::new(false); + let contract_acc_id = client + .instantiate("flipper", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let get = build_message::(contract_acc_id.clone()) + .call(|flipper| flipper.get()); + let get_res = client + .call(&ink_e2e::bob(), get, 0, None) + .await + .expect("get failed"); + assert!(matches!(get_res.return_value(), Ok(false))); + + // when + let flip = build_message::(contract_acc_id.clone()) + .call(|flipper| flipper.flip()); + let _flip_res = client + .call(&ink_e2e::bob(), flip, 0, None) + .await + .expect("flip failed"); + + // then + let get = build_message::(contract_acc_id.clone()) + .call(|flipper| flipper.get()); + let get_res = client + .call(&ink_e2e::bob(), get, 0, None) + .await + .expect("get failed"); + assert!(matches!(get_res.return_value(), Ok(true))); + + Ok(()) + } + + #[ink_e2e::test] + async fn default_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // given + let constructor = FlipperRef::new_default(); + + // when + let contract_acc_id = client + .instantiate("flipper", &ink_e2e::bob(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + // then + let get = build_message::(contract_acc_id.clone()) + .call(|flipper| flipper.get()); + let get_res = client + .call(&ink_e2e::bob(), get, 0, None) + .await + .expect("get failed"); + assert!(matches!(get_res.return_value(), Ok(false))); + + Ok(()) + } + } } From cc76f0f60e25a522c369095c18c11b84d8d046a6 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Tue, 10 Jan 2023 14:33:36 +0530 Subject: [PATCH 04/18] added Debug trait for EmittedEvent (#1583) --- crates/engine/src/test_api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/src/test_api.rs b/crates/engine/src/test_api.rs index 2d6703d7b20..4ee8d67745d 100644 --- a/crates/engine/src/test_api.rs +++ b/crates/engine/src/test_api.rs @@ -24,7 +24,7 @@ use crate::{ use std::collections::HashMap; /// Record for an emitted event. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EmittedEvent { /// Recorded topics of the emitted event. pub topics: Vec>, From 76b8c91848f185b1115eaba1e4bb38a69caee626 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 11 Jan 2023 13:15:45 +0000 Subject: [PATCH 05/18] dependabot: fix ignoring substrate deps (#1588) * dependabot: explicitly ignore substrate deps * Indentation * Revert to wildcards, move to cargo package * Comments * Fix update-types * Formatting --- .github/dependabot.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0c4838356b6..018a56649b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,14 +4,14 @@ updates: directory: "/" schedule: interval: "daily" + # ignore Substrate pallets major updates. + # automated Substrate releases cause dependabot PR spam, so these must be updated manually when required. + ignore: + - dependency-name: "sp-*" + update-types: [ "version-update:semver-major" ] + - dependency-name: "pallet-*" + update-types: [ "version-update:semver-major" ] - package-ecosystem: github-actions directory: '/' schedule: interval: daily - - ignore: - - dependency-name: sp-* - versions: - - ">= 0" - - dependency-name: pallet-* - versions: - - ">= 0" From a4443c191184f5ad3210f35aaad7ff3bb3a24bd5 Mon Sep 17 00:00:00 2001 From: yjh Date: Thu, 12 Jan 2023 00:39:55 +0800 Subject: [PATCH 06/18] chore: make more functions be const (#1574) * feat: make more functions be const * fix * fix * fix examples * fmt --- crates/e2e/src/builders.rs | 2 +- crates/env/src/call/call_builder.rs | 12 ++++++------ crates/env/src/engine/off_chain/impls.rs | 2 +- crates/env/src/engine/on_chain/impls.rs | 2 +- crates/ink/src/env_access.rs | 2 +- crates/primitives/src/types.rs | 20 ++++++++------------ crates/storage/src/lazy/mapping.rs | 8 +++----- crates/storage/src/lazy/mod.rs | 8 +++----- examples/erc20/lib.rs | 9 ++++++--- examples/trait-erc20/lib.rs | 2 +- 10 files changed, 31 insertions(+), 36 deletions(-) diff --git a/crates/e2e/src/builders.rs b/crates/e2e/src/builders.rs index f29af243639..7c3f9b871f3 100644 --- a/crates/e2e/src/builders.rs +++ b/crates/e2e/src/builders.rs @@ -49,7 +49,7 @@ pub fn constructor_exec_input( // set all the other properties to default values, we only require the `exec_input`. builder .endowment(0u32.into()) - .code_hash(ink_primitives::Clear::clear()) + .code_hash(ink_primitives::Clear::CLEAR_HASH) .salt_bytes(Vec::new()) .params() .exec_input() diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index fe34065325c..9a45106ca7d 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -228,7 +228,7 @@ where /// # type AccountId = ::AccountId; /// let my_return_value: i32 = build_call::() /// .call_type(DelegateCall::new() -/// .code_hash(::Hash::clear())) +/// .code_hash(::Hash::CLEAR_HASH)) /// .exec_input( /// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) /// .push_arg(42u8) @@ -322,16 +322,16 @@ pub struct DelegateCall { impl DelegateCall { /// Returns a clean builder for [`DelegateCall`] - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + DelegateCall { + code_hash: E::Hash::CLEAR_HASH, + } } } impl Default for DelegateCall { fn default() -> Self { - DelegateCall { - code_hash: E::Hash::clear(), - } + Self::new() } } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 6954942e69c..e94c36568d4 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -136,7 +136,7 @@ where { let encoded = topic_value.encode(); let len_encoded = encoded.len(); - let mut result = ::Hash::clear(); + let mut result = ::Hash::CLEAR_HASH; let len_result = result.as_ref().len(); if len_encoded <= len_result { result.as_mut()[..len_encoded].copy_from_slice(&encoded[..]); diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 036bb16d877..d140fc9949c 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -148,7 +148,7 @@ where { fn inner(encoded: &mut [u8]) -> ::Hash { let len_encoded = encoded.len(); - let mut result = ::Hash::clear(); + let mut result = ::Hash::CLEAR_HASH; let len_result = result.as_ref().len(); if len_encoded <= len_result { result.as_mut()[..len_encoded].copy_from_slice(encoded); diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 1afdfbe8392..c9fe7491919 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -569,7 +569,7 @@ where /// let call_params = build_call::() /// .call_type( /// DelegateCall::new() - /// .code_hash(::Hash::clear())) + /// .code_hash(::Hash::CLEAR_HASH)) /// .exec_input( /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) /// .push_arg(42u8) diff --git a/crates/primitives/src/types.rs b/crates/primitives/src/types.rs index e6645ac0f55..6b86068e6b5 100644 --- a/crates/primitives/src/types.rs +++ b/crates/primitives/src/types.rs @@ -129,29 +129,25 @@ impl AsMut<[u8]> for Hash { /// /// A hash that consists only of 0 bits is clear. pub trait Clear { + /// The clear hash. + const CLEAR_HASH: Self; + /// Returns `true` if the hash is clear. fn is_clear(&self) -> bool; - - /// Returns a clear hash. - fn clear() -> Self; } impl Clear for [u8; 32] { - fn is_clear(&self) -> bool { - self.as_ref().iter().all(|&byte| byte == 0x00) - } + const CLEAR_HASH: Self = [0x00; 32]; - fn clear() -> Self { - [0x00; 32] + fn is_clear(&self) -> bool { + self == &Self::CLEAR_HASH } } impl Clear for Hash { + const CLEAR_HASH: Self = Self(<[u8; 32] as Clear>::CLEAR_HASH); + fn is_clear(&self) -> bool { <[u8; 32] as Clear>::is_clear(&self.0) } - - fn clear() -> Self { - Self(<[u8; 32] as Clear>::clear()) - } } diff --git a/crates/storage/src/lazy/mapping.rs b/crates/storage/src/lazy/mapping.rs index dd75510ee8f..6515dec5cbc 100644 --- a/crates/storage/src/lazy/mapping.rs +++ b/crates/storage/src/lazy/mapping.rs @@ -93,9 +93,7 @@ where KeyType: StorageKey, { fn default() -> Self { - Self { - _marker: Default::default(), - } + Self::new() } } @@ -105,9 +103,9 @@ where KeyType: StorageKey, { /// Creates a new empty `Mapping`. - pub fn new() -> Self { + pub const fn new() -> Self { Self { - _marker: Default::default(), + _marker: PhantomData, } } } diff --git a/crates/storage/src/lazy/mod.rs b/crates/storage/src/lazy/mod.rs index 4909d8bda16..a9418eceba8 100644 --- a/crates/storage/src/lazy/mod.rs +++ b/crates/storage/src/lazy/mod.rs @@ -100,9 +100,7 @@ where KeyType: StorageKey, { fn default() -> Self { - Self { - _marker: Default::default(), - } + Self::new() } } @@ -111,9 +109,9 @@ where KeyType: StorageKey, { /// Creates a new empty `Lazy`. - pub fn new() -> Self { + pub const fn new() -> Self { Self { - _marker: Default::default(), + _marker: PhantomData, } } } diff --git a/examples/erc20/lib.rs b/examples/erc20/lib.rs index 86b947a4a73..4608550f0aa 100644 --- a/examples/erc20/lib.rs +++ b/examples/erc20/lib.rs @@ -217,7 +217,10 @@ mod erc20 { mod tests { use super::*; - use ink::primitives::Clear; + use ink::primitives::{ + Clear, + Hash, + }; type Event = ::Type; @@ -259,7 +262,7 @@ mod erc20 { for (n, (actual_topic, expected_topic)) in topics.iter().zip(expected_topics).enumerate() { - let mut topic_hash = Hash::clear(); + let mut topic_hash = Hash::CLEAR_HASH; let len = actual_topic.len(); topic_hash.as_mut()[0..len].copy_from_slice(&actual_topic[0..len]); @@ -513,7 +516,7 @@ mod erc20 { primitives::Clear, }; - let mut result = Hash::clear(); + let mut result = Hash::CLEAR_HASH; let len_result = result.as_ref().len(); let encoded = entity.encode(); let len_encoded = encoded.len(); diff --git a/examples/trait-erc20/lib.rs b/examples/trait-erc20/lib.rs index fb1bd8da5a2..2168d42f10c 100644 --- a/examples/trait-erc20/lib.rs +++ b/examples/trait-erc20/lib.rs @@ -290,7 +290,7 @@ mod erc20 { where T: scale::Encode, { - let mut result = Hash::clear(); + let mut result = Hash::CLEAR_HASH; let len_result = result.as_ref().len(); let encoded = entity.encode(); let len_encoded = encoded.len(); From 8ed95eab822c9f83cacb444813f00431428293a7 Mon Sep 17 00:00:00 2001 From: yjh Date: Thu, 12 Jan 2023 01:39:28 +0800 Subject: [PATCH 07/18] chore: replace scale::Encode/Decode by scale::Codec (#1575) --- crates/storage/traits/src/storage.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/storage/traits/src/storage.rs b/crates/storage/traits/src/storage.rs index 9db6320ff86..326adae7729 100644 --- a/crates/storage/traits/src/storage.rs +++ b/crates/storage/traits/src/storage.rs @@ -16,8 +16,8 @@ use ink_primitives::Key; /// Trait for representing types which can be read and written to storage. /// -/// This trait is not the same as the `scale::Encode + scale::Decode`. Each type that implements -/// `scale::Encode + scale::Decode` are storable by default and transferable between contracts. +/// This trait is not the same as the [`scale::Codec`]. Each type that implements +/// [`scale::Codec`] are storable by default and transferable between contracts. /// But not each storable type is transferable. pub trait Storable: Sized { /// Convert self to a slice and append it to the destination. @@ -31,7 +31,7 @@ pub trait Storable: Sized { /// they can be written directly into the storage cell. impl

Storable for P where - P: scale::Encode + scale::Decode, + P: scale::Codec, { #[inline] fn encode(&self, dest: &mut T) { @@ -57,9 +57,9 @@ pub(crate) mod private { /// /// # Note /// -/// The trait is automatically implemented for types that implement `scale::Encode` -/// and `scale::Decode` via blanket implementation. -pub trait Packed: Storable + scale::Decode + scale::Encode + private::Sealed {} +/// The trait is automatically implemented for types that implement [`scale::Codec`] +/// via blanket implementation. +pub trait Packed: Storable + scale::Codec + private::Sealed {} /// Holds storage key for the type. /// From 8c3f65c3368429c607f630920c424cb2004ae8ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 18:10:42 -0800 Subject: [PATCH 08/18] Update secp256k1 requirement from 0.25.0 to 0.26.0 (#1596) Updates the requirements on [secp256k1](https://github.com/rust-bitcoin/rust-secp256k1) to permit the latest version. - [Release notes](https://github.com/rust-bitcoin/rust-secp256k1/releases) - [Changelog](https://github.com/rust-bitcoin/rust-secp256k1/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-bitcoin/rust-secp256k1/commits/secp256k1-0.26.0) --- updated-dependencies: - dependency-name: secp256k1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- crates/engine/Cargo.toml | 2 +- crates/env/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 56d4c4938a3..e3a716486eb 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -24,7 +24,7 @@ sha3 = { version = "0.10" } blake2 = { version = "0.10" } # ECDSA for the off-chain environment. -secp256k1 = { version = "0.25.0", features = ["recovery", "global-context"], optional = true } +secp256k1 = { version = "0.26.0", features = ["recovery", "global-context"], optional = true } [features] default = ["std"] diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index e9ec0e0d47d..c6ef99cf836 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -41,7 +41,7 @@ sha3 = { version = "0.10", optional = true } blake2 = { version = "0.10", optional = true } # ECDSA for the off-chain environment. -secp256k1 = { version = "0.25.0", features = ["recovery", "global-context"], optional = true } +secp256k1 = { version = "0.26.0", features = ["recovery", "global-context"], optional = true } # Only used in the off-chain environment. # From e2715f79bcc97b2190d35474244e5f595acda0bc Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 19 Jan 2023 14:53:51 -0800 Subject: [PATCH 09/18] Clean up `CallBuilder` `return()` type (#1525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Assume that `CallBuilder`s return a `MessageResult` * Add `try_*` variants to `CallBuilder` * Add doc test showing how to handle `LangError` from `build_call` * Remove TODO related to `delegate_call` * Account for `LangError` in E2E tests * Improve `return_value` error message * Remove extra `expect` from E2E tests * Add test for checking that `fire` panics on `LangError` * Fix spelling * Remove extra `unwrap` in more E2E tests * Fix ERC-1155 example * Fix `invoke_contract` doc test * RustFmt * Fix `delegator` example * Update ERC-20 tests * Indicate that doc test panics in off-chain env * Forgot some commas 🤦 * Get `Flipper` example compiling again * Remove more unwraps * Update UI tests * Print out `LangError` when panicking after `invoke` * Bump `scale` to fix UI tests --- crates/e2e/src/client.rs | 24 +++-- crates/env/src/api.rs | 4 +- crates/env/src/backend.rs | 2 +- crates/env/src/call/call_builder.rs | 94 ++++++++++++++++++- crates/env/src/engine/off_chain/impls.rs | 2 +- crates/env/src/engine/on_chain/impls.rs | 2 +- .../generator/as_dependency/call_builder.rs | 5 +- .../generator/as_dependency/contract_ref.rs | 2 +- crates/ink/src/env_access.rs | 7 +- .../fail/message-input-non-codec.stderr | 4 +- .../fail/message-returns-non-codec.stderr | 19 ++-- .../fail/message_output_non_codec.stderr | 66 ++++++------- examples/delegator/lib.rs | 9 +- examples/erc1155/lib.rs | 7 +- examples/erc20/lib.rs | 10 +- examples/flipper/lib.rs | 6 +- .../call-builder/lib.rs | 87 ++++++++++++++--- .../constructors-return-value/lib.rs | 3 +- .../contract-ref/lib.rs | 8 +- .../integration-flipper/lib.rs | 16 +--- 20 files changed, 261 insertions(+), 116 deletions(-) diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index 3f9981d2cc5..4860bbf75cb 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -33,6 +33,7 @@ use super::{ }; use contract_metadata::ContractMetadata; use ink_env::Environment; +use ink_primitives::MessageResult; use sp_runtime::traits::{ IdentifyAccount, @@ -124,7 +125,7 @@ pub struct CallResult { pub events: ExtrinsicEvents, /// Contains the result of decoding the return value of the called /// function. - pub value: Result, + pub value: Result, scale::Error>, /// Returns the bytes of the encoded dry-run return value. pub data: Vec, } @@ -139,12 +140,19 @@ where /// Panics if the value could not be decoded. The raw bytes can be accessed /// via [`return_data`]. pub fn return_value(self) -> V { - self.value.unwrap_or_else(|err| { - panic!( - "decoding dry run result to ink! message return type failed: {}", - err - ) - }) + self.value + .unwrap_or_else(|env_err| { + panic!( + "Decoding dry run result to ink! message return type failed: {}", + env_err + ) + }) + .unwrap_or_else(|lang_err| { + panic!( + "Encountered a `LangError` while decoding dry run result to ink! message: {:?}", + lang_err + ) + }) } /// Returns true if the specified event was triggered by the call. @@ -655,7 +663,7 @@ where } let bytes = &dry_run.result.as_ref().unwrap().data; - let value: Result = + let value: Result, scale::Error> = scale::Decode::decode(&mut bytes.as_ref()); Ok(CallResult { diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index e4821480a67..0683fcf4b09 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -268,7 +268,9 @@ where /// - If the called contract execution has trapped. /// - If the called contract ran out of gas upon execution. /// - If the returned value failed to decode properly. -pub fn invoke_contract(params: &CallParams, Args, R>) -> Result +pub fn invoke_contract( + params: &CallParams, Args, R>, +) -> Result> where E: Environment, Args: scale::Encode, diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index dc3a8832b0e..39beb23cdee 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -414,7 +414,7 @@ pub trait TypedEnvBackend: EnvBackend { fn invoke_contract( &mut self, call_data: &CallParams, Args, R>, - ) -> Result + ) -> Result> where E: Environment, Args: scale::Encode, diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index 9a45106ca7d..3c208c5cddb 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -109,7 +109,27 @@ where /// Invokes the contract with the given built-up call parameters. /// /// Returns the result of the contract execution. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle + /// those use the [`try_invoke`][`CallParams::try_invoke`] method instead. pub fn invoke(&self) -> Result { + crate::invoke_contract(self).map(|inner| { + inner.unwrap_or_else(|lang_error| { + panic!("Cross-contract call failed with {:?}", lang_error) + }) + }) + } + + /// Invokes the contract with the given built-up call parameters. + /// + /// Returns the result of the contract execution. + /// + /// # Note + /// + /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + pub fn try_invoke(&self) -> Result, crate::Error> { crate::invoke_contract(self) } } @@ -173,7 +193,7 @@ where /// ) /// .returns::<()>() /// .fire() -/// .unwrap(); +/// .expect("Got an error from the Contract's pallet."); /// ``` /// /// ## Example 2: With Return Value @@ -209,7 +229,7 @@ where /// ) /// .returns::() /// .fire() -/// .unwrap(); +/// .expect("Got an error from the Contract's pallet."); /// ``` /// /// ## Example 3: Delegate call @@ -237,7 +257,47 @@ where /// ) /// .returns::() /// .fire() -/// .unwrap(); +/// .expect("Got an error from the Contract's pallet."); +/// ``` +/// +/// # Handling `LangError`s +/// +/// It is also important to note that there are certain types of errors which can happen during +/// cross-contract calls which can be handled know as [`LangError`][`ink_primitives::LangError`]. +/// +/// If you want to handle these errors use the [`CallBuilder::try_fire`] methods instead of the +/// [`CallBuilder::fire`] ones. +/// +/// **Note:** The shown examples panic because there is currently no cross-calling +/// support in the off-chain testing environment. However, this code +/// should work fine in on-chain environments. +/// +/// ## Example: Handling a `LangError` +/// +/// ```should_panic +/// # use ::ink_env::{ +/// # Environment, +/// # DefaultEnvironment, +/// # call::{build_call, Selector, ExecutionInput} +/// # }; +/// # use ink_env::call::Call; +/// # type AccountId = ::AccountId; +/// # type Balance = ::Balance; +/// let call_result = build_call::() +/// .call_type( +/// Call::new() +/// .callee(AccountId::from([0x42; 32])) +/// .gas_limit(5000) +/// .transferred_value(10), +/// ) +/// .try_fire() +/// .expect("Got an error from the Contract's pallet."); +/// +/// match call_result { +/// Ok(_) => unimplemented!(), +/// Err(e @ ink_primitives::LangError::CouldNotReadInput) => unimplemented!(), +/// Err(_) => unimplemented!(), +/// } /// ``` #[allow(clippy::type_complexity)] pub fn build_call() -> CallBuilder< @@ -597,9 +657,23 @@ where E: Environment, { /// Invokes the cross-chain function call. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle + /// those use the [`try_fire`][`CallBuilder::try_fire`] method instead. pub fn fire(self) -> Result<(), Error> { self.params().invoke() } + + /// Invokes the cross-chain function call. + /// + /// # Note + /// + /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + pub fn try_fire(self) -> Result, Error> { + self.params().try_invoke() + } } impl @@ -626,9 +700,23 @@ where R: scale::Decode, { /// Invokes the cross-chain function call and returns the result. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle + /// those use the [`try_fire`][`CallBuilder::try_fire`] method instead. pub fn fire(self) -> Result { self.params().invoke() } + + /// Invokes the cross-chain function call and returns the result. + /// + /// # Note + /// + /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + pub fn try_fire(self) -> Result, Error> { + self.params().try_invoke() + } } impl diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index e94c36568d4..a17bdc3a5ff 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -440,7 +440,7 @@ impl TypedEnvBackend for EnvInstance { fn invoke_contract( &mut self, params: &CallParams, Args, R>, - ) -> Result + ) -> Result> where E: Environment, Args: scale::Encode, diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index d140fc9949c..6f3b7572c04 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -415,7 +415,7 @@ impl TypedEnvBackend for EnvInstance { fn invoke_contract( &mut self, params: &CallParams, Args, R>, - ) -> Result + ) -> Result> where E: Environment, Args: scale::Encode, diff --git a/crates/ink/codegen/src/generator/as_dependency/call_builder.rs b/crates/ink/codegen/src/generator/as_dependency/call_builder.rs index 9d3232133f8..e85e52b0ee3 100644 --- a/crates/ink/codegen/src/generator/as_dependency/call_builder.rs +++ b/crates/ink/codegen/src/generator/as_dependency/call_builder.rs @@ -369,7 +369,10 @@ impl CallBuilder<'_> { let input_types = generator::input_types(message.inputs()); let arg_list = generator::generate_argument_list(input_types.iter().cloned()); let mut_tok = callable.receiver().is_ref_mut().then(|| quote! { mut }); - let return_type = message.wrapped_output(); + let return_type = message + .output() + .map(quote::ToTokens::to_token_stream) + .unwrap_or_else(|| quote::quote! { () }); let output_span = return_type.span(); let output_type = quote_spanned!(output_span=> ::ink::env::call::CallBuilder< diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index 588d14fd8af..2cf5fe71b26 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -370,7 +370,7 @@ impl ContractRef<'_> { ) -> #wrapped_output_type { ::#call_operator(self) .#message_ident( #( #input_bindings ),* ) - .fire() + .try_fire() .unwrap_or_else(|error| ::core::panic!( "encountered error while calling {}::{}: {:?}", ::core::stringify!(#storage_ident), diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index c9fe7491919..cd691cfb875 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -519,7 +519,10 @@ where /// ) /// .returns::() /// .params(); - /// self.env().invoke_contract(&call_params).unwrap_or_else(|err| panic!("call invocation must succeed: {:?}", err)) + /// + /// self.env().invoke_contract(&call_params) + /// .unwrap_or_else(|env_err| panic!("Received an error from the Environment: {:?}", env_err)) + /// .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err)) /// } /// # /// # } @@ -532,7 +535,7 @@ where pub fn invoke_contract( self, params: &CallParams, Args, R>, - ) -> Result + ) -> Result> where Args: scale::Encode, R: scale::Decode, diff --git a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr index e6c2260060a..a083a628f30 100644 --- a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr @@ -53,11 +53,11 @@ note: required by a bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `fire` exists for struct `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-input-non-codec.rs:16:9 | 16 | pub fn message(&self, _input: NonCodecType) {} - | ^^^ method cannot be called on `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>>` due to unsatisfied trait bounds + | ^^^ method cannot be called on `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds | ::: $WORKSPACE/crates/env/src/call/execution_input.rs | diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index b5565368175..1bdb9e94006 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -34,16 +34,19 @@ note: required by a bound in `return_value` | R: scale::Encode, | ^^^^^^^^^^^^^ required by this bound in `return_value` -error[E0599]: the method `fire` exists for struct `ink::ink_env::call::CallBuilder>, Set>>, Set>>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:16:9 | +4 | pub struct NonCodecType; + | ----------------------- doesn't satisfy `NonCodecType: parity_scale_codec::Decode` +... 16 | pub fn message(&self) -> NonCodecType { - | ^^^ method cannot be called on `ink::ink_env::call::CallBuilder>, Set>>, Set>>>` due to unsatisfied trait bounds - | - ::: $RUST/core/src/result.rs - | - | pub enum Result { - | --------------------- doesn't satisfy `_: parity_scale_codec::Decode` + | ^^^ method cannot be called on `ink::ink_env::call::CallBuilder>, Set>>, Set>>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: - `Result: parity_scale_codec::Decode` + `NonCodecType: parity_scale_codec::Decode` +note: the following trait must be implemented + --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs + | + | pub trait Decode: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index d9b358b613f..2c487dcca6f 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -1,39 +1,39 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied - --> tests/ui/trait_def/fail/message_output_non_codec.rs:6:26 - | -6 | fn message(&self) -> NonCodec; - | ^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` - | - = help: the following other types implement trait `WrapperTypeEncode`: - &T - &mut T - Arc - Box - Cow<'a, T> - Rc - String - Vec - parity_scale_codec::Ref<'a, T, U> - = note: required for `NonCodec` to implement `Encode` + --> tests/ui/trait_def/fail/message_output_non_codec.rs:6:26 + | +6 | fn message(&self) -> NonCodec; + | ^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` + | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + String + Vec + parity_scale_codec::Ref<'a, T, U> + = note: required for `NonCodec` to implement `Encode` note: required by a bound in `DispatchOutput` - --> src/codegen/dispatch/type_check.rs - | - | T: scale::Encode + 'static; - | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` + --> src/codegen/dispatch/type_check.rs + | + | T: scale::Encode + 'static; + | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` error[E0599]: the method `fire` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied - --> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5 - | -1 | pub struct NonCodec; - | ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode` + --> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5 + | +1 | pub struct NonCodec; + | ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode` ... -5 | #[ink(message)] - | ^ method cannot be called on `CallBuilder>, Set>>, Set>>` due to unsatisfied trait bounds - | - = note: the following trait bounds were not satisfied: - `NonCodec: parity_scale_codec::Decode` +5 | #[ink(message)] + | ^ method cannot be called on `CallBuilder>, Set>>, Set>>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `NonCodec: parity_scale_codec::Decode` note: the following trait must be implemented - --> $CARGO/parity-scale-codec-3.2.1/src/codec.rs - | - | pub trait Decode: Sized { - | ^^^^^^^^^^^^^^^^^^^^^^^ + --> $CARGO/parity-scale-codec-3.2.2/src/codec.rs + | + | pub trait Decode: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/delegator/lib.rs b/examples/delegator/lib.rs index ec45e70c0d3..074ac5cae7b 100644 --- a/examples/delegator/lib.rs +++ b/examples/delegator/lib.rs @@ -174,8 +174,7 @@ mod delegator { .call(&ink_e2e::bob(), get, 0, None) .await .expect("calling `get` failed") - .return_value() - .expect("calling `get` returned a `LangError`"); + .return_value(); assert_eq!(value, 1234); let change = build_message::(delegator_acc_id.clone()) .call(|contract| contract.change(6)); @@ -191,8 +190,7 @@ mod delegator { .call(&ink_e2e::bob(), get, 0, None) .await .expect("calling `get` failed") - .return_value() - .expect("calling `get` returned a `LangError`"); + .return_value(); assert_eq!(value, 1234 + 6); // when @@ -216,8 +214,7 @@ mod delegator { .call(&ink_e2e::bob(), get, 0, None) .await .expect("calling `get` failed") - .return_value() - .expect("calling `get` returned a `LangError`"); + .return_value(); assert_eq!(value, 1234 + 6 - 3); Ok(()) diff --git a/examples/erc1155/lib.rs b/examples/erc1155/lib.rs index 9140fa21f80..55f86b16f5a 100644 --- a/examples/erc1155/lib.rs +++ b/examples/erc1155/lib.rs @@ -365,7 +365,7 @@ mod erc1155 { // If our recipient is a smart contract we need to see if they accept or // reject this transfer. If they reject it we need to revert the call. - let params = build_call::() + let result = build_call::() .call_type(Call::new().callee(to).gas_limit(5000)) .exec_input( ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR)) @@ -376,9 +376,10 @@ mod erc1155 { .push_arg(data), ) .returns::>() - .params(); + .params() + .invoke(); - match ink::env::invoke_contract(¶ms) { + match result { Ok(v) => { ink::env::debug_println!( "Received return value \"{:?}\" from contract {:?}", diff --git a/examples/erc20/lib.rs b/examples/erc20/lib.rs index 4608550f0aa..65e14a15f29 100644 --- a/examples/erc20/lib.rs +++ b/examples/erc20/lib.rs @@ -577,14 +577,10 @@ mod erc20 { // then assert_eq!( total_supply, - total_supply_res.return_value().unwrap(), + total_supply_res.return_value(), "total_supply" ); - assert_eq!( - transfer_to_bob, - balance_of_res.return_value().unwrap(), - "balance_of" - ); + assert_eq!(transfer_to_bob, balance_of_res.return_value(), "balance_of"); Ok(()) } @@ -671,7 +667,7 @@ mod erc20 { assert_eq!( total_supply - approved_value, - balance_of_res.return_value().unwrap(), + balance_of_res.return_value(), "balance_of" ); diff --git a/examples/flipper/lib.rs b/examples/flipper/lib.rs index d4d0c82b2ac..3525b8b633d 100644 --- a/examples/flipper/lib.rs +++ b/examples/flipper/lib.rs @@ -75,7 +75,7 @@ pub mod flipper { .call(&ink_e2e::bob(), get, 0, None) .await .expect("get failed"); - assert!(matches!(get_res.return_value(), Ok(false))); + assert!(matches!(get_res.return_value(), false)); // when let flip = build_message::(contract_acc_id.clone()) @@ -92,7 +92,7 @@ pub mod flipper { .call(&ink_e2e::bob(), get, 0, None) .await .expect("get failed"); - assert!(matches!(get_res.return_value(), Ok(true))); + assert!(matches!(get_res.return_value(), true)); Ok(()) } @@ -116,7 +116,7 @@ pub mod flipper { .call(&ink_e2e::bob(), get, 0, None) .await .expect("get failed"); - assert!(matches!(get_res.return_value(), Ok(false))); + assert!(matches!(get_res.return_value(), false)); Ok(()) } diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index 5459e33548a..0a3de1e94ef 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -43,8 +43,8 @@ mod call_builder { let result = build_call::() .call_type(Call::new().callee(address)) .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::>() - .fire() + .returns::<()>() + .try_fire() .expect("Error from the Contracts pallet."); match result { @@ -56,6 +56,25 @@ mod call_builder { } } + /// Call a contract using the `CallBuilder`. + /// + /// Since we can't use the `CallBuilder` in a test environment directly we need this + /// wrapper to test things like crafting calls with invalid selectors. + /// + /// This message does not allow the caller to handle any `LangErrors`, for that use the + /// `call` message instead. + #[ink(message)] + pub fn fire(&mut self, address: AccountId, selector: [u8; 4]) { + use ink::env::call::build_call; + + build_call::() + .call_type(Call::new().callee(address)) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::<()>() + .fire() + .expect("Error from the Contracts pallet.") + } + #[ink(message)] pub fn call_instantiate( &mut self, @@ -79,6 +98,8 @@ mod call_builder { // NOTE: Right now we can't handle any `LangError` from `instantiate`, we can only tell // that our contract reverted (i.e we see error from the Contracts pallet). + // + // This will be fixed with #1512. result.ok().map(|id| ink::ToAccountId::to_account_id(&id)) } } @@ -123,9 +144,7 @@ mod call_builder { .call(&ink_e2e::charlie(), flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); - let initial_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let initial_value = get_call_result.return_value(); let invalid_selector = [0x00, 0x00, 0x00, 0x00]; let call = build_message::(contract_acc_id) @@ -135,9 +154,7 @@ mod call_builder { .await .expect("Calling `call_builder::call` failed"); - let flipper_result = call_result - .return_value() - .expect("Call to `call_builder::call` failed"); + let flipper_result = call_result.return_value(); assert!(matches!( flipper_result, @@ -150,14 +167,56 @@ mod call_builder { .call(&ink_e2e::charlie(), flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); - let flipped_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let flipped_value = get_call_result.return_value(); assert!(flipped_value == initial_value); Ok(()) } + #[ink_e2e::test(additional_contracts = "../integration-flipper/Cargo.toml")] + async fn e2e_invalid_message_selector_panics_on_fire( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let flipper_constructor = FlipperRef::new_default(); + let flipper_acc_id = client + .instantiate( + "integration_flipper", + &ink_e2e::ferdie(), + flipper_constructor, + 0, + None, + ) + .await + .expect("instantiate `flipper` failed") + .account_id; + + // Since `LangError`s can't be handled by the `CallBuilder::fire()` method we expect + // this to panic. + let invalid_selector = [0x00, 0x00, 0x00, 0x00]; + let call = build_message::(contract_acc_id) + .call(|contract| contract.fire(flipper_acc_id, invalid_selector)); + let call_result = client.call(&ink_e2e::ferdie(), call, 0, None).await; + + assert!(call_result.is_err()); + let contains_err_msg = match call_result.unwrap_err() { + ink_e2e::Error::CallDryRun(dry_run) => { + String::from_utf8_lossy(&dry_run.debug_message) + .contains("Cross-contract call failed with CouldNotReadInput") + } + _ => false, + }; + assert!(contains_err_msg); + + Ok(()) + } + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] async fn e2e_create_builder_works_with_valid_selector( mut client: ink_e2e::Client, @@ -184,8 +243,7 @@ mod call_builder { .call(&ink_e2e::dave(), call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") - .return_value() - .expect("Dispatching `call_builder::call_instantiate` failed."); + .return_value(); assert!( call_result.is_some(), @@ -221,8 +279,7 @@ mod call_builder { .call(&ink_e2e::eve(), call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") - .return_value() - .expect("Dispatching `call_builder::call_instantiate` failed."); + .return_value(); assert!( call_result.is_none(), diff --git a/examples/lang-err-integration-tests/constructors-return-value/lib.rs b/examples/lang-err-integration-tests/constructors-return-value/lib.rs index 53147d9d41f..35716615f18 100644 --- a/examples/lang-err-integration-tests/constructors-return-value/lib.rs +++ b/examples/lang-err-integration-tests/constructors-return-value/lib.rs @@ -191,8 +191,7 @@ pub mod constructors_return_value { .call(&ink_e2e::bob(), get, 0, None) .await .expect("Calling `get_value` failed") - .return_value() - .expect("Input is valid, call must not fail."); + .return_value(); assert_eq!( true, value, diff --git a/examples/lang-err-integration-tests/contract-ref/lib.rs b/examples/lang-err-integration-tests/contract-ref/lib.rs index 9ab9ec45ab3..64074d14faa 100755 --- a/examples/lang-err-integration-tests/contract-ref/lib.rs +++ b/examples/lang-err-integration-tests/contract-ref/lib.rs @@ -80,9 +80,7 @@ mod contract_ref { .call(&ink_e2e::alice(), get_check, 0, None) .await .expect("Calling `get_check` failed"); - let initial_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let initial_value = get_call_result.return_value(); let flip_check = build_message::(contract_acc_id.clone()) .call(|contract| contract.flip_check()); @@ -101,9 +99,7 @@ mod contract_ref { .call(&ink_e2e::alice(), get_check, 0, None) .await .expect("Calling `get_check` failed"); - let flipped_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let flipped_value = get_call_result.return_value(); assert!(flipped_value != initial_value); Ok(()) diff --git a/examples/lang-err-integration-tests/integration-flipper/lib.rs b/examples/lang-err-integration-tests/integration-flipper/lib.rs index c000ad24802..0df61977285 100644 --- a/examples/lang-err-integration-tests/integration-flipper/lib.rs +++ b/examples/lang-err-integration-tests/integration-flipper/lib.rs @@ -77,9 +77,7 @@ pub mod integration_flipper { .call(&ink_e2e::alice(), get, 0, None) .await .expect("Calling `get` failed"); - let initial_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let initial_value = get_call_result.return_value(); let flip = build_message::(contract_acc_id) .call(|contract| contract.flip()); @@ -98,9 +96,7 @@ pub mod integration_flipper { .call(&ink_e2e::alice(), get, 0, None) .await .expect("Calling `get` failed"); - let flipped_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let flipped_value = get_call_result.return_value(); assert!(flipped_value != initial_value); Ok(()) @@ -123,9 +119,7 @@ pub mod integration_flipper { .call(&ink_e2e::bob(), get, 0, None) .await .expect("Calling `get` failed"); - let initial_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let initial_value = get_call_result.return_value(); let err_flip = build_message::(contract_acc_id) .call(|contract| contract.err_flip()); @@ -143,9 +137,7 @@ pub mod integration_flipper { .call(&ink_e2e::bob(), get, 0, None) .await .expect("Calling `get` failed"); - let flipped_value = get_call_result - .return_value() - .expect("Input is valid, call must not fail."); + let flipped_value = get_call_result.return_value(); assert!(flipped_value == initial_value); Ok(()) From de17d1009da622a3fb96ebcbd665bdaca4f03736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Fri, 20 Jan 2023 12:28:30 +0100 Subject: [PATCH 10/18] Add `SECURITY.md` (#1603) --- SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..7a6cbe7ecc9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Reporting a vulnerability + +If you find something that can be treated as a security vulnerability, please do not use the issue tracker or discuss it in the public forum/channels, as it can cause more damage rather than giving real help to the ecosystem. + +Security vulnerabilities should be reported using [this contact form](https://security-submission.parity.io/). + +If you think that your report might be eligible for the Bug Bounty Program, please mark this during the submission. Please check up-to-date [Parity Bug Bounty Program rules](https://www.parity.io/bug-bounty) for more information about our Bug Bounty Program. + +**Warning:** This is an unified `SECURITY.md` file for the Paritytech GitHub Organization. The presence of this file does not mean that this repository is covered by the Bug Bounty program. Please always check the Bug Bounty Program scope for the information. From a83c937d41d16125260afdf5e72bdb8a00a053bf Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 20 Jan 2023 06:32:16 -0800 Subject: [PATCH 11/18] Make `CallBuilder` and `CreateBuilder` error handling optional (#1602) * Make default behaviour of `fire()` to return "raw" return value * Update delegate call codepath to give option to handle `EnvError` * Update `CreateBuilder::instantiate()`'s default cleaner as well * Add a breaking change note in the changelog * Fix delegator example * Drive-by: rename `fire()` method to `invoke()` This keeps this more consistent since we're wrapping a few methods with `invoke` in the name anyways. * Fix ERC-1155 example * Update `Multisig` example * Fix ContractRef tests * Revert "Drive-by: rename `fire()` method to `invoke()`" This reverts commit 72add05d0c6dcc703502e11c7e862a0a03222eac. Decided it's best to do this in a standalone PR. * Use instantiate instead of `try_instantiate` in `delegator` Co-authored-by: Andrew Jones --- CHANGELOG.md | 8 ++ crates/env/src/call/call_builder.rs | 107 +++++++++++++----- crates/env/src/call/create_builder.rs | 45 +++++++- .../src/generator/trait_def/call_forwarder.rs | 5 +- .../fail/message_input_non_codec.stderr | 100 ++++++++-------- .../fail/message_output_non_codec.stderr | 2 +- examples/delegator/lib.rs | 18 +-- examples/erc1155/lib.rs | 8 +- .../call-builder/lib.rs | 3 +- .../contract-ref/lib.rs | 5 +- examples/multisig/lib.rs | 24 ++-- .../forward-calls/lib.rs | 12 +- 12 files changed, 216 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 754fada5fe1..e447c852d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Add E2E testing framework MVP ‒ [#1395](https://github.com/paritytech/ink/pull/1395) - 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) + +### Breaking Changes +With the addition of [#1602](https://github.com/paritytech/ink/pull/1602), +the `CallBuilder::fire()`, `CallParams::invoke()`, and `CreateBuilder::instantiate()` +methods now unwrap the `Result` from `pallet-contracts` under the hood. + +If you wish to handle the error use the new `try_` variants of those methods instead. ## Version 4.0.0-beta diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index 3c208c5cddb..e88ec37df1a 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -112,14 +112,17 @@ where /// /// # Panics /// - /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle - /// those use the [`try_invoke`][`CallParams::try_invoke`] method instead. - pub fn invoke(&self) -> Result { - crate::invoke_contract(self).map(|inner| { - inner.unwrap_or_else(|lang_error| { + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_invoke`][`CallParams::try_invoke`] method instead. + pub fn invoke(&self) -> R { + crate::invoke_contract(self) + .unwrap_or_else(|env_error| { + panic!("Cross-contract call failed with {:?}", env_error) + }) + .unwrap_or_else(|lang_error| { panic!("Cross-contract call failed with {:?}", lang_error) }) - }) } /// Invokes the contract with the given built-up call parameters. @@ -128,7 +131,8 @@ where /// /// # Note /// - /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink_primitives::LangError`], both of which can be handled by the caller. pub fn try_invoke(&self) -> Result, crate::Error> { crate::invoke_contract(self) } @@ -140,11 +144,29 @@ where Args: scale::Encode, R: scale::Decode, { - /// Invokes the contract via delegated call with the given - /// built-up call parameters. + /// Invoke the contract using Delegate Call semantics with the given built-up call parameters. /// /// Returns the result of the contract execution. - pub fn invoke(&self) -> Result { + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] If you want to + /// handle those use the [`try_invoke`][`CallParams::try_invoke`] method instead. + pub fn invoke(&self) -> R { + crate::invoke_contract_delegate(self).unwrap_or_else(|env_error| { + panic!("Cross-contract call failed with {:?}", env_error) + }) + } + + /// Invoke the contract using Delegate Call semantics with the given built-up call parameters. + /// + /// Returns the result of the contract execution. + /// + /// # Note + /// + /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be handled by the + /// caller. + pub fn try_invoke(&self) -> Result { crate::invoke_contract_delegate(self) } } @@ -192,8 +214,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::<()>() -/// .fire() -/// .expect("Got an error from the Contract's pallet."); +/// .fire(); /// ``` /// /// ## Example 2: With Return Value @@ -228,8 +249,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() -/// .fire() -/// .expect("Got an error from the Contract's pallet."); +/// .fire(); /// ``` /// /// ## Example 3: Delegate call @@ -256,8 +276,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() -/// .fire() -/// .expect("Got an error from the Contract's pallet."); +/// .fire(); /// ``` /// /// # Handling `LangError`s @@ -660,9 +679,10 @@ where /// /// # Panics /// - /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle - /// those use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) -> Result<(), Error> { + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_fire`][`CallBuilder::try_fire`] method instead. + pub fn fire(self) { self.params().invoke() } @@ -670,7 +690,8 @@ where /// /// # Note /// - /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink_primitives::LangError`], both of which can be handled by the caller. pub fn try_fire(self) -> Result, Error> { self.params().try_invoke() } @@ -686,10 +707,24 @@ impl where E: Environment, { - /// Invokes the cross-chain function call. - pub fn fire(self) -> Result<(), Error> { + /// Invokes the cross-chain function call using Delegate Call semantics. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] + /// If you want to handle those use the [`try_fire`][`CallBuilder::try_fire`] method instead. + pub fn fire(self) { self.params().invoke() } + + /// Invokes the cross-chain function call using Delegate Call semantics. + /// + /// # Note + /// + /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the caller. + pub fn try_fire(self) -> Result<(), Error> { + self.params().try_invoke() + } } impl @@ -703,9 +738,10 @@ where /// /// # Panics /// - /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle - /// those use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) -> Result { + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_fire`][`CallBuilder::try_fire`] method instead. + pub fn fire(self) -> R { self.params().invoke() } @@ -713,7 +749,8 @@ where /// /// # Note /// - /// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller. + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink_primitives::LangError`], both of which can be handled by the caller. pub fn try_fire(self) -> Result, Error> { self.params().try_invoke() } @@ -726,8 +763,22 @@ where Args: scale::Encode, R: scale::Decode, { - /// Invokes the cross-chain function call and returns the result. - pub fn fire(self) -> Result { + /// Invokes the cross-chain function call using Delegate Call semantics and returns the result. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] + /// If you want to handle those use the [`try_fire`][`CallBuilder::try_fire`] method instead. + pub fn fire(self) -> R { self.params().invoke() } + + /// Invokes the cross-chain function call using Delegate Call semantics and returns the result. + /// + /// # Note + /// + /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the caller. + pub fn try_fire(self) -> Result { + self.params().try_invoke() + } } diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index c0657858377..527ede79dcb 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -119,8 +119,28 @@ where R: FromAccountId, { /// Instantiates the contract and returns its account ID back to the caller. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you want to + /// handle those use the [`try_instantiate`][`CreateParams::try_instantiate`] method instead. #[inline] - pub fn instantiate(&self) -> Result { + pub fn instantiate(&self) -> R { + crate::instantiate_contract(self) + .map(FromAccountId::from_account_id) + .unwrap_or_else(|env_error| { + panic!("Cross-contract instantiation failed with {:?}", env_error) + }) + } + + /// Instantiates the contract and returns its account ID back to the caller. + /// + /// # Note + /// + /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be handled by the + /// caller. + #[inline] + pub fn try_instantiate(&self) -> Result { crate::instantiate_contract(self).map(FromAccountId::from_account_id) } } @@ -180,8 +200,7 @@ where /// ) /// .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) /// .params() -/// .instantiate() -/// .unwrap(); +/// .instantiate(); /// ``` /// /// **Note:** The shown example panics because there is currently no cross-calling @@ -384,9 +403,25 @@ where Salt: AsRef<[u8]>, R: FromAccountId, { - /// Instantiates the contract using the given instantiation parameters. + /// Instantiates the contract and returns its account ID back to the caller. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you want to + /// handle those use the [`try_instantiate`][`CreateBuilder::try_instantiate`] method instead. #[inline] - pub fn instantiate(self) -> Result { + pub fn instantiate(self) -> R { self.params().instantiate() } + + /// Instantiates the contract and returns its account ID back to the caller. + /// + /// # Note + /// + /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be handled by the + /// caller. + #[inline] + pub fn try_instantiate(self) -> Result { + self.params().try_instantiate() + } } diff --git a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs index d2828712aa7..46017c5dff2 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs @@ -355,8 +355,9 @@ impl CallForwarder<'_> { , #input_bindings )* ) - .fire() - .unwrap_or_else(|err| ::core::panic!("{}: {:?}", #panic_str, err)) + .try_fire() + .unwrap_or_else(|env_err| ::core::panic!("{}: {:?}", #panic_str, env_err)) + .unwrap_or_else(|lang_err| ::core::panic!("{}: {:?}", #panic_str, lang_err)) } ) } diff --git a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr index 63b1a30a63a..a4a79052510 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr @@ -1,56 +1,56 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeDecode` is not satisfied - --> tests/ui/trait_def/fail/message_input_non_codec.rs:6:23 - | -6 | fn message(&self, input: NonCodec); - | ^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodec` - | - = help: the following other types implement trait `WrapperTypeDecode`: - Arc - Box - Rc - = note: required for `NonCodec` to implement `parity_scale_codec::Decode` + --> tests/ui/trait_def/fail/message_input_non_codec.rs:6:23 + | +6 | fn message(&self, input: NonCodec); + | ^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodec` + | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + = note: required for `NonCodec` to implement `parity_scale_codec::Decode` note: required by a bound in `DispatchInput` - --> src/codegen/dispatch/type_check.rs - | - | T: scale::Decode + 'static; - | ^^^^^^^^^^^^^ required by this bound in `DispatchInput` + --> src/codegen/dispatch/type_check.rs + | + | T: scale::Decode + 'static; + | ^^^^^^^^^^^^^ required by this bound in `DispatchInput` error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied - --> tests/ui/trait_def/fail/message_input_non_codec.rs:3:1 - | -3 | #[ink::trait_definition] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` -4 | pub trait TraitDefinition { -5 | #[ink(message)] - | - required by a bound introduced by this call - | - = help: the following other types implement trait `WrapperTypeEncode`: - &T - &mut T - Arc - Box - Cow<'a, T> - Rc - String - Vec - parity_scale_codec::Ref<'a, T, U> - = note: required for `NonCodec` to implement `Encode` + --> tests/ui/trait_def/fail/message_input_non_codec.rs:3:1 + | +3 | #[ink::trait_definition] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` +4 | pub trait TraitDefinition { +5 | #[ink(message)] + | - required by a bound introduced by this call + | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + String + Vec + parity_scale_codec::Ref<'a, T, U> + = note: required for `NonCodec` to implement `Encode` note: required by a bound in `ExecutionInput::>::push_arg` - --> $WORKSPACE/crates/env/src/call/execution_input.rs - | - | T: scale::Encode, - | ^^^^^^^^^^^^^ required by this bound in `ExecutionInput::>::push_arg` + --> $WORKSPACE/crates/env/src/call/execution_input.rs + | + | T: scale::Encode, + | ^^^^^^^^^^^^^ required by this bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `fire` exists for struct `CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied - --> tests/ui/trait_def/fail/message_input_non_codec.rs:5:5 - | -5 | #[ink(message)] - | ^ method cannot be called on `CallBuilder>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds - | - ::: $WORKSPACE/crates/env/src/call/execution_input.rs - | - | pub struct ArgumentList { - | ----------------------------------- doesn't satisfy `_: Encode` - | - = note: the following trait bounds were not satisfied: - `ArgumentList, ArgumentList>: Encode` +error[E0599]: the method `try_fire` exists for struct `CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied + --> tests/ui/trait_def/fail/message_input_non_codec.rs:5:5 + | +5 | #[ink(message)] + | ^ method cannot be called on `CallBuilder>, Set, ArgumentList>>>, Set>>` due to unsatisfied trait bounds + | + ::: $WORKSPACE/crates/env/src/call/execution_input.rs + | + | pub struct ArgumentList { + | ----------------------------------- doesn't satisfy `_: Encode` + | + = note: the following trait bounds were not satisfied: + `ArgumentList, ArgumentList>: Encode` diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index 2c487dcca6f..2cbf7883085 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -21,7 +21,7 @@ note: required by a bound in `DispatchOutput` | T: scale::Encode + 'static; | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` -error[E0599]: the method `fire` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_fire` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5 | 1 | pub struct NonCodec; diff --git a/examples/delegator/lib.rs b/examples/delegator/lib.rs index 074ac5cae7b..69f5c5ab354 100644 --- a/examples/delegator/lib.rs +++ b/examples/delegator/lib.rs @@ -63,29 +63,17 @@ mod delegator { .endowment(total_balance / 4) .code_hash(accumulator_code_hash) .salt_bytes(salt) - .instantiate() - .unwrap_or_else(|error| { - panic!( - "failed at instantiating the Accumulator contract: {:?}", - error - ) - }); + .instantiate(); let adder = AdderRef::new(accumulator.clone()) .endowment(total_balance / 4) .code_hash(adder_code_hash) .salt_bytes(salt) - .instantiate() - .unwrap_or_else(|error| { - panic!("failed at instantiating the Adder contract: {:?}", error) - }); + .instantiate(); let subber = SubberRef::new(accumulator.clone()) .endowment(total_balance / 4) .code_hash(subber_code_hash) .salt_bytes(salt) - .instantiate() - .unwrap_or_else(|error| { - panic!("failed at instantiating the Subber contract: {:?}", error) - }); + .instantiate(); Self { which: Which::Adder, accumulator, diff --git a/examples/erc1155/lib.rs b/examples/erc1155/lib.rs index 55f86b16f5a..0f86d98d48b 100644 --- a/examples/erc1155/lib.rs +++ b/examples/erc1155/lib.rs @@ -377,17 +377,19 @@ mod erc1155 { ) .returns::>() .params() - .invoke(); + .try_invoke(); match result { Ok(v) => { ink::env::debug_println!( "Received return value \"{:?}\" from contract {:?}", - v, + v.clone().expect( + "Call should be valid, don't expect a `LangError`." + ), from ); assert_eq!( - v, + v.clone().expect("Call should be valid, don't expect a `LangError`."), &ON_ERC_1155_RECEIVED_SELECTOR[..], "The recipient contract at {:?} does not accept token transfers.\n Expected: {:?}, Got {:?}", to, ON_ERC_1155_RECEIVED_SELECTOR, v diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index 0a3de1e94ef..454ecdbe828 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -72,7 +72,6 @@ mod call_builder { .exec_input(ExecutionInput::new(Selector::new(selector))) .returns::<()>() .fire() - .expect("Error from the Contracts pallet.") } #[ink(message)] @@ -94,7 +93,7 @@ mod call_builder { .exec_input(ExecutionInput::new(Selector::new(selector)).push_arg(init_value)) .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) .params() - .instantiate(); + .try_instantiate(); // NOTE: Right now we can't handle any `LangError` from `instantiate`, we can only tell // that our contract reverted (i.e we see error from the Contracts pallet). diff --git a/examples/lang-err-integration-tests/contract-ref/lib.rs b/examples/lang-err-integration-tests/contract-ref/lib.rs index 64074d14faa..bd6e6108e1f 100755 --- a/examples/lang-err-integration-tests/contract-ref/lib.rs +++ b/examples/lang-err-integration-tests/contract-ref/lib.rs @@ -17,10 +17,7 @@ mod contract_ref { .endowment(0) .code_hash(flipper_code_hash) .salt_bytes(salt) - .instantiate() - .unwrap_or_else(|error| { - panic!("failed at instantiating the Flipper contract: {:?}", error) - }); + .instantiate(); Self { flipper } } diff --git a/examples/multisig/lib.rs b/examples/multisig/lib.rs index 16a35430528..42fa16593df 100755 --- a/examples/multisig/lib.rs +++ b/examples/multisig/lib.rs @@ -348,8 +348,7 @@ mod multisig { /// .push_arg(&transaction_candidate) /// ) /// .returns::<(u32, ConfirmationStatus)>() - /// .fire() - /// .expect("submit_transaction won't panic"); + /// .fire(); /// /// // Wait until all required owners have confirmed and then execute the transaction /// // @@ -362,8 +361,7 @@ mod multisig { /// .push_arg(&id) /// ) /// .returns::<()>() - /// .fire() - /// .expect("invoke_transaction won't panic"); + /// .fire(); /// ``` #[ink(message)] pub fn add_owner(&mut self, new_owner: AccountId) { @@ -549,8 +547,13 @@ mod multisig { ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) .returns::<()>() - .fire() - .map_err(|_| Error::TransactionFailed); + .try_fire(); + + let result = match result { + Ok(Ok(_)) => Ok(()), + _ => Err(Error::TransactionFailed), + }; + self.env().emit_event(Execution { transaction: trans_id, result: result.map(|_| None), @@ -582,8 +585,13 @@ mod multisig { ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) .returns::>() - .fire() - .map_err(|_| Error::TransactionFailed); + .try_fire(); + + let result = match result { + Ok(Ok(v)) => Ok(v), + _ => Err(Error::TransactionFailed), + }; + self.env().emit_event(Execution { transaction: trans_id, result: result.clone().map(Some), diff --git a/examples/upgradeable-contracts/forward-calls/lib.rs b/examples/upgradeable-contracts/forward-calls/lib.rs index 09a47cd3996..b4b073a512b 100644 --- a/examples/upgradeable-contracts/forward-calls/lib.rs +++ b/examples/upgradeable-contracts/forward-calls/lib.rs @@ -81,11 +81,17 @@ pub mod proxy { .set_forward_input(true) .set_tail_call(true), ) - .fire() - .unwrap_or_else(|err| { + .try_fire() + .unwrap_or_else(|env_err| { panic!( "cross-contract call to {:?} failed due to {:?}", - self.forward_to, err + self.forward_to, env_err + ) + }) + .unwrap_or_else(|lang_err| { + panic!( + "cross-contract call to {:?} failed due to {:?}", + self.forward_to, lang_err ) }); unreachable!( From 72f7883592e8988b3d67111a3d922c591f283966 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 20 Jan 2023 08:24:46 -0800 Subject: [PATCH 12/18] Rename `CallBuilder::fire()` method to `invoke()` (#1604) * Rename `fire()` method to `invoke()` This keeps this more consistent since we're wrapping a few methods with `invoke` in the name anyways. * Update `CHANGELOG` * Use actual PR number --- CHANGELOG.md | 14 +++++--- crates/env/src/call/call_builder.rs | 36 +++++++++---------- .../generator/as_dependency/contract_ref.rs | 2 +- .../src/generator/trait_def/call_forwarder.rs | 2 +- .../fail/message-input-non-codec.stderr | 2 +- .../fail/message-returns-non-codec.stderr | 2 +- .../fail/message_input_non_codec.stderr | 2 +- .../fail/message_output_non_codec.stderr | 2 +- .../call-builder/lib.rs | 12 +++---- examples/multisig/lib.rs | 8 ++--- .../forward-calls/lib.rs | 2 +- 11 files changed, 45 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e447c852d73..470a2c77f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add E2E testing framework MVP ‒ [#1395](https://github.com/paritytech/ink/pull/1395) - 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) ### Breaking Changes -With the addition of [#1602](https://github.com/paritytech/ink/pull/1602), -the `CallBuilder::fire()`, `CallParams::invoke()`, and `CreateBuilder::instantiate()` -methods now unwrap the `Result` from `pallet-contracts` under the hood. +With this release there are two breaking changes related to the `CallBuilder` and +`CreateBuilder`. -If you wish to handle the error use the new `try_` variants of those methods instead. +1. The `invoke()` methods now unwrap the `Result` from `pallet-contracts` under the hood + ([#1602](https://github.com/paritytech/ink/pull/1602)) +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. ## Version 4.0.0-beta diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index e88ec37df1a..01c990cde74 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -214,7 +214,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::<()>() -/// .fire(); +/// .invoke(); /// ``` /// /// ## Example 2: With Return Value @@ -249,7 +249,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() -/// .fire(); +/// .invoke(); /// ``` /// /// ## Example 3: Delegate call @@ -276,7 +276,7 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .returns::() -/// .fire(); +/// .invoke(); /// ``` /// /// # Handling `LangError`s @@ -284,8 +284,8 @@ where /// It is also important to note that there are certain types of errors which can happen during /// cross-contract calls which can be handled know as [`LangError`][`ink_primitives::LangError`]. /// -/// If you want to handle these errors use the [`CallBuilder::try_fire`] methods instead of the -/// [`CallBuilder::fire`] ones. +/// If you want to handle these errors use the [`CallBuilder::try_invoke`] methods instead of the +/// [`CallBuilder::invoke`] ones. /// /// **Note:** The shown examples panic because there is currently no cross-calling /// support in the off-chain testing environment. However, this code @@ -309,7 +309,7 @@ where /// .gas_limit(5000) /// .transferred_value(10), /// ) -/// .try_fire() +/// .try_invoke() /// .expect("Got an error from the Contract's pallet."); /// /// match call_result { @@ -681,8 +681,8 @@ where /// /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those - /// use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) { + /// use the [`try_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) { self.params().invoke() } @@ -692,7 +692,7 @@ where /// /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner /// [`ink_primitives::LangError`], both of which can be handled by the caller. - pub fn try_fire(self) -> Result, Error> { + pub fn try_invoke(self) -> Result, Error> { self.params().try_invoke() } } @@ -712,8 +712,8 @@ where /// # Panics /// /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] - /// If you want to handle those use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) { + /// If you want to handle those use the [`try_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) { self.params().invoke() } @@ -722,7 +722,7 @@ where /// # Note /// /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the caller. - pub fn try_fire(self) -> Result<(), Error> { + pub fn try_invoke(self) -> Result<(), Error> { self.params().try_invoke() } } @@ -740,8 +740,8 @@ where /// /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those - /// use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) -> R { + /// use the [`try_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) -> R { self.params().invoke() } @@ -751,7 +751,7 @@ where /// /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner /// [`ink_primitives::LangError`], both of which can be handled by the caller. - pub fn try_fire(self) -> Result, Error> { + pub fn try_invoke(self) -> Result, Error> { self.params().try_invoke() } } @@ -768,8 +768,8 @@ where /// # Panics /// /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] - /// If you want to handle those use the [`try_fire`][`CallBuilder::try_fire`] method instead. - pub fn fire(self) -> R { + /// If you want to handle those use the [`try_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) -> R { self.params().invoke() } @@ -778,7 +778,7 @@ where /// # Note /// /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the caller. - pub fn try_fire(self) -> Result { + pub fn try_invoke(self) -> Result { self.params().try_invoke() } } diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index 2cf5fe71b26..4c03e90e6f1 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -370,7 +370,7 @@ impl ContractRef<'_> { ) -> #wrapped_output_type { ::#call_operator(self) .#message_ident( #( #input_bindings ),* ) - .try_fire() + .try_invoke() .unwrap_or_else(|error| ::core::panic!( "encountered error while calling {}::{}: {:?}", ::core::stringify!(#storage_ident), diff --git a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs index 46017c5dff2..8fd6fc85995 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs @@ -355,7 +355,7 @@ impl CallForwarder<'_> { , #input_bindings )* ) - .try_fire() + .try_invoke() .unwrap_or_else(|env_err| ::core::panic!("{}: {:?}", #panic_str, env_err)) .unwrap_or_else(|lang_err| ::core::panic!("{}: {:?}", #panic_str, lang_err)) } diff --git a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr index a083a628f30..92f73a26387 100644 --- a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr @@ -53,7 +53,7 @@ note: required by a bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_invoke` exists for struct `ink::ink_env::call::CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-input-non-codec.rs:16:9 | 16 | pub fn message(&self, _input: NonCodecType) {} diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index 1bdb9e94006..8b9240b02c3 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -34,7 +34,7 @@ note: required by a bound in `return_value` | R: scale::Encode, | ^^^^^^^^^^^^^ required by this bound in `return_value` -error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_invoke` exists for struct `ink::ink_env::call::CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:16:9 | 4 | pub struct NonCodecType; diff --git a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr index a4a79052510..eafe57d892a 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr @@ -41,7 +41,7 @@ note: required by a bound in `ExecutionInput::>::push_arg` -error[E0599]: the method `try_fire` exists for struct `CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_invoke` exists for struct `CallBuilder>, Set, ArgumentList>>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_input_non_codec.rs:5:5 | 5 | #[ink(message)] diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index 2cbf7883085..46e1e66dc6d 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -21,7 +21,7 @@ note: required by a bound in `DispatchOutput` | T: scale::Encode + 'static; | ^^^^^^^^^^^^^ required by this bound in `DispatchOutput` -error[E0599]: the method `try_fire` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied +error[E0599]: the method `try_invoke` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5 | 1 | pub struct NonCodec; diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index 454ecdbe828..2099ebe7e40 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -44,7 +44,7 @@ mod call_builder { .call_type(Call::new().callee(address)) .exec_input(ExecutionInput::new(Selector::new(selector))) .returns::<()>() - .try_fire() + .try_invoke() .expect("Error from the Contracts pallet."); match result { @@ -64,14 +64,14 @@ mod call_builder { /// This message does not allow the caller to handle any `LangErrors`, for that use the /// `call` message instead. #[ink(message)] - pub fn fire(&mut self, address: AccountId, selector: [u8; 4]) { + pub fn invoke(&mut self, address: AccountId, selector: [u8; 4]) { use ink::env::call::build_call; build_call::() .call_type(Call::new().callee(address)) .exec_input(ExecutionInput::new(Selector::new(selector))) .returns::<()>() - .fire() + .invoke() } #[ink(message)] @@ -173,7 +173,7 @@ mod call_builder { } #[ink_e2e::test(additional_contracts = "../integration-flipper/Cargo.toml")] - async fn e2e_invalid_message_selector_panics_on_fire( + async fn e2e_invalid_message_selector_panics_on_invoke( mut client: ink_e2e::Client, ) -> E2EResult<()> { let constructor = CallBuilderTestRef::new(); @@ -196,11 +196,11 @@ mod call_builder { .expect("instantiate `flipper` failed") .account_id; - // Since `LangError`s can't be handled by the `CallBuilder::fire()` method we expect + // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method we expect // this to panic. let invalid_selector = [0x00, 0x00, 0x00, 0x00]; let call = build_message::(contract_acc_id) - .call(|contract| contract.fire(flipper_acc_id, invalid_selector)); + .call(|contract| contract.invoke(flipper_acc_id, invalid_selector)); let call_result = client.call(&ink_e2e::ferdie(), call, 0, None).await; assert!(call_result.is_err()); diff --git a/examples/multisig/lib.rs b/examples/multisig/lib.rs index 42fa16593df..ea3b814e273 100755 --- a/examples/multisig/lib.rs +++ b/examples/multisig/lib.rs @@ -348,7 +348,7 @@ mod multisig { /// .push_arg(&transaction_candidate) /// ) /// .returns::<(u32, ConfirmationStatus)>() - /// .fire(); + /// .invoke(); /// /// // Wait until all required owners have confirmed and then execute the transaction /// // @@ -361,7 +361,7 @@ mod multisig { /// .push_arg(&id) /// ) /// .returns::<()>() - /// .fire(); + /// .invoke(); /// ``` #[ink(message)] pub fn add_owner(&mut self, new_owner: AccountId) { @@ -547,7 +547,7 @@ mod multisig { ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) .returns::<()>() - .try_fire(); + .try_invoke(); let result = match result { Ok(Ok(_)) => Ok(()), @@ -585,7 +585,7 @@ mod multisig { ExecutionInput::new(t.selector.into()).push_arg(CallInput(&t.input)), ) .returns::>() - .try_fire(); + .try_invoke(); let result = match result { Ok(Ok(v)) => Ok(v), diff --git a/examples/upgradeable-contracts/forward-calls/lib.rs b/examples/upgradeable-contracts/forward-calls/lib.rs index b4b073a512b..51c4144cc06 100644 --- a/examples/upgradeable-contracts/forward-calls/lib.rs +++ b/examples/upgradeable-contracts/forward-calls/lib.rs @@ -81,7 +81,7 @@ pub mod proxy { .set_forward_input(true) .set_tail_call(true), ) - .try_fire() + .try_invoke() .unwrap_or_else(|env_err| { panic!( "cross-contract call to {:?} failed due to {:?}", From 78f70db8423a859748804c5f99cc560fd16c708d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Sat, 21 Jan 2023 18:24:10 +0100 Subject: [PATCH 13/18] CI: Add E2E crates to `.gitlab-ci.yml` (#1607) * Add E2E crates to `.gitlab-ci.yml` * Fix clippy complaints --- .gitlab-ci.yml | 4 +++- crates/e2e/src/client.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a77ca824997..abd73be748e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ variables: # CI_IMAGE is changed to "-:staging" when the CI image gets rebuilt # read more https://github.com/paritytech/scripts/pull/244 CI_IMAGE: "paritytech/ink-ci-linux:production" - PURELY_STD_CRATES: "ink/codegen metadata engine" + PURELY_STD_CRATES: "ink/codegen metadata engine e2e e2e/macro" ALSO_WASM_CRATES: "env storage storage/traits allocator prelude primitives ink ink/macro ink/ir" ALL_CRATES: "${PURELY_STD_CRATES} ${ALSO_WASM_CRATES}" DELEGATOR_SUBCONTRACTS: "accumulator adder subber" @@ -311,6 +311,8 @@ docs: - cargo doc --no-deps --all-features -p ink_ir - cargo doc --no-deps --all-features -p ink_codegen - cargo doc --no-deps --all-features -p ink_metadata + - cargo doc --no-deps --all-features -p ink_e2e + - cargo doc --no-deps --all-features -p ink_e2e_macro - mv ${CARGO_TARGET_DIR}/doc ./crate-docs # FIXME: remove me after CI image gets nonroot - chown -R nonroot:nonroot ./crate-docs diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index 4860bbf75cb..d6bc75a80b1 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -325,7 +325,7 @@ where .into_iter() .map(|path| { let path = Path::new(path); - let contract = ContractMetadata::load(&path).unwrap_or_else(|err| { + let contract = ContractMetadata::load(path).unwrap_or_else(|err| { panic!( "Error loading contract metadata {}: {:?}", path.display(), @@ -706,7 +706,7 @@ where }); let account_data = get_composite_field_value(&account, "data")?; - let balance = get_composite_field_value(&account_data, "free")?; + let balance = get_composite_field_value(account_data, "free")?; let balance = balance.as_u128().ok_or_else(|| { Error::Balance(format!("{:?} should convert to u128", balance)) })?; From ed468b28f057b631933740b794bde9d076b9c149 Mon Sep 17 00:00:00 2001 From: Kurtsley <73447098+Kurtsley@users.noreply.github.com> Date: Mon, 23 Jan 2023 03:44:19 -0600 Subject: [PATCH 14/18] chore: add minimum rust version to the ink crate, refs #1606 (#1609) --- crates/ink/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ink/Cargo.toml b/crates/ink/Cargo.toml index 11f6db3d823..d48ae0dc43e 100644 --- a/crates/ink/Cargo.toml +++ b/crates/ink/Cargo.toml @@ -3,6 +3,7 @@ name = "ink" version = "4.0.0-beta" authors = ["Parity Technologies ", "Robin Freyler "] edition = "2021" +rust-version = "1.63" license = "Apache-2.0" readme = "README.md" From cdfb7fe3099ad80c1aa4c2b6d847edf51dfdc271 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 23 Jan 2023 14:02:05 -0800 Subject: [PATCH 15/18] Handle `LangError` from instantiate (#1512) * Handle `LangError` from instantiate (fails for success case) This commit lets us grab what's in the output buffer after our call to `instantiate`, however we are unable to succesfully decode the `AccountId` from the success case. * Change generic in `CreateBuilder` to be more consistent * Remove extra generic parameter I accidently introduced this not knowing that the generic `C` was for the return type * Remove generic return type parameter from `CreateBuidler` codegen * Hardcode assumption that `instantiate` returns a `ConstructorResult` * Update `CreateBuilder` codegen to just return `Self` * Remove generic usage to fix formatting * Unwrap `ConstructorResult` in `contract-ref` E2E test * Clean up some comments * Bring back the assumption that we expect an `AccountId` Is supposed to help give better error messages if somebody uses a wrong type with the builder * Remove unused method * Update doc tests for new builder pattern * Clean up some comments * Fix Clippy warning * Fix typo * Add `try_instantiate` method to `CreateBuilder` * Remove unneeded `unwrap` * Remove debug logging * Update doc test * Fix some typos Co-authored-by: Andrew Jones * Mention panicking behaviour of `instantiate` methods * Improve error messages from wrong `returns()` type * Actually check return values from `call_instantiate` * Add test showing a reverting constructor with `Ok` in error buffer * Check that we're only returning `LangError`s if the contract reverted * Clean up the manual encoding test a bit * Add test for constructors which return a contract level error * Add `CreateBuilder` message which calls a fallible constructor * Add test which calls falliable constructor for success case * Get verbose `instantiate_contract_with_result` decoding past typechecker * Add `try_instantiate_with_result` to `CreateBuilder` * Clean up decoding logic for output from `seal_instatiate` * Small cleanups in `call-builder` E2E tests * RustFmt `env_access` * Remove unused import * Flip decoding logic so that it's more strict initially Otherwise we may end up decoding a `Result` too eagerly and end up in a wrong branch. * Add test which revert a fallible constructor * Remove note about removing `assert` statement We still need this to prevent someone from manually encoding an `Ok` value into the return buffer. * Check return value from fallible constructor tests * Update E2E Builder typedef to match changes * Update E2E test for new call syntax * Use `selector_bytes!` macro in more places * Change order to accounts used in tests The tests started failing due to nonce issues, re-ordering the accounts seems to help with that. * Update function names to use `fallible` * Add note about docs * Update `ContractRef` codegen to use fallible constructor return types * Stop returning an `AccountId` directly from `CreateBuilder::try_instantiate_fallible` This matches the behaviour of the other intantiate methods * Add panicking version of `try_instantiate_fallible` * Add test for using fallible constructors through ContractRefs * Add test for instantiation failure too * Add `instantiate_fallible` to `CreateParams` * Add a couple of missing docs * Convert `call-builder` test return type to `AccountId` * Extract reverted fallible constructor fn for testing * Fmt * Add fallible_constructor_reverted_lang_error FAILs * Rename tests * Add test for a decode error * Rename some tests * Make `Result` types more explicit * Add another test * Clean up decoding match statement * Andrew was right * Small cleanups to naming and imports * Couple more import and comment fixes * Use decode trait method directly * Fix `call-builder` E2E tests This now accounts for the better error handling in the `CalleeReverted` case * Remove leading colons from non-codegen contexts * Add doc test for `instantiate_fallible_contract` * Add doc test to `build_create` function * Remove leftover trait bound We can't use this with fallible constructors * Remove a few more leading colons * Panic in case where we get `Ok` encoded into error buffer * Add some links to env docs * Add more docs to `call-builder` E2E tests * Use correct path in `call-builder` docs * Use return_value() method in e2e test * Fix `call-builder` E2E test compilation * Fix `contract-ref` E2E test compilation * Fix some of the comment links * Unwrap errors from default `instantiate_fallible` codepath * Fix `contract-ref` E2E test * Wrap long line * Remove TODO * Fix instatiation doc test Co-authored-by: Andrew Jones --- crates/e2e/src/builders.rs | 2 +- crates/env/src/api.rs | 48 ++- crates/env/src/backend.rs | 26 +- crates/env/src/call/call_builder.rs | 11 +- crates/env/src/call/create_builder.rs | 282 +++++++++++--- crates/env/src/engine/mod.rs | 120 +++++- crates/env/src/engine/off_chain/impls.rs | 28 +- crates/env/src/engine/on_chain/impls.rs | 88 ++++- .../generator/as_dependency/contract_ref.rs | 9 +- crates/ink/src/env_access.rs | 120 +++++- .../call-builder/lib.rs | 361 +++++++++++++++--- .../constructors-return-value/lib.rs | 27 +- .../contract-ref/lib.rs | 85 +++++ .../integration-flipper/lib.rs | 15 + 14 files changed, 1089 insertions(+), 133 deletions(-) diff --git a/crates/e2e/src/builders.rs b/crates/e2e/src/builders.rs index 7c3f9b871f3..1ef93fba5a2 100644 --- a/crates/e2e/src/builders.rs +++ b/crates/e2e/src/builders.rs @@ -39,7 +39,7 @@ pub type CreateBuilderPartial = CreateBuilder< Unset<::Balance>, Set>, Unset, - R, + Set>, >; /// Get the encoded constructor arguments from the partially initialized `CreateBuilder` diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 0683fcf4b09..90c1294da19 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -311,7 +311,40 @@ where /// # Note /// /// This is a low level way to instantiate another smart contract. -/// Prefer to use the ink! guided and type safe approach to using this. +/// +/// Prefer to use methods on a `ContractRef` or the [`CreateBuilder`](`crate::call::CreateBuilder`) +/// through [`build_create`](`crate::call::build_create`) instead. +/// +/// # Errors +/// +/// - If the code hash is invalid. +/// - If the arguments passed to the instantiation process are invalid. +/// - If the instantiation process traps. +/// - If the instantiation process runs out of gas. +/// - If given insufficient endowment. +/// - If the returned account ID failed to decode properly. +pub fn instantiate_contract( + params: &CreateParams, +) -> Result> +where + E: Environment, + Args: scale::Encode, + Salt: AsRef<[u8]>, +{ + ::on_instance(|instance| { + TypedEnvBackend::instantiate_contract::(instance, params) + }) +} + +/// Attempts to instantiate another contract, returning the instantiation result back to the +/// caller. +/// +/// # Note +/// +/// This is a low level way to instantiate another smart contract. +/// +/// Prefer to use methods on a `ContractRef` or the [`CreateBuilder`](`crate::call::CreateBuilder`) +/// through [`build_create`](`crate::call::build_create`) instead. /// /// # Errors /// @@ -321,16 +354,21 @@ where /// - If the instantiation process runs out of gas. /// - If given insufficient endowment. /// - If the returned account ID failed to decode properly. -pub fn instantiate_contract( - params: &CreateParams, -) -> Result +pub fn instantiate_fallible_contract( + params: &CreateParams, +) -> Result< + ink_primitives::ConstructorResult>, +> where E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, + ContractError: scale::Decode, { ::on_instance(|instance| { - TypedEnvBackend::instantiate_contract::(instance, params) + TypedEnvBackend::instantiate_fallible_contract::( + instance, params, + ) }) } diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 39beb23cdee..99b2024f39d 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -439,15 +439,35 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`] - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result> where E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>; + /// Attempts to instantiate another contract, returning the instantiation result back to the + /// caller. + /// + /// # Note + /// + /// For more details visit: [`instantiate_fallible_contract`][`crate::instantiate_fallible_contract`] + fn instantiate_fallible_contract( + &mut self, + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + core::result::Result, + >, + > + where + E: Environment, + Args: scale::Encode, + Salt: AsRef<[u8]>, + ContractError: scale::Decode; + /// Terminates a smart contract. /// /// # Note diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index 01c990cde74..ae6ab49edba 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -132,7 +132,8 @@ where /// # Note /// /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner - /// [`ink_primitives::LangError`], both of which can be handled by the caller. + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. pub fn try_invoke(&self) -> Result, crate::Error> { crate::invoke_contract(self) } @@ -150,7 +151,7 @@ where /// /// # Panics /// - /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] If you want to + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you want to /// handle those use the [`try_invoke`][`CallParams::try_invoke`] method instead. pub fn invoke(&self) -> R { crate::invoke_contract_delegate(self).unwrap_or_else(|env_error| { @@ -691,7 +692,8 @@ where /// # Note /// /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner - /// [`ink_primitives::LangError`], both of which can be handled by the caller. + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. pub fn try_invoke(self) -> Result, Error> { self.params().try_invoke() } @@ -750,7 +752,8 @@ where /// # Note /// /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner - /// [`ink_primitives::LangError`], both of which can be handled by the caller. + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. pub fn try_invoke(self) -> Result, Error> { self.params().try_invoke() } diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 527ede79dcb..6dd80724439 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -122,31 +122,93 @@ where /// /// # Panics /// - /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you want to - /// handle those use the [`try_instantiate`][`CreateParams::try_instantiate`] method instead. + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_instantiate`][`CreateParams::try_instantiate`] method instead. #[inline] pub fn instantiate(&self) -> R { crate::instantiate_contract(self) - .map(FromAccountId::from_account_id) .unwrap_or_else(|env_error| { panic!("Cross-contract instantiation failed with {:?}", env_error) }) + .map(FromAccountId::from_account_id) + .unwrap_or_else(|lang_error| { + panic!( + "Received a `LangError` while instantiating: {:?}", + lang_error + ) + }) } /// Instantiates the contract and returns its account ID back to the caller. /// /// # Note /// - /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be handled by the - /// caller. + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. #[inline] - pub fn try_instantiate(&self) -> Result { - crate::instantiate_contract(self).map(FromAccountId::from_account_id) + pub fn try_instantiate( + &self, + ) -> Result, crate::Error> { + crate::instantiate_contract(self) + .map(|inner| inner.map(FromAccountId::from_account_id)) + } +} + +impl + CreateParams> +where + E: Environment, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: FromAccountId, + ContractError: scale::Decode, +{ + /// Attempts to instantiate the contract, returning the execution result back to the caller. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle + /// those use the [`try_instantiate_fallible`][`CreateParams::try_instantiate_fallible`] method + /// instead. + #[inline] + pub fn instantiate_fallible(&self) -> Result { + crate::instantiate_fallible_contract(self) + .unwrap_or_else(|env_error| { + panic!("Cross-contract instantiation failed with {:?}", env_error) + }) + .unwrap_or_else(|lang_error| { + panic!( + "Received a `LangError` while instantiating: {:?}", + lang_error + ) + }) + .map(FromAccountId::from_account_id) + } + + /// Attempts to instantiate the contract, returning the execution result back to the caller. + /// + /// # Note + /// + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. + #[inline] + pub fn try_instantiate_fallible( + &self, + ) -> Result>, crate::Error> + { + crate::instantiate_fallible_contract(self).map(|constructor_result| { + constructor_result.map(|contract_result| { + contract_result.map(FromAccountId::from_account_id) + }) + }) } } /// Builds up contract instantiations. -pub struct CreateBuilder +pub struct CreateBuilder where E: Environment, { @@ -155,7 +217,7 @@ where endowment: Endowment, exec_input: Args, salt: Salt, - return_type: ReturnType, + return_type: RetType, _phantom: PhantomData E>, } @@ -163,6 +225,12 @@ where /// /// # Example /// +/// **Note:** The shown examples panic because there is currently no cross-calling +/// support in the off-chain testing environment. However, this code +/// should work fine in on-chain environments. +/// +/// ## Example 1: Returns Address of Instantiated Contract +/// /// The below example shows instantiation of contract of type `MyContract`. /// /// The used constructor: @@ -188,7 +256,7 @@ where /// # impl FromAccountId for MyContract { /// # fn from_account_id(account_id: AccountId) -> Self { Self } /// # } -/// let my_contract: MyContract = build_create::() +/// let my_contract: MyContract = build_create::() /// .code_hash(Hash::from([0x42; 32])) /// .gas_limit(4000) /// .endowment(25) @@ -199,26 +267,59 @@ where /// .push_arg(&[0x10u8; 32]) /// ) /// .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) +/// .returns::() /// .params() /// .instantiate(); /// ``` /// -/// **Note:** The shown example panics because there is currently no cross-calling -/// support in the off-chain testing environment. However, this code -/// should work fine in on-chain environments. +/// ## Example 2: Handles Result from Fallible Constructor +/// +/// ```should_panic +/// # use ::ink_env::{ +/// # Environment, +/// # DefaultEnvironment, +/// # call::{build_create, Selector, ExecutionInput, FromAccountId} +/// # }; +/// # type Hash = ::Hash; +/// # type AccountId = ::AccountId; +/// # type Salt = &'static [u8]; +/// # struct MyContract; +/// # impl FromAccountId for MyContract { +/// # fn from_account_id(account_id: AccountId) -> Self { Self } +/// # } +/// # #[derive(scale::Encode, scale::Decode, Debug)] +/// # #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +/// # pub struct ConstructorError; +/// let my_contract: MyContract = build_create::() +/// .code_hash(Hash::from([0x42; 32])) +/// .gas_limit(4000) +/// .endowment(25) +/// .exec_input( +/// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) +/// .push_arg(42) +/// .push_arg(true) +/// .push_arg(&[0x10u8; 32]) +/// ) +/// .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) +/// .returns::>() +/// .params() +/// .instantiate_fallible() +/// .expect("Constructor should've run without errors."); +/// ``` +/// +/// Note the usage of the [`CreateBuilder::instantiate_fallible`] method. #[allow(clippy::type_complexity)] -pub fn build_create() -> CreateBuilder< +pub fn build_create() -> CreateBuilder< E, Unset, Unset, Unset, Unset>, Unset, - R, + Unset>, > where E: Environment, - R: FromAccountId, { CreateBuilder { code_hash: Default::default(), @@ -231,8 +332,8 @@ where } } -impl - CreateBuilder, GasLimit, Endowment, Args, Salt, R> +impl + CreateBuilder, GasLimit, Endowment, Args, Salt, RetType> where E: Environment, { @@ -241,7 +342,7 @@ where pub fn code_hash( self, code_hash: E::Hash, - ) -> CreateBuilder, GasLimit, Endowment, Args, Salt, R> { + ) -> CreateBuilder, GasLimit, Endowment, Args, Salt, RetType> { CreateBuilder { code_hash: Set(code_hash), gas_limit: self.gas_limit, @@ -254,8 +355,8 @@ where } } -impl - CreateBuilder, Endowment, Args, Salt, R> +impl + CreateBuilder, Endowment, Args, Salt, RetType> where E: Environment, { @@ -264,7 +365,7 @@ where pub fn gas_limit( self, gas_limit: u64, - ) -> CreateBuilder, Endowment, Args, Salt, R> { + ) -> CreateBuilder, Endowment, Args, Salt, RetType> { CreateBuilder { code_hash: self.code_hash, gas_limit: Set(gas_limit), @@ -277,8 +378,8 @@ where } } -impl - CreateBuilder, Args, Salt, R> +impl + CreateBuilder, Args, Salt, RetType> where E: Environment, { @@ -287,7 +388,7 @@ where pub fn endowment( self, endowment: E::Balance, - ) -> CreateBuilder, Args, Salt, R> { + ) -> CreateBuilder, Args, Salt, RetType> { CreateBuilder { code_hash: self.code_hash, gas_limit: self.gas_limit, @@ -300,7 +401,7 @@ where } } -impl +impl CreateBuilder< E, CodeHash, @@ -308,7 +409,7 @@ impl Endowment, Unset>, Salt, - R, + RetType, > where E: Environment, @@ -318,8 +419,15 @@ where pub fn exec_input( self, exec_input: ExecutionInput, - ) -> CreateBuilder>, Salt, R> - { + ) -> CreateBuilder< + E, + CodeHash, + GasLimit, + Endowment, + Set>, + Salt, + RetType, + > { CreateBuilder { code_hash: self.code_hash, gas_limit: self.gas_limit, @@ -332,8 +440,8 @@ where } } -impl - CreateBuilder, R> +impl + CreateBuilder, RetType> where E: Environment, { @@ -342,7 +450,7 @@ where pub fn salt_bytes( self, salt: Salt, - ) -> CreateBuilder, R> + ) -> CreateBuilder, RetType> where Salt: AsRef<[u8]>, { @@ -358,7 +466,38 @@ where } } -impl +impl + CreateBuilder>> +where + E: Environment, +{ + /// Sets the type of the returned value upon the execution of the constructor. + /// + /// # Note + /// + /// Constructors are not able to return arbitrary values. Instead a successful call to a + /// constructor returns the address at which the contract was instantiated. + /// + /// Therefore this must always be a reference (i.e `ContractRef`) to the contract you're trying + /// to instantiate. + #[inline] + pub fn returns( + self, + ) -> CreateBuilder>> + { + CreateBuilder { + code_hash: self.code_hash, + gas_limit: self.gas_limit, + endowment: self.endowment, + exec_input: self.exec_input, + salt: self.salt, + return_type: Set(Default::default()), + _phantom: Default::default(), + } + } +} + +impl CreateBuilder< E, Set, @@ -366,27 +505,27 @@ impl Set, Set>, Set, - R, + Set>, > where E: Environment, GasLimit: Unwrap, { - /// Sets the value transferred upon the execution of the call. + /// Finalizes the create builder, allowing it to instantiate a contract. #[inline] - pub fn params(self) -> CreateParams { + pub fn params(self) -> CreateParams { CreateParams { code_hash: self.code_hash.value(), gas_limit: self.gas_limit.unwrap_or_else(|| 0), endowment: self.endowment.value(), exec_input: self.exec_input.value(), salt_bytes: self.salt.value(), - _return_type: self.return_type, + _return_type: Default::default(), } } } -impl +impl CreateBuilder< E, Set, @@ -394,23 +533,24 @@ impl Set, Set>, Set, - R, + Set>, > where E: Environment, GasLimit: Unwrap, Args: scale::Encode, Salt: AsRef<[u8]>, - R: FromAccountId, + RetType: FromAccountId, { /// Instantiates the contract and returns its account ID back to the caller. /// /// # Panics /// - /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you want to - /// handle those use the [`try_instantiate`][`CreateBuilder::try_instantiate`] method instead. + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_instantiate`][`CreateBuilder::try_instantiate`] method instead. #[inline] - pub fn instantiate(self) -> R { + pub fn instantiate(self) -> RetType { self.params().instantiate() } @@ -418,10 +558,60 @@ where /// /// # Note /// - /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be handled by the - /// caller. + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. #[inline] - pub fn try_instantiate(self) -> Result { + pub fn try_instantiate( + self, + ) -> Result, Error> { self.params().try_instantiate() } } + +impl + CreateBuilder< + E, + Set, + GasLimit, + Set, + Set>, + Set, + Set>>, + > +where + E: Environment, + GasLimit: Unwrap, + Args: scale::Encode, + Salt: AsRef<[u8]>, + RetType: FromAccountId, + ContractError: scale::Decode, +{ + /// Attempts to instantiate the contract, returning the execution result back to the caller. + /// + /// # Panics + /// + /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an + /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those + /// use the [`try_instantiate_fallible`][`CreateParams::try_instantiate_fallible`] method + /// instead. + #[inline] + pub fn instantiate_fallible(self) -> Result { + self.params().instantiate_fallible() + } + + /// Attempts to instantiate the contract, returning the execution result back to the caller. + /// + /// # Note + /// + /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner + /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled + /// by the caller. + #[inline] + pub fn try_instantiate_fallible( + self, + ) -> Result>, Error> + { + self.params().try_instantiate_fallible() + } +} diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 2e74d582e12..440cc5b1032 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::backend::{ - EnvBackend, - TypedEnvBackend, +use crate::{ + backend::{ + EnvBackend, + TypedEnvBackend, + }, + Result as EnvResult, }; use cfg_if::cfg_if; +use ink_primitives::ConstructorResult; pub trait OnInstance: EnvBackend + TypedEnvBackend { fn on_instance(f: F) -> R @@ -37,3 +41,113 @@ cfg_if! { } } } + +// The `Result` type used to represent the programmer defined contract output. +type ContractResult = core::result::Result; + +// We only use this function when 1) compiling to Wasm 2) compiling for tests. +#[cfg_attr(all(feature = "std", not(test)), allow(dead_code))] +pub(crate) fn decode_fallible_constructor_reverted_return_value( + out_return_value: &mut I, +) -> EnvResult>> +where + I: scale::Input, + E: crate::Environment, + ContractError: scale::Decode, +{ + let out: ConstructorResult> = + scale::Decode::decode(out_return_value)?; + + match out { + ConstructorResult::Ok(ContractResult::Ok(())) => { + // Since the contract reverted we don't expect an `Ok` return value from the + // constructor, otherwise we'd be in the `AccountId` decoding branch. + // + // While we could handle this more gracefully, e.g through a `LangError`, we're going to + // be defensive for now and trap. + panic!( + "The callee reverted, but did not encode an error in the output buffer." + ) + } + ConstructorResult::Ok(ContractResult::Err(contract_error)) => { + Ok(ConstructorResult::Ok(ContractResult::Err(contract_error))) + } + ConstructorResult::Err(lang_error) => Ok(ConstructorResult::Err(lang_error)), + } +} + +#[cfg(test)] +mod fallible_constructor_reverted_tests { + use super::*; + use scale::Encode; + + #[derive(scale::Encode, scale::Decode)] + struct ContractError(String); + + fn encode_and_decode_return_value( + return_value: ConstructorResult>, + ) -> EnvResult>> + { + let encoded_return_value = return_value.encode(); + decode_return_value(&mut &encoded_return_value[..]) + } + + fn decode_return_value( + input: &mut I, + ) -> EnvResult>> + { + decode_fallible_constructor_reverted_return_value::< + I, + crate::DefaultEnvironment, + ContractError, + >(input) + } + + #[test] + #[should_panic( + expected = "The callee reverted, but did not encode an error in the output buffer." + )] + fn revert_branch_rejects_valid_output_buffer_with_success_case() { + let return_value = ConstructorResult::Ok(ContractResult::Ok(())); + + let _decoded_result = encode_and_decode_return_value(return_value); + } + + #[test] + fn succesful_dispatch_with_error_from_contract_constructor() { + let return_value = ConstructorResult::Ok(ContractResult::Err(ContractError( + "Contract's constructor failed.".to_owned(), + ))); + + let decoded_result = encode_and_decode_return_value(return_value); + + assert!(matches!( + decoded_result, + EnvResult::Ok(ConstructorResult::Ok(ContractResult::Err(ContractError(_)))) + )) + } + + #[test] + fn dispatch_error_gets_decoded_correctly() { + let return_value = + ConstructorResult::Err(ink_primitives::LangError::CouldNotReadInput); + + let decoded_result = encode_and_decode_return_value(return_value); + + assert!(matches!( + decoded_result, + EnvResult::Ok(ConstructorResult::Err( + ink_primitives::LangError::CouldNotReadInput + )) + )) + } + + #[test] + fn invalid_bytes_in_output_buffer_fail_decoding() { + let invalid_encoded_return_value = vec![69]; + + let decoded_result = decode_return_value(&mut &invalid_encoded_return_value[..]); + + assert!(matches!(decoded_result, Err(crate::Error::Decode(_)))) + } +} diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index a17bdc3a5ff..771d8608054 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -469,10 +469,10 @@ impl TypedEnvBackend for EnvInstance { ) } - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result> where E: Environment, Args: scale::Encode, @@ -486,6 +486,28 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("off-chain environment does not support contract instantiation") } + fn instantiate_fallible_contract( + &mut self, + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + core::result::Result, + >, + > + where + E: Environment, + Args: scale::Encode, + Salt: AsRef<[u8]>, + ContractError: scale::Decode, + { + let _code_hash = params.code_hash(); + let _gas_limit = params.gas_limit(); + let _endowment = params.endowment(); + let _input = params.exec_input(); + let _salt_bytes = params.salt_bytes(); + unimplemented!("off-chain environment does not support contract instantiation") + } + fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! where E: Environment, diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 6f3b7572c04..3b98fa02ec9 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -479,10 +479,10 @@ impl TypedEnvBackend for EnvInstance { } } - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result> where E: Environment, Args: scale::Encode, @@ -499,11 +499,63 @@ impl TypedEnvBackend for EnvInstance { let out_address = &mut scoped.take(1024); let salt = params.salt_bytes().as_ref(); let out_return_value = &mut scoped.take_rest(); - // We currently do nothing with the `out_return_value` buffer. - // This should change in the future but for that we need to add support - // for constructors that may return values. - // This is useful to support fallible constructors for example. - ext::instantiate( + + let instantiate_result = ext::instantiate( + enc_code_hash, + gas_limit, + enc_endowment, + enc_input, + out_address, + out_return_value, + salt, + ); + + match instantiate_result { + Ok(()) => { + let account_id = scale::Decode::decode(&mut &out_address[..])?; + Ok(Ok(account_id)) + } + Err(ext::Error::CalleeReverted) => { + // We don't wrap manually with an extra `Err` like we do in the `Ok` case since the + // buffer already comes back in the form of `Err(LangError)` (assuming it's encoded + // by the ink! codegen and not the contract). + let out = ink_primitives::ConstructorResult::::decode( + &mut &out_return_value[..], + )?; + assert!(out.is_err(), "The callee reverted, but did not encode an error in the output buffer."); + Ok(out) + } + Err(actual_error) => Err(actual_error.into()), + } + } + + fn instantiate_fallible_contract( + &mut self, + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + core::result::Result, + >, + > + where + E: Environment, + Args: scale::Encode, + Salt: AsRef<[u8]>, + ContractError: scale::Decode, + { + let mut scoped = self.scoped_buffer(); + let gas_limit = params.gas_limit(); + let enc_code_hash = scoped.take_encoded(params.code_hash()); + let enc_endowment = scoped.take_encoded(params.endowment()); + let enc_input = scoped.take_encoded(params.exec_input()); + // We support `AccountId` types with an encoding that requires up to + // 1024 bytes. Beyond that limit ink! contracts will trap for now. + // In the default configuration encoded `AccountId` require 32 bytes. + let out_address = &mut scoped.take(1024); + let salt = params.salt_bytes().as_ref(); + let out_return_value = &mut scoped.take_rest(); + + let instantiate_result = ext::instantiate( enc_code_hash, gas_limit, enc_endowment, @@ -511,9 +563,23 @@ impl TypedEnvBackend for EnvInstance { out_address, out_return_value, salt, - )?; - let account_id = scale::Decode::decode(&mut &out_address[..])?; - Ok(account_id) + ); + + match instantiate_result { + Ok(()) => { + let account_id: E::AccountId = + scale::Decode::decode(&mut &out_address[..])?; + Ok(ink_primitives::ConstructorResult::Ok(Ok(account_id))) + } + Err(ext::Error::CalleeReverted) => { + crate::engine::decode_fallible_constructor_reverted_return_value::< + _, + E, + ContractError, + >(&mut &out_return_value[..]) + } + Err(actual_error) => Err(actual_error.into()), + } } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index 4c03e90e6f1..a1242d5cdeb 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -403,6 +403,10 @@ impl ContractRef<'_> { let input_bindings = generator::input_bindings(constructor.inputs()); let input_types = generator::input_types(constructor.inputs()); let arg_list = generator::generate_argument_list(input_types.iter().cloned()); + let ret_type = constructor + .output() + .map(quote::ToTokens::to_token_stream) + .unwrap_or_else(|| quote::quote! { Self }); quote_spanned!(span => #( #attrs )* #[inline] @@ -416,9 +420,9 @@ impl ContractRef<'_> { ::ink::env::call::utils::Unset, ::ink::env::call::utils::Set<::ink::env::call::ExecutionInput<#arg_list>>, ::ink::env::call::utils::Unset<::ink::env::call::state::Salt>, - Self, + ::ink::env::call::utils::Set<::ink::env::call::utils::ReturnType<#ret_type>>, > { - ::ink::env::call::build_create::() + ::ink::env::call::build_create::() .exec_input( ::ink::env::call::ExecutionInput::new( ::ink::env::call::Selector::new([ #( #selector_bytes ),* ]) @@ -427,6 +431,7 @@ impl ContractRef<'_> { .push_arg(#input_bindings) )* ) + .returns::<#ret_type>() } ) } diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index cd691cfb875..59b3ad376b8 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -444,19 +444,28 @@ where /// /// Instantiates another contract. /// #[ink(message)] /// pub fn instantiate_contract(&self) -> AccountId { - /// let create_params = build_create::() + /// let create_params = build_create::() /// .code_hash(Hash::from([0x42; 32])) /// .gas_limit(4000) /// .endowment(25) /// .exec_input( - /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) + /// ExecutionInput::new(Selector::new(ink::selector_bytes!("new"))) /// .push_arg(42) /// .push_arg(true) - /// .push_arg(&[0x10u8; 32]) - /// ) + /// .push_arg(&[0x10u8; 32]), + /// ) /// .salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE]) + /// .returns::() /// .params(); - /// self.env().instantiate_contract(&create_params).unwrap_or_else(|err| panic!("instantiation must succeed: {:?}", err)) + /// self.env() + /// .instantiate_contract(&create_params) + /// .unwrap_or_else(|error| { + /// panic!( + /// "Received an error from the Contracts pallet while instantiating: {:?}", + /// error + /// ) + /// }) + /// .unwrap_or_else(|error| panic!("Received a `LangError` while instatiating: {:?}", error)) /// } /// # /// # } @@ -469,15 +478,108 @@ where /// # Note /// /// For more details visit: [`ink_env::instantiate_contract`] - pub fn instantiate_contract( + pub fn instantiate_contract( + self, + params: &CreateParams, + ) -> Result> + where + Args: scale::Encode, + Salt: AsRef<[u8]>, + { + ink_env::instantiate_contract::(params) + } + + /// Attempts to instantiate a contract, returning the execution result back to the caller. + /// + /// # Example + /// + /// ``` + /// # #[ink::contract] + /// # pub mod my_contract { + /// # // In order for this to actually work with another contract we'd need a way + /// # // to turn the `ink-as-dependency` crate feature on in doctests, which we + /// # // can't do. + /// # // + /// # // Instead we use our own contract's `Ref`, which is fine for this example + /// # // (just need something that implements the `ContractRef` trait). + /// # pub mod other_contract { + /// # pub use super::MyContractRef as OtherContractRef; + /// # pub use super::ConstructorError as OtherConstructorError; + /// # } + /// use ink::env::{ + /// DefaultEnvironment, + /// call::{build_create, Selector, ExecutionInput} + /// }; + /// use other_contract::{OtherContractRef, OtherConstructorError}; + /// + /// # #[derive(scale::Encode, scale::Decode, Debug)] + /// # #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + /// # pub struct ConstructorError; + /// # + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn try_new() -> Result { + /// # Ok(Self {}) + /// # } + /// # + /// /// Attempts to instantiate a contract, returning the `AccountId` back to the caller. + /// #[ink(message)] + /// pub fn instantiate_fallible_contract(&self) -> AccountId { + /// let create_params = build_create::() + /// .code_hash(Hash::from([0x42; 32])) + /// .gas_limit(4000) + /// .endowment(25) + /// .exec_input( + /// ExecutionInput::new(Selector::new(ink::selector_bytes!("try_new"))) + /// .push_arg(42) + /// .push_arg(true) + /// .push_arg(&[0x10u8; 32]), + /// ) + /// .salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE]) + /// .returns::>() + /// .params(); + /// self.env() + /// .instantiate_fallible_contract(&create_params) + /// .unwrap_or_else(|error| { + /// panic!( + /// "Received an error from the Contracts pallet while instantiating: {:?}", + /// error + /// ) + /// }) + /// .unwrap_or_else(|error| panic!("Received a `LangError` while instatiating: {:?}", error)) + /// .unwrap_or_else(|error: ConstructorError| { + /// panic!( + /// "Received a `ConstructorError` while instatiating: {:?}", + /// error + /// ) + /// }) + /// } + /// # + /// # } + /// # } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::instantiate_fallible_contract`] + pub fn instantiate_fallible_contract( self, - params: &CreateParams, - ) -> Result + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + core::result::Result, + >, + > where + E: Environment, Args: scale::Encode, Salt: AsRef<[u8]>, + ContractError: scale::Decode, { - ink_env::instantiate_contract::(params) + ink_env::instantiate_fallible_contract::(params) } /// Invokes a contract message and returns its result. diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index 2099ebe7e40..6b4638d01a1 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -2,8 +2,16 @@ //! //! This contract is used to ensure that the behavior around `LangError`s works as expected. //! -//! It makes use of ink!'s end-to-end testing features, so ensure that you have a node which -//! includes the Contract's pallet running alongside your tests. +//! In particular, it exercises the codepaths that stem from the usage of the +//! [`CallBuilder`](`ink::env::call::CallBuilder`) and [`CreateBuilder`](`ink::env::call::CreateBuilder`) +//! structs. +//! +//! This differs from the codepath used by external tooling, such as `cargo-contract` or the +//! `Contracts-UI` which instead depend on methods from the Contracts pallet which are exposed via +//! RPC. +//! +//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure that you +//! have a node which includes the Contracts pallet running alongside your tests. #![cfg_attr(not(feature = "std"), no_std)] @@ -11,6 +19,8 @@ mod call_builder { use ink::env::{ call::{ + build_call, + build_create, Call, ExecutionInput, Selector, @@ -32,14 +42,15 @@ mod call_builder { /// /// Since we can't use the `CallBuilder` in a test environment directly we need this /// wrapper to test things like crafting calls with invalid selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` directly from a + /// contract message without erroring out ourselves. #[ink(message)] pub fn call( &mut self, address: AccountId, selector: [u8; 4], - ) -> Option<::ink::LangError> { - use ink::env::call::build_call; - + ) -> Option { let result = build_call::() .call_type(Call::new().callee(address)) .exec_input(ExecutionInput::new(Selector::new(selector))) @@ -74,32 +85,80 @@ mod call_builder { .invoke() } + /// Instantiate a contract using the `CreateBuilder`. + /// + /// Since we can't use the `CreateBuilder` in a test environment directly we need this + /// wrapper to test things like crafting calls with invalid selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` directly from a + /// contract message without erroring out ourselves. #[ink(message)] pub fn call_instantiate( &mut self, code_hash: Hash, selector: [u8; 4], init_value: bool, - ) -> Option { - use ink::env::call::build_create; - - let result = build_create::< - DefaultEnvironment, - constructors_return_value::ConstructorsReturnValueRef, - >() - .code_hash(code_hash) - .gas_limit(0) - .endowment(0) - .exec_input(ExecutionInput::new(Selector::new(selector)).push_arg(init_value)) - .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) - .params() - .try_instantiate(); - - // NOTE: Right now we can't handle any `LangError` from `instantiate`, we can only tell - // that our contract reverted (i.e we see error from the Contracts pallet). - // - // This will be fixed with #1512. - result.ok().map(|id| ink::ToAccountId::to_account_id(&id)) + ) -> Option { + let result = build_create::() + .code_hash(code_hash) + .gas_limit(0) + .endowment(0) + .exec_input( + ExecutionInput::new(Selector::new(selector)).push_arg(init_value), + ) + .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) + .returns::() + .params() + .try_instantiate() + .expect("Error from the Contracts pallet."); + + match result { + Ok(_) => None, + Err(e @ ink::LangError::CouldNotReadInput) => Some(e), + Err(_) => { + unimplemented!("No other `LangError` variants exist at the moment.") + } + } + } + + /// Attempt to instantiate a contract using the `CreateBuilder`. + /// + /// Since we can't use the `CreateBuilder` in a test environment directly we need this + /// wrapper to test things like crafting calls with invalid selectors. + /// + /// We also wrap the output in an `Option` since we can't return a `Result` directly from a + /// contract message without erroring out ourselves. + #[ink(message)] + pub fn call_instantiate_fallible( + &mut self, + code_hash: Hash, + selector: [u8; 4], + init_value: bool, + ) -> Option< + Result< + Result, + ink::LangError, + >, + > { + let lang_result = build_create::() + .code_hash(code_hash) + .gas_limit(0) + .endowment(0) + .exec_input( + ExecutionInput::new(Selector::new(selector)).push_arg(init_value), + ) + .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) + .returns::>() + .params() + .try_instantiate_fallible() + .expect("Error from the Contracts pallet."); + + Some(lang_result.map(|contract_result| { + contract_result.map(|inner| ink::ToAccountId::to_account_id(&inner)) + })) } } @@ -119,7 +178,7 @@ mod call_builder { ) -> E2EResult<()> { let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::charlie(), constructor, 0, None) + .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) .await .expect("instantiate failed") .account_id; @@ -128,7 +187,7 @@ mod call_builder { let flipper_acc_id = client .instantiate( "integration_flipper", - &ink_e2e::charlie(), + &ink_e2e::alice(), flipper_constructor, 0, None, @@ -140,16 +199,16 @@ mod call_builder { let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::charlie(), flipper_get, 0, None) + .call(&ink_e2e::alice(), flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let initial_value = get_call_result.return_value(); - let invalid_selector = [0x00, 0x00, 0x00, 0x00]; + let selector = ink::selector_bytes!("invalid_selector"); let call = build_message::(contract_acc_id) - .call(|contract| contract.call(flipper_acc_id, invalid_selector)); + .call(|contract| contract.call(flipper_acc_id, selector)); let call_result = client - .call(&ink_e2e::charlie(), call, 0, None) + .call(&ink_e2e::alice(), call, 0, None) .await .expect("Calling `call_builder::call` failed"); @@ -157,13 +216,13 @@ mod call_builder { assert!(matches!( flipper_result, - Some(::ink::LangError::CouldNotReadInput) + Some(ink::LangError::CouldNotReadInput) )); let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::charlie(), flipper_get, 0, None) + .call(&ink_e2e::alice(), flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let flipped_value = get_call_result.return_value(); @@ -222,30 +281,31 @@ mod call_builder { ) -> E2EResult<()> { let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::dave(), constructor, 0, None) + .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::dave(), None) + .upload("constructors_return_value", &ink_e2e::bob(), None) .await .expect("upload `constructors_return_value` failed") .code_hash; - let new_selector = [0x9B, 0xAE, 0x9D, 0x5E]; + let selector = ink::selector_bytes!("new"); + let init_value = true; let call = build_message::(contract_acc_id).call(|contract| { - contract.call_instantiate(code_hash, new_selector, true) + contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::dave(), call, 0, None) + .call(&ink_e2e::bob(), call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); assert!( - call_result.is_some(), + call_result.is_none(), "Call using valid selector failed, when it should've succeeded." ); @@ -258,34 +318,245 @@ mod call_builder { ) -> E2EResult<()> { let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::eve(), constructor, 0, None) + .instantiate("call_builder", &ink_e2e::charlie(), constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::eve(), None) + .upload("constructors_return_value", &ink_e2e::charlie(), None) .await .expect("upload `constructors_return_value` failed") .code_hash; - let invalid_selector = [0x00, 0x00, 0x00, 0x00]; + let selector = ink::selector_bytes!("invalid_selector"); + let init_value = true; let call = build_message::(contract_acc_id).call(|contract| { - contract.call_instantiate(code_hash, invalid_selector, true) + contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::eve(), call, 0, None) + .call(&ink_e2e::charlie(), call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); assert!( - call_result.is_none(), + matches!(call_result, Some(ink::LangError::CouldNotReadInput)), "Call using invalid selector succeeded, when it should've failed." ); Ok(()) } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_with_infallible_revert_constructor_encodes_ok( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::dave(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload("constructors_return_value", &ink_e2e::dave(), None) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("revert_new"); + let init_value = false; + let call = + build_message::(contract_acc_id).call(|contract| { + contract.call_instantiate(code_hash, selector, init_value) + }); + + let call_result = client.call(&mut ink_e2e::dave(), call, 0, None).await; + assert!( + call_result.is_err(), + "Call execution should've failed, but didn't." + ); + let contains_err_msg = match call_result.unwrap_err() { + ink_e2e::Error::CallDryRun(dry_run) => { + String::from_utf8_lossy(&dry_run.debug_message) + .contains("The callee reverted, but did not encode an error in the output buffer.") + } + _ => false, + }; + assert!( + contains_err_msg, + "Call execution failed for an unexpected reason." + ); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_can_handle_fallible_constructor_success( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::eve(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload("constructors_return_value", &ink_e2e::eve(), None) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_new"); + let init_value = true; + let call = + build_message::(contract_acc_id).call(|contract| { + contract.call_instantiate_fallible(code_hash, selector, init_value) + }); + let call_result = client + .call(&mut ink_e2e::eve(), call, 0, None) + .await + .expect("Calling `call_builder::call_instantiate_fallible` failed") + .return_value(); + + assert!( + matches!(call_result, Some(Ok(_))), + "Call to falliable constructor failed, when it should have succeeded." + ); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_can_handle_fallible_constructor_error( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload("constructors_return_value", &ink_e2e::ferdie(), None) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_new"); + let init_value = false; + let call = + build_message::(contract_acc_id).call(|contract| { + contract.call_instantiate_fallible(code_hash, selector, init_value) + }); + let call_result = client + .call(&mut ink_e2e::ferdie(), call, 0, None) + .await + .expect("Calling `call_builder::call_instantiate_fallible` failed") + .return_value(); + + let contract_result = call_result + .unwrap() + .expect("Dispatching `constructors_return_value::try_new` failed."); + + assert!( + matches!( + contract_result, + Err(constructors_return_value::ConstructorError) + ), + "Got an unexpected error from the contract." + ); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_with_fallible_revert_constructor_encodes_ok( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload("constructors_return_value", &ink_e2e::alice(), None) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_revert_new"); + let init_value = true; + let call = + build_message::(contract_acc_id).call(|contract| { + contract.call_instantiate_fallible(code_hash, selector, init_value) + }); + let call_result = client.call(&mut ink_e2e::alice(), call, 0, None).await; + + assert!( + call_result.is_err(), + "Call execution should've failed, but didn't." + ); + + let contains_err_msg = match call_result.unwrap_err() { + ink_e2e::Error::CallDryRun(dry_run) => { + String::from_utf8_lossy(&dry_run.debug_message) + .contains("The callee reverted, but did not encode an error in the output buffer.") + } + _ => false, + }; + assert!( + contains_err_msg, + "Call execution failed for an unexpected reason." + ); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_with_fallible_revert_constructor_encodes_err( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = CallBuilderTestRef::new(); + let contract_acc_id = client + .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload("constructors_return_value", &ink_e2e::bob(), None) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let selector = ink::selector_bytes!("try_revert_new"); + let init_value = false; + let call = + build_message::(contract_acc_id).call(|contract| { + contract.call_instantiate_fallible(code_hash, selector, init_value) + }); + let call_result = client + .call(&mut ink_e2e::bob(), call, 0, None) + .await + .expect( + "Client failed to call `call_builder::call_instantiate_fallible`.", + ) + .return_value(); + + assert!( + matches!(call_result, Some(Err(ink::LangError::CouldNotReadInput))), + "The callee manually encoded `CouldNotReadInput` to the output buffer, we should've + gotten that back." + ); + + Ok(()) + } } } diff --git a/examples/lang-err-integration-tests/constructors-return-value/lib.rs b/examples/lang-err-integration-tests/constructors-return-value/lib.rs index 35716615f18..018df58ddf9 100644 --- a/examples/lang-err-integration-tests/constructors-return-value/lib.rs +++ b/examples/lang-err-integration-tests/constructors-return-value/lib.rs @@ -34,6 +34,31 @@ pub mod constructors_return_value { } } + /// A constructor which reverts and fills the output buffer with an erronenously encoded + /// return value. + #[ink(constructor)] + pub fn revert_new(_init_value: bool) -> Self { + ink::env::return_value::>( + ink::env::ReturnFlags::new_with_reverted(true), + &Ok(AccountId::from([0u8; 32])), + ) + } + + /// A constructor which reverts and fills the output buffer with an erronenously encoded + /// return value. + #[ink(constructor)] + pub fn try_revert_new(init_value: bool) -> Result { + let value = if init_value { + Ok(Ok(AccountId::from([0u8; 32]))) + } else { + Err(ink::LangError::CouldNotReadInput) + }; + + ink::env::return_value::< + ink::ConstructorResult>, + >(ink::env::ReturnFlags::new_with_reverted(true), &value) + } + /// Returns the current value of the contract storage. #[ink(message)] pub fn get_value(&self) -> bool { @@ -114,7 +139,7 @@ pub mod constructors_return_value { .expect("Instantiate dry run should succeed"); let data = infallible_constructor_result.result.data; - let decoded_result = Result::<(), ::ink::LangError>::decode(&mut &data[..]) + let decoded_result = Result::<(), ink::LangError>::decode(&mut &data[..]) .expect("Failed to decode constructor Result"); assert!( decoded_result.is_ok(), diff --git a/examples/lang-err-integration-tests/contract-ref/lib.rs b/examples/lang-err-integration-tests/contract-ref/lib.rs index bd6e6108e1f..b38d3ff3dd1 100755 --- a/examples/lang-err-integration-tests/contract-ref/lib.rs +++ b/examples/lang-err-integration-tests/contract-ref/lib.rs @@ -22,6 +22,24 @@ mod contract_ref { Self { flipper } } + #[ink(constructor)] + pub fn try_new(version: u32, flipper_code_hash: Hash, succeed: bool) -> Self { + let salt = version.to_le_bytes(); + let flipper = FlipperRef::try_new(succeed) + .endowment(0) + .code_hash(flipper_code_hash) + .salt_bytes(salt) + .instantiate_fallible() + .unwrap_or_else(|error| { + panic!( + "Received an error from the Flipper constructor while instantiating \ + Flipper {:?}", error + ) + }); + + Self { flipper } + } + #[ink(message)] pub fn flip(&mut self) { self.flipper.flip(); @@ -101,5 +119,72 @@ mod contract_ref { Ok(()) } + + #[ink_e2e::test(additional_contracts = "../integration-flipper/Cargo.toml")] + async fn e2e_fallible_ref_can_be_instantiated( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let flipper_hash = client + .upload("integration_flipper", &ink_e2e::bob(), None) + .await + .expect("uploading `flipper` failed") + .code_hash; + + let succeed = true; + let constructor = ContractRefRef::try_new(0, flipper_hash, succeed); + let contract_acc_id = client + .instantiate("contract_ref", &ink_e2e::bob(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let get_check = build_message::(contract_acc_id.clone()) + .call(|contract| contract.get_check()); + let get_call_result = client + .call(&ink_e2e::bob(), get_check, 0, None) + .await + .expect("Calling `get_check` failed"); + let initial_value = get_call_result.return_value(); + assert!(initial_value); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../integration-flipper/Cargo.toml")] + async fn e2e_fallible_ref_fails_to_be_instantiated( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let flipper_hash = client + .upload("integration_flipper", &ink_e2e::charlie(), None) + .await + .expect("uploading `flipper` failed") + .code_hash; + + let succeed = false; + let constructor = ContractRefRef::try_new(0, flipper_hash, succeed); + let instantiate_result = client + .instantiate("contract_ref", &ink_e2e::charlie(), constructor, 0, None) + .await; + + assert!( + instantiate_result.is_err(), + "Call execution should've failed, but didn't." + ); + + let contains_err_msg = match instantiate_result.unwrap_err() { + ink_e2e::Error::InstantiateDryRun(dry_run) => { + String::from_utf8_lossy(&dry_run.debug_message).contains( + "Received an error from the Flipper constructor while instantiating Flipper FlipperError" + ) + } + _ => false, + }; + assert!( + contains_err_msg, + "Call execution failed for an unexpected reason." + ); + + Ok(()) + } } } diff --git a/examples/lang-err-integration-tests/integration-flipper/lib.rs b/examples/lang-err-integration-tests/integration-flipper/lib.rs index 0df61977285..1a9eeaf70f5 100644 --- a/examples/lang-err-integration-tests/integration-flipper/lib.rs +++ b/examples/lang-err-integration-tests/integration-flipper/lib.rs @@ -12,6 +12,10 @@ pub mod integration_flipper { value: bool, } + #[derive(scale::Encode, scale::Decode, Debug)] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct FlipperError; + impl Flipper { /// Creates a new integration_flipper smart contract initialized with the given value. #[ink(constructor)] @@ -25,6 +29,17 @@ pub mod integration_flipper { Self::new(Default::default()) } + /// Attemps to create a new integration_flipper smart contract initialized with the given + /// value. + #[ink(constructor)] + pub fn try_new(succeed: bool) -> Result { + if succeed { + Ok(Self::new(true)) + } else { + Err(FlipperError) + } + } + /// Flips the current value of the Flipper's boolean. #[ink(message)] pub fn flip(&mut self) { From 0ef7755791bc7d3c481efe22cf5df73d19db2d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Tue, 24 Jan 2023 07:50:09 +0100 Subject: [PATCH 16/18] Update `ARCHITECTURE.md` (#1605) * Update notes on unstable Rust features * Add information on `ink_e2e` crate. * Add info on `Environment` trait * Fix capitalization * Apply suggestions from code review Co-authored-by: Hernando Castano * Update ARCHITECTURE.md Co-authored-by: Hernando Castano * Improve text on `Environment` trait * Improve formatting Co-authored-by: Hernando Castano --- ARCHITECTURE.md | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 739cda64f85..85894ac93a4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -19,9 +19,6 @@ ink! is composed of a number of crates that are all found in the The ink! language itself. * [`allocator`](https://github.com/paritytech/ink/tree/master/crates/allocator): The allocator used for dynamic memory allocation in a contract. -* [`engine`](https://github.com/paritytech/ink/tree/master/crates/engine): - An off-chain testing engine, it simulates a blockchain environment and allows - mocking specified conditions. * [`env`](https://github.com/paritytech/ink/tree/master/crates/env): Serves two roles: * Exposes environmental functions, like information about the caller @@ -44,6 +41,15 @@ ink! is composed of a number of crates that are all found in the * [`storage`](https://github.com/paritytech/ink/tree/master/crates/prelude): The collections that are available for contract developers to put in a smart contracts storage. +* [`engine`](https://github.com/paritytech/ink/tree/master/crates/engine): + An off-chain testing engine, it simulates a blockchain environment and allows + mocking specified conditions. +* [`e2e`](https://github.com/paritytech/ink/tree/master/crates/e2e): + An end-to-end testing framework for ink! contracts. It requires a Substrate node + which includes `pallet-contracts` running in the background. The crate provides a + macro which can be used + to write an idiomatic Rust test that will in the background create transactions, + submit it to the Substrate chain and return the state changes, gas costs, etc. An important thing to note is that the crates are primarily run in a `no_std` environment. @@ -96,14 +102,27 @@ gas costs) and a lower transaction throughput. Freeing memory is irrelevant for our use-case anyway, as the entire memory instance is set up fresh for each individual contract call anyway. -## Nightly Rust features in ink! +## Unstable Rust features in ink! -We would like to get away from nightly features of Rust in ink!, so +We would like to get away from unstable features of Rust in ink!, so that users can just use stable Rust for building their contracts. At the moment we're still stuck with one nightly feature though: [alloc_error_handler](https://github.com/rust-lang/rust/issues/51540). It's needed because we use a specialized memory allocation handler, the `ink_allocator` crate. +It's unclear when or if this feature will ever make it to stable. + +We had a lot of issues when requiring users to use Rust nightly. Mostly +because there were regularly bugs in the nightly Rust compiler that +often took days to be fixed. +As a consequence we decided on having `cargo-contract` `v2.0.0` run +`cargo +stable build` with `RUSTC_BOOTSTRAP=1`. This is kind of a hack, +the env variable enables unstable features in the stable Rust toolchain. +But it enabled us to switch tutorials/guides to Rust stable. + +One advantage is that users don't deal with an ever-changing nightly +compiler. It's easier for us to support. If you build a contract without +`cargo-contract` you will have to set this env variable too or use nightly. ## Interaction with `pallet-contracts` @@ -170,3 +189,35 @@ one point. The prefix `seal` here is for historic reasons. There is some analogy to sealing a contract. And we found seals to be a cute animal as well ‒ like squids! + +## `Environment` Trait + +You can use ink! on any blockchain that was built with the [Substrate](https://substrate.io) +framework and includes the [`pallet-contracts`](https://github.com/paritytech/substrate/tree/master/frame/contracts) +module. +Substrate does not define specific types for a blockchain, it uses +generic types throughout. +Chains built on Substrate can decide on their own which types they want +to use for e.g. the chain's block number or account id's. For example, +chains that intend to be compatible to Ethereum typically use the same +type as Ethereum for their `AccountId`. + +The `Environment` trait is how ink! knows the concretes types of the chain +to which the contract will be deployed to. +Specifically, our `ink_env` crate defines a trait [`Environment`](https://paritytech.github.io/ink/ink_env/trait.Environment.html) +which specifies the types. +By default, ink! uses the default Substrate types, the `ink_env` crate +exports an implementation of the `Environment` trait for that: +[`DefaultEnvironment`](https://paritytech.github.io/ink/ink_env/enum.DefaultEnvironment.html). + +If you are developing for a chain that uses different types than the +Substrate default types you can configure a different environment in +the contract macro ([documentation here](https://paritytech.github.io/ink/ink/attr.contract.html#header-arguments)): + +```rust +#[ink::contract(env = MyCustomTypes)] +``` + +__Important:__ If a developer writes a contract for a chain that deviates +from the default Substrate types, they have to make sure to use that +chain's `Environment`. From 59bbd36c1161370697882532c21ee1833e729f13 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 24 Jan 2023 19:24:11 +0000 Subject: [PATCH 17/18] Unify fallible and non fallible instantiate methods (#1591) * Remove fallible create_builder.rs methods * WIP experiment * InstantiateResult blanket impl for T and Result * Introduce ContractRef type parameter * Fix up env access * WIP... * Make it compile * Add ContractStorage parameter * Remove commented out instantiate_fallible_contract * Convert to env Error in helper * Return Decode errors in case of invalid Result first byte * Fix impls::instantiate_contract * Remove ContractStorage generic parameter * Fix env access * Use generated constructor ref, introduces update_selector * Fix e2e * Remove commented out code * Typos * Clippy * Rename some instantiate_fallible * Restore `returns` method * Remove ContractReference Result impl * WIP implementing ConstructorReturnType * Reorder ContractRef parameter, move ContractRef and ContractEnv trait to env crate * Fmt and fix * Remove E param from build_create * Fix up build_create * Fix up e2e creat builder * Implement ContstructorReturnType for the storage_ident * Fmt * Fix envaccess test * Fully qualify Result in macro * More fully qualify Result in macro * Fix up build_create examples * Add test for different combos of Self and struct name * Fix ui test * Fmt * Remove unused assoc type * Change error fn to return Option * Remove commented out code * Fmt * ConstructorReturnType comments * Fix `contract-ref` E2E test compilation * Fix up return types after merge * Fmt * Clippy * Fix create_builder tests * Fix cross-contract compile test * Clean up some comments * Remove outdated doc * Update comment * Another comment fix * Wrap long line * Remove TODO * Bump `contract-metadata` Fixes some inconsistent errors between Clippy and `rustc` * Fix `CreateBuilder` compilation * Fix one of the doc tests * Clean up doc tests a bit Co-authored-by: Hernando Castano Co-authored-by: Hernando Castano --- crates/e2e/Cargo.toml | 2 +- crates/e2e/src/builders.rs | 12 +- crates/e2e/src/client.rs | 12 +- crates/env/Cargo.toml | 3 + crates/env/src/api.rs | 44 +- crates/env/src/backend.rs | 26 +- crates/env/src/call/create_builder.rs | 466 ++++++++++++------ crates/env/src/call/execution_input.rs | 10 + crates/env/src/call/mod.rs | 1 + crates/env/src/contract.rs | 134 +++++ crates/env/src/engine/mod.rs | 152 ++++-- crates/env/src/engine/off_chain/impls.rs | 28 +- crates/env/src/engine/on_chain/impls.rs | 81 +-- crates/env/src/lib.rs | 5 + .../generator/as_dependency/call_builder.rs | 4 +- .../generator/as_dependency/contract_ref.rs | 37 +- crates/ink/codegen/src/generator/dispatch.rs | 14 +- crates/ink/codegen/src/generator/env.rs | 14 +- crates/ink/codegen/src/generator/events.rs | 2 +- .../ink/codegen/src/generator/item_impls.rs | 4 +- crates/ink/codegen/src/generator/metadata.rs | 4 +- crates/ink/codegen/src/generator/storage.rs | 4 +- .../src/generator/trait_def/call_builder.rs | 2 +- .../src/generator/trait_def/call_forwarder.rs | 2 +- .../src/generator/trait_def/definition.rs | 2 +- crates/ink/src/env_access.rs | 105 +--- crates/ink/src/reflect/contract.rs | 135 ----- crates/ink/src/reflect/mod.rs | 6 +- crates/ink/src/reflect/trait_def/registry.rs | 2 +- ...uctor-return-result-non-codec-error.stderr | 18 + ...onstructor-return-result-cross-contract.rs | 83 ++++ .../ui/trait_def/pass/using-env-types.rs | 2 +- .../call-builder/lib.rs | 31 +- .../constructors-return-value/lib.rs | 4 +- .../contract-ref/lib.rs | 2 +- examples/multisig/lib.rs | 4 +- 36 files changed, 813 insertions(+), 644 deletions(-) create mode 100644 crates/env/src/contract.rs create mode 100644 crates/ink/tests/ui/contract/pass/constructor-return-result-cross-contract.rs diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 397a2d098ee..252765b6cb0 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -21,7 +21,7 @@ ink = { version = "4.0.0-beta", path = "../ink" } ink_env = { version = "4.0.0-beta", path = "../env" } ink_primitives = { version = "4.0.0-beta", path = "../primitives" } -contract-metadata = { version = "2.0.0-beta.1" } +contract-metadata = { version = "2.0.0-rc" } impl-serde = { version = "0.3.1", default-features = false } jsonrpsee = { version = "0.16.0", features = ["ws-client"] } serde = { version = "1.0.137", default-features = false, features = ["derive"] } diff --git a/crates/e2e/src/builders.rs b/crates/e2e/src/builders.rs index 1ef93fba5a2..f0ae3b36eaf 100644 --- a/crates/e2e/src/builders.rs +++ b/crates/e2e/src/builders.rs @@ -32,8 +32,9 @@ use scale::Encode; /// The type returned from `ContractRef` constructors, partially initialized with the execution /// input arguments. -pub type CreateBuilderPartial = CreateBuilder< +pub type CreateBuilderPartial = CreateBuilder< E, + ContractRef, Unset<::Hash>, Unset, Unset<::Balance>, @@ -43,9 +44,12 @@ pub type CreateBuilderPartial = CreateBuilder< >; /// Get the encoded constructor arguments from the partially initialized `CreateBuilder` -pub fn constructor_exec_input( - builder: CreateBuilderPartial, -) -> Vec { +pub fn constructor_exec_input( + builder: CreateBuilderPartial, +) -> Vec +where + E: Environment, +{ // set all the other properties to default values, we only require the `exec_input`. builder .endowment(0u32.into()) diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index d6bc75a80b1..d38433e6e4c 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -350,11 +350,11 @@ where /// Calling this function multiple times is idempotent, the contract is /// newly instantiated each time using a unique salt. No existing contract /// instance is reused! - pub async fn instantiate( + pub async fn instantiate( &mut self, contract_name: &str, signer: &Signer, - constructor: CreateBuilderPartial, + constructor: CreateBuilderPartial, value: E::Balance, storage_deposit_limit: Option, ) -> Result, Error> @@ -374,11 +374,11 @@ where } /// Dry run contract instantiation using the given constructor. - pub async fn instantiate_dry_run( + pub async fn instantiate_dry_run( &mut self, contract_name: &str, signer: &Signer, - constructor: CreateBuilderPartial, + constructor: CreateBuilderPartial, value: E::Balance, storage_deposit_limit: Option, ) -> ContractInstantiateResult @@ -406,11 +406,11 @@ where } /// Executes an `instantiate_with_code` call and captures the resulting events. - async fn exec_instantiate( + async fn exec_instantiate( &mut self, signer: &Signer, code: Vec, - constructor: CreateBuilderPartial, + constructor: CreateBuilderPartial, value: E::Balance, storage_deposit_limit: Option, ) -> Result, Error> diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index c6ef99cf836..bacc9ba4af4 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -49,6 +49,9 @@ secp256k1 = { version = "0.26.0", features = ["recovery", "global-context"], opt # Never use this crate outside the off-chain environment! scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true } +[dev-dependencies] +ink = { path = "../ink" } + [features] default = ["std"] std = [ diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 90c1294da19..cb0d25d1812 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -23,8 +23,10 @@ use crate::{ call::{ Call, CallParams, + ConstructorReturnType, CreateParams, DelegateCall, + FromAccountId, }, engine::{ EnvInstance, @@ -323,50 +325,20 @@ where /// - If the instantiation process runs out of gas. /// - If given insufficient endowment. /// - If the returned account ID failed to decode properly. -pub fn instantiate_contract( - params: &CreateParams, -) -> Result> -where - E: Environment, - Args: scale::Encode, - Salt: AsRef<[u8]>, -{ - ::on_instance(|instance| { - TypedEnvBackend::instantiate_contract::(instance, params) - }) -} - -/// Attempts to instantiate another contract, returning the instantiation result back to the -/// caller. -/// -/// # Note -/// -/// This is a low level way to instantiate another smart contract. -/// -/// Prefer to use methods on a `ContractRef` or the [`CreateBuilder`](`crate::call::CreateBuilder`) -/// through [`build_create`](`crate::call::build_create`) instead. -/// -/// # Errors -/// -/// - If the code hash is invalid. -/// - If the arguments passed to the instantiation process are invalid. -/// - If the instantiation process traps. -/// - If the instantiation process runs out of gas. -/// - If given insufficient endowment. -/// - If the returned account ID failed to decode properly. -pub fn instantiate_fallible_contract( - params: &CreateParams, +pub fn instantiate_contract( + params: &CreateParams, ) -> Result< - ink_primitives::ConstructorResult>, + ink_primitives::ConstructorResult<>::Output>, > where E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - ContractError: scale::Decode, + R: ConstructorReturnType, { ::on_instance(|instance| { - TypedEnvBackend::instantiate_fallible_contract::( + TypedEnvBackend::instantiate_contract::( instance, params, ) }) diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 99b2024f39d..93503d1b3ae 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -16,8 +16,10 @@ use crate::{ call::{ Call, CallParams, + ConstructorReturnType, CreateParams, DelegateCall, + FromAccountId, }, hash::{ CryptoHash, @@ -439,34 +441,20 @@ pub trait TypedEnvBackend: EnvBackend { /// # Note /// /// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`] - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result> - where - E: Environment, - Args: scale::Encode, - Salt: AsRef<[u8]>; - - /// Attempts to instantiate another contract, returning the instantiation result back to the - /// caller. - /// - /// # Note - /// - /// For more details visit: [`instantiate_fallible_contract`][`crate::instantiate_fallible_contract`] - fn instantiate_fallible_contract( - &mut self, - params: &CreateParams, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< - core::result::Result, + >::Output, >, > where E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - ContractError: scale::Decode; + R: ConstructorReturnType; /// Terminates a smart contract. /// diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 6dd80724439..b7fcc2646e7 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -22,7 +22,9 @@ use crate::{ Unwrap, }, ExecutionInput, + Selector, }, + ContractEnv, Environment, Error, }; @@ -50,9 +52,118 @@ where fn from_account_id(account_id: ::AccountId) -> Self; } +/// Represents any type that can be returned from an `ink!` constructor. The following contract +/// implements the four different return type signatures implementing this trait: +/// +/// - `Self` +/// - `Result` +/// - `Contract` +/// - `Result` +/// +/// ```rust +/// #[ink::contract] +/// mod contract { +/// #[ink(storage)] +/// pub struct Contract {} +/// +/// #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +/// #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))] +/// pub enum Error { +/// Foo, +/// } +/// +/// impl Contract { +/// #[ink(constructor)] +/// pub fn new_self() -> Self { +/// Self {} +/// } +/// +/// #[ink(constructor)] +/// pub fn new_storage_name() -> Contract { +/// Contract {} +/// } +/// +/// #[ink(constructor)] +/// pub fn new_result_self() -> Result { +/// Ok(Self {}) +/// } +/// +/// #[ink(constructor)] +/// pub fn new_result_storage_name() -> Result { +/// Ok(Contract {}) +/// } +/// +/// #[ink(message)] +/// pub fn message(&self) {} +/// } +/// } +/// ``` +/// +/// These constructor return signatures are then used by the `ContractRef` codegen for the +/// [`CreateBuilder::returns`] type parameter. +pub trait ConstructorReturnType { + /// Is `true` if `Self` is `Result`. + const IS_RESULT: bool = false; + + /// The actual return type of the constructor. + /// - If a constructor returns `Self`, then `Output = Self` + /// - If a constructor returns a `Result`, then `Output = Result` + type Output; + + /// The error type of the constructor return type. + type Error: scale::Decode; + + /// Construct a success value of the `Output` type. + fn ok(value: C) -> Self::Output; + + /// Construct an error value of the `Output` type. + /// + /// `Result` implementations should return `Some(Err(err))`, otherwise default to `None`. + fn err(_err: Self::Error) -> Option { + None + } +} + +/// Blanket implementation for `ContractRef` types, generated for cross-contract calls. +/// +/// In the context of a `ContractRef` inherent, `Self` from a constructor return +/// type will become the type of the `ContractRef`'s type. +impl ConstructorReturnType for C +where + C: ContractEnv + FromAccountId<::Env>, +{ + type Output = C; + type Error = (); + + fn ok(value: C) -> Self::Output { + value + } +} + +/// Blanket implementation for a `Result` return type. `Self` in the context +/// of a `ContractRef` inherent becomes the `ContractRef`s type. +impl ConstructorReturnType for core::result::Result +where + C: ContractEnv + FromAccountId<::Env>, + E: scale::Decode, +{ + const IS_RESULT: bool = true; + + type Output = core::result::Result; + type Error = E; + + fn ok(value: C) -> Self::Output { + Ok(value) + } + + fn err(err: Self::Error) -> Option { + Some(Err(err)) + } +} + /// Builds up contract instantiations. #[derive(Debug)] -pub struct CreateParams +pub struct CreateParams where E: Environment, { @@ -66,11 +177,13 @@ where exec_input: ExecutionInput, /// The salt for determining the hash for the contract account ID. salt_bytes: Salt, - /// The type of the instantiated contract. + /// The return type of the target contract's constructor method. _return_type: ReturnType, + /// The type of the reference to the contract returned from the constructor. + _phantom: PhantomData ContractRef>, } -impl CreateParams +impl CreateParams where E: Environment, { @@ -97,9 +210,17 @@ where pub fn exec_input(&self) -> &ExecutionInput { &self.exec_input } + + /// Modify the selector. + /// + /// Useful when using the [`CreateParams`] generated as part of the + /// `ContractRef`, but using a custom selector. + pub fn update_selector(&mut self, selector: Selector) { + self.exec_input.update_selector(selector) + } } -impl CreateParams +impl CreateParams where E: Environment, Salt: AsRef<[u8]>, @@ -111,12 +232,13 @@ where } } -impl CreateParams +impl CreateParams where E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - R: FromAccountId, + R: ConstructorReturnType, { /// Instantiates the contract and returns its account ID back to the caller. /// @@ -126,12 +248,11 @@ where /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those /// use the [`try_instantiate`][`CreateParams::try_instantiate`] method instead. #[inline] - pub fn instantiate(&self) -> R { + pub fn instantiate(&self) -> >::Output { crate::instantiate_contract(self) .unwrap_or_else(|env_error| { panic!("Cross-contract instantiation failed with {:?}", env_error) }) - .map(FromAccountId::from_account_id) .unwrap_or_else(|lang_error| { panic!( "Received a `LangError` while instantiating: {:?}", @@ -150,66 +271,27 @@ where #[inline] pub fn try_instantiate( &self, - ) -> Result, crate::Error> { + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + crate::Error, + > { crate::instantiate_contract(self) - .map(|inner| inner.map(FromAccountId::from_account_id)) - } -} - -impl - CreateParams> -where - E: Environment, - Args: scale::Encode, - Salt: AsRef<[u8]>, - R: FromAccountId, - ContractError: scale::Decode, -{ - /// Attempts to instantiate the contract, returning the execution result back to the caller. - /// - /// # Panics - /// - /// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle - /// those use the [`try_instantiate_fallible`][`CreateParams::try_instantiate_fallible`] method - /// instead. - #[inline] - pub fn instantiate_fallible(&self) -> Result { - crate::instantiate_fallible_contract(self) - .unwrap_or_else(|env_error| { - panic!("Cross-contract instantiation failed with {:?}", env_error) - }) - .unwrap_or_else(|lang_error| { - panic!( - "Received a `LangError` while instantiating: {:?}", - lang_error - ) - }) - .map(FromAccountId::from_account_id) - } - - /// Attempts to instantiate the contract, returning the execution result back to the caller. - /// - /// # Note - /// - /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner - /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled - /// by the caller. - #[inline] - pub fn try_instantiate_fallible( - &self, - ) -> Result>, crate::Error> - { - crate::instantiate_fallible_contract(self).map(|constructor_result| { - constructor_result.map(|contract_result| { - contract_result.map(FromAccountId::from_account_id) - }) - }) } } /// Builds up contract instantiations. -pub struct CreateBuilder -where +pub struct CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Endowment, + Args, + Salt, + RetType, +> where E: Environment, { code_hash: CodeHash, @@ -218,7 +300,7 @@ where exec_input: Args, salt: Salt, return_type: RetType, - _phantom: PhantomData E>, + _phantom: PhantomData (E, ContractRef)>, } /// Returns a new [`CreateBuilder`] to build up the parameters to a cross-contract instantiation. @@ -250,25 +332,33 @@ where /// # call::{build_create, Selector, ExecutionInput, FromAccountId} /// # }; /// # type Hash = ::Hash; -/// # type AccountId = ::AccountId; -/// # type Salt = &'static [u8]; -/// # struct MyContract; -/// # impl FromAccountId for MyContract { -/// # fn from_account_id(account_id: AccountId) -> Self { Self } +/// # +/// # #[ink::contract] +/// # pub mod contract { +/// # #[ink(storage)] +/// # pub struct MyContract {} +/// # +/// # impl MyContract { +/// # #[ink(constructor)] +/// # pub fn my_constructor() -> Self { Self {} } +/// # +/// # #[ink(message)] +/// # pub fn message(&self) {} +/// # } /// # } -/// let my_contract: MyContract = build_create::() +/// # use contract::MyContractRef; +/// let my_contract: MyContractRef = build_create::() /// .code_hash(Hash::from([0x42; 32])) /// .gas_limit(4000) /// .endowment(25) /// .exec_input( -/// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) +/// ExecutionInput::new(Selector::new(ink::selector_bytes!("my_constructor"))) /// .push_arg(42) /// .push_arg(true) /// .push_arg(&[0x10u8; 32]) /// ) /// .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) -/// .returns::() -/// .params() +/// .returns::() /// .instantiate(); /// ``` /// @@ -281,45 +371,55 @@ where /// # call::{build_create, Selector, ExecutionInput, FromAccountId} /// # }; /// # type Hash = ::Hash; -/// # type AccountId = ::AccountId; -/// # type Salt = &'static [u8]; -/// # struct MyContract; -/// # impl FromAccountId for MyContract { -/// # fn from_account_id(account_id: AccountId) -> Self { Self } +/// # +/// # #[ink::contract] +/// # pub mod contract { +/// # #[derive(scale::Encode, scale::Decode, Debug)] +/// # #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +/// # pub struct ConstructorError; +/// # +/// # #[ink(storage)] +/// # pub struct MyContract {} +/// # +/// # impl MyContract { +/// # #[ink(constructor)] +/// # pub fn my_constructor() -> Result { +/// # Ok(Self {}) +/// # } +/// # +/// # #[ink(message)] +/// # pub fn message(&self) {} +/// # } /// # } -/// # #[derive(scale::Encode, scale::Decode, Debug)] -/// # #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -/// # pub struct ConstructorError; -/// let my_contract: MyContract = build_create::() +/// # use contract::{MyContractRef, ConstructorError}; +/// let my_contract: MyContractRef = build_create::() /// .code_hash(Hash::from([0x42; 32])) /// .gas_limit(4000) /// .endowment(25) /// .exec_input( -/// ExecutionInput::new(Selector::new([0xDE, 0xAD, 0xBE, 0xEF])) +/// ExecutionInput::new(Selector::new(ink::selector_bytes!("my_constructor"))) /// .push_arg(42) /// .push_arg(true) /// .push_arg(&[0x10u8; 32]) /// ) /// .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) -/// .returns::>() -/// .params() -/// .instantiate_fallible() -/// .expect("Constructor should've run without errors."); +/// .returns::>() +/// .instantiate() +/// .expect("Constructor should have executed succesfully."); /// ``` -/// -/// Note the usage of the [`CreateBuilder::instantiate_fallible`] method. #[allow(clippy::type_complexity)] -pub fn build_create() -> CreateBuilder< - E, - Unset, +pub fn build_create() -> CreateBuilder< + ::Env, + ContractRef, + Unset<<::Env as Environment>::Hash>, Unset, - Unset, + Unset<<::Env as Environment>::Balance>, Unset>, Unset, Unset>, > where - E: Environment, + ContractRef: ContractEnv, { CreateBuilder { code_hash: Default::default(), @@ -332,8 +432,17 @@ where } } -impl - CreateBuilder, GasLimit, Endowment, Args, Salt, RetType> +impl + CreateBuilder< + E, + ContractRef, + Unset, + GasLimit, + Endowment, + Args, + Salt, + RetType, + > where E: Environment, { @@ -342,7 +451,16 @@ where pub fn code_hash( self, code_hash: E::Hash, - ) -> CreateBuilder, GasLimit, Endowment, Args, Salt, RetType> { + ) -> CreateBuilder< + E, + ContractRef, + Set, + GasLimit, + Endowment, + Args, + Salt, + RetType, + > { CreateBuilder { code_hash: Set(code_hash), gas_limit: self.gas_limit, @@ -355,8 +473,8 @@ where } } -impl - CreateBuilder, Endowment, Args, Salt, RetType> +impl + CreateBuilder, Endowment, Args, Salt, RetType> where E: Environment, { @@ -365,7 +483,8 @@ where pub fn gas_limit( self, gas_limit: u64, - ) -> CreateBuilder, Endowment, Args, Salt, RetType> { + ) -> CreateBuilder, Endowment, Args, Salt, RetType> + { CreateBuilder { code_hash: self.code_hash, gas_limit: Set(gas_limit), @@ -378,8 +497,17 @@ where } } -impl - CreateBuilder, Args, Salt, RetType> +impl + CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Unset, + Args, + Salt, + RetType, + > where E: Environment, { @@ -388,7 +516,16 @@ where pub fn endowment( self, endowment: E::Balance, - ) -> CreateBuilder, Args, Salt, RetType> { + ) -> CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Set, + Args, + Salt, + RetType, + > { CreateBuilder { code_hash: self.code_hash, gas_limit: self.gas_limit, @@ -401,9 +538,10 @@ where } } -impl +impl CreateBuilder< E, + ContractRef, CodeHash, GasLimit, Endowment, @@ -421,6 +559,7 @@ where exec_input: ExecutionInput, ) -> CreateBuilder< E, + ContractRef, CodeHash, GasLimit, Endowment, @@ -440,8 +579,17 @@ where } } -impl - CreateBuilder, RetType> +impl + CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Endowment, + Args, + Unset, + RetType, + > where E: Environment, { @@ -450,7 +598,16 @@ where pub fn salt_bytes( self, salt: Salt, - ) -> CreateBuilder, RetType> + ) -> CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Endowment, + Args, + Set, + RetType, + > where Salt: AsRef<[u8]>, { @@ -466,8 +623,17 @@ where } } -impl - CreateBuilder>> +impl + CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Endowment, + Args, + Salt, + Unset>, + > where E: Environment, { @@ -483,7 +649,19 @@ where #[inline] pub fn returns( self, - ) -> CreateBuilder>> + ) -> CreateBuilder< + E, + ContractRef, + CodeHash, + GasLimit, + Endowment, + Args, + Salt, + Set>, + > + where + ContractRef: FromAccountId, + R: ConstructorReturnType, { CreateBuilder { code_hash: self.code_hash, @@ -497,9 +675,10 @@ where } } -impl +impl CreateBuilder< E, + ContractRef, Set, GasLimit, Set, @@ -513,7 +692,7 @@ where { /// Finalizes the create builder, allowing it to instantiate a contract. #[inline] - pub fn params(self) -> CreateParams { + pub fn params(self) -> CreateParams { CreateParams { code_hash: self.code_hash.value(), gas_limit: self.gas_limit.unwrap_or_else(|| 0), @@ -521,13 +700,15 @@ where exec_input: self.exec_input.value(), salt_bytes: self.salt.value(), _return_type: Default::default(), + _phantom: Default::default(), } } } -impl +impl CreateBuilder< E, + ContractRef, Set, GasLimit, Set, @@ -537,10 +718,11 @@ impl > where E: Environment, + ContractRef: FromAccountId, GasLimit: Unwrap, Args: scale::Encode, Salt: AsRef<[u8]>, - RetType: FromAccountId, + RetType: ConstructorReturnType, { /// Instantiates the contract and returns its account ID back to the caller. /// @@ -550,7 +732,7 @@ where /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those /// use the [`try_instantiate`][`CreateBuilder::try_instantiate`] method instead. #[inline] - pub fn instantiate(self) -> RetType { + pub fn instantiate(self) -> >::Output { self.params().instantiate() } @@ -564,54 +746,12 @@ where #[inline] pub fn try_instantiate( self, - ) -> Result, Error> { + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + Error, + > { self.params().try_instantiate() } } - -impl - CreateBuilder< - E, - Set, - GasLimit, - Set, - Set>, - Set, - Set>>, - > -where - E: Environment, - GasLimit: Unwrap, - Args: scale::Encode, - Salt: AsRef<[u8]>, - RetType: FromAccountId, - ContractError: scale::Decode, -{ - /// Attempts to instantiate the contract, returning the execution result back to the caller. - /// - /// # Panics - /// - /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an - /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle those - /// use the [`try_instantiate_fallible`][`CreateParams::try_instantiate_fallible`] method - /// instead. - #[inline] - pub fn instantiate_fallible(self) -> Result { - self.params().instantiate_fallible() - } - - /// Attempts to instantiate the contract, returning the execution result back to the caller. - /// - /// # Note - /// - /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner - /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be handled - /// by the caller. - #[inline] - pub fn try_instantiate_fallible( - self, - ) -> Result>, Error> - { - self.params().try_instantiate_fallible() - } -} diff --git a/crates/env/src/call/execution_input.rs b/crates/env/src/call/execution_input.rs index fba340ebf1c..fb491468522 100644 --- a/crates/env/src/call/execution_input.rs +++ b/crates/env/src/call/execution_input.rs @@ -63,6 +63,16 @@ impl ExecutionInput, Rest>> { } } +impl ExecutionInput { + /// Modify the selector. + /// + /// Useful when using the [`ExecutionInput`] generated as part of the + /// `ContractRef`, but using a custom selector. + pub fn update_selector(&mut self, selector: Selector) { + self.selector = selector; + } +} + /// An argument list. /// /// This type is constructed mainly at compile type via type constructions diff --git a/crates/env/src/call/mod.rs b/crates/env/src/call/mod.rs index ac4ba6cc4bb..f5fd923cef7 100644 --- a/crates/env/src/call/mod.rs +++ b/crates/env/src/call/mod.rs @@ -50,6 +50,7 @@ pub use self::{ create_builder::{ build_create, state, + ConstructorReturnType, CreateBuilder, CreateParams, FromAccountId, diff --git a/crates/env/src/contract.rs b/crates/env/src/contract.rs new file mode 100644 index 00000000000..4eef18fa49b --- /dev/null +++ b/crates/env/src/contract.rs @@ -0,0 +1,134 @@ +/// Stores the used host environment type of the ink! smart contract. +/// +/// # Note +/// +/// The used host environment can be altered using the `env` configuration +/// parameter in the `#[ink::contract]` parameters. For example if the user +/// wanted to use an environment type definition called `MyEnvironment` they +/// issue the ink! smart contract as follows: +/// +/// ```no_compile +/// #[ink::contract(env = MyEnvironment)] +/// ``` +/// +/// # Usage: Default Environment +/// +/// ``` +/// +/// #[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) {} +/// } +/// } +/// +/// use contract::Contract; +/// +/// # use ink::env::ContractEnv; +/// # use ink::codegen::utils::IsSameType; +/// +/// // The following line only compiles successfully if both +/// // `ink_env::DefaultEnvironment` and `::Env` +/// // are of the same type. +/// const _: IsSameType<::Env> = +/// >::new(); +/// ``` +/// +/// # Usage: Custom Environment +/// +/// ``` +/// # use ink_env::{Environment, DefaultEnvironment}; +/// +/// pub struct CustomEnvironment {} +/// +/// impl Environment for CustomEnvironment { +/// const MAX_EVENT_TOPICS: usize = 4; +/// +/// type AccountId = ::AccountId; +/// type Balance = u64; +/// type Hash = ::Hash; +/// type BlockNumber = u32; +/// type Timestamp = u64; +/// type ChainExtension = ::ChainExtension; +/// } +/// +/// #[ink::contract(env = super::CustomEnvironment)] +/// pub mod contract { +/// #[ink(storage)] +/// pub struct Contract {} +/// +/// impl Contract { +/// #[ink(constructor)] +/// pub fn constructor() -> Self { Self {} } +/// +/// #[ink(message)] +/// pub fn message(&self) {} +/// } +/// } +/// +/// use contract::Contract; +/// # use ink::env::ContractEnv; +/// # use ink::codegen::utils::IsSameType; +/// +/// // The following line only compiles successfully if both +/// // `CustomEnvironment` and `::Env` +/// // are of the same type. +/// const _: IsSameType<::Env> = +/// >::new(); +/// +/// fn main() {} +/// ``` +pub trait ContractEnv { + /// The environment type. + type Env: crate::Environment; +} + +/// Refers to the generated ink! smart contract reference type. +/// +/// # Note +/// +/// Given an ink! storage struct with identifier `Contract` the ink! codegen produces +/// the ink! root type `Contract` and the ink! reference type `ContractRef`. +/// +/// This trait exists so that users can avoid using a generated identifier to refer to +/// the generated reference type of the ink! smart contract. +/// +/// # Usage +/// +/// ``` +/// +/// #[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) {} +/// } +/// } +/// +/// use contract::{Contract, ContractRef}; +/// # use ink::codegen::utils::IsSameType; +/// # use ink::env::ContractReference; +/// +/// // The following line only compiles successfully if both +/// // `ContractReference` and `::Type` +/// // are of the same type. +/// const _: IsSameType<::Type> = +/// >::new(); +/// ``` +pub trait ContractReference { + /// The generated contract reference type. + type Type; +} diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 440cc5b1032..f75a32c4aed 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -17,10 +17,19 @@ use crate::{ EnvBackend, TypedEnvBackend, }, + call::{ + ConstructorReturnType, + FromAccountId, + }, + Error as EnvError, + Error, Result as EnvResult, }; use cfg_if::cfg_if; -use ink_primitives::ConstructorResult; +use ink_primitives::{ + ConstructorResult, + LangError, +}; pub trait OnInstance: EnvBackend + TypedEnvBackend { fn on_instance(f: F) -> R @@ -42,65 +51,132 @@ cfg_if! { } } -// The `Result` type used to represent the programmer defined contract output. -type ContractResult = core::result::Result; - // We only use this function when 1) compiling to Wasm 2) compiling for tests. #[cfg_attr(all(feature = "std", not(test)), allow(dead_code))] -pub(crate) fn decode_fallible_constructor_reverted_return_value( +pub(crate) fn decode_instantiate_result( + instantiate_result: EnvResult<()>, + out_address: &mut I, out_return_value: &mut I, -) -> EnvResult>> +) -> EnvResult>::Output>> where I: scale::Input, E: crate::Environment, - ContractError: scale::Decode, + ContractRef: FromAccountId, + R: ConstructorReturnType, { - let out: ConstructorResult> = - scale::Decode::decode(out_return_value)?; - - match out { - ConstructorResult::Ok(ContractResult::Ok(())) => { - // Since the contract reverted we don't expect an `Ok` return value from the - // constructor, otherwise we'd be in the `AccountId` decoding branch. - // - // While we could handle this more gracefully, e.g through a `LangError`, we're going to - // be defensive for now and trap. - panic!( - "The callee reverted, but did not encode an error in the output buffer." - ) + match instantiate_result { + Ok(()) => { + let account_id = scale::Decode::decode(out_address)?; + let contract_ref = + >::from_account_id(account_id); + let output = >::ok(contract_ref); + Ok(Ok(output)) } - ConstructorResult::Ok(ContractResult::Err(contract_error)) => { - Ok(ConstructorResult::Ok(ContractResult::Err(contract_error))) + Err(EnvError::CalleeReverted) => { + decode_instantiate_err::(out_return_value) } - ConstructorResult::Err(lang_error) => Ok(ConstructorResult::Err(lang_error)), + Err(actual_error) => Err(actual_error), + } +} + +#[cfg_attr(all(feature = "std", not(test)), allow(dead_code))] +fn decode_instantiate_err( + out_return_value: &mut I, +) -> EnvResult>::Output>> +where + I: scale::Input, + E: crate::Environment, + ContractRef: FromAccountId, + R: ConstructorReturnType, +{ + let constructor_result_variant = out_return_value.read_byte()?; + match constructor_result_variant { + // 0 == `ConstructorResult::Ok` variant + 0 => { + if >::IS_RESULT { + let result_variant = out_return_value.read_byte()?; + match result_variant { + // 0 == `Ok` variant + 0 => panic!("The callee reverted, but did not encode an error in the output buffer."), + // 1 == `Err` variant + 1 => { + let contract_err = <>::Error + as scale::Decode>::decode(out_return_value)?; + let err = >::err(contract_err) + .unwrap_or_else(|| { + panic!("Expected an error instance for return type where IS_RESULT == true") + }); + Ok(Ok(err)) + } + _ => Err(Error::Decode( + "Invalid inner constructor Result encoding, expected 0 or 1 as the first byte".into()) + ) + } + } else { + panic!("The callee reverted, but did not encode an error in the output buffer.") + } + } + // 1 == `ConstructorResult::Err` variant + 1 => { + let lang_err = ::decode(out_return_value)?; + Ok(Err(lang_err)) + } + _ => Err(Error::Decode( + "Invalid outer constructor Result encoding, expected 0 or 1 as the first byte".into()) + ) } } #[cfg(test)] -mod fallible_constructor_reverted_tests { +mod decode_instantiate_result_tests { use super::*; + use crate::{ + DefaultEnvironment, + Environment, + Error, + }; use scale::Encode; + // The `Result` type used to represent the programmer defined contract output. + type ContractResult = Result; + #[derive(scale::Encode, scale::Decode)] struct ContractError(String); + type AccountId = ::AccountId; + struct TestContractRef(AccountId); + + impl crate::ContractEnv for TestContractRef { + type Env = DefaultEnvironment; + } + + impl FromAccountId for TestContractRef { + fn from_account_id(account_id: AccountId) -> Self { + Self(account_id) + } + } + fn encode_and_decode_return_value( return_value: ConstructorResult>, - ) -> EnvResult>> - { + ) -> EnvResult>> { + let out_address = Vec::new(); let encoded_return_value = return_value.encode(); - decode_return_value(&mut &encoded_return_value[..]) + decode_return_value_fallible( + &mut &out_address[..], + &mut &encoded_return_value[..], + ) } - fn decode_return_value( - input: &mut I, - ) -> EnvResult>> - { - decode_fallible_constructor_reverted_return_value::< + fn decode_return_value_fallible( + out_address: &mut I, + out_return_value: &mut I, + ) -> EnvResult>> { + decode_instantiate_result::< I, - crate::DefaultEnvironment, - ContractError, - >(input) + DefaultEnvironment, + TestContractRef, + Result, + >(Err(Error::CalleeReverted), out_address, out_return_value) } #[test] @@ -144,9 +220,13 @@ mod fallible_constructor_reverted_tests { #[test] fn invalid_bytes_in_output_buffer_fail_decoding() { + let out_address = Vec::new(); let invalid_encoded_return_value = vec![69]; - let decoded_result = decode_return_value(&mut &invalid_encoded_return_value[..]); + let decoded_result = decode_return_value_fallible( + &mut &out_address[..], + &mut &invalid_encoded_return_value[..], + ); assert!(matches!(decoded_result, Err(crate::Error::Decode(_)))) } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 771d8608054..8e4f4e8f115 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -17,8 +17,10 @@ use crate::{ call::{ Call, CallParams, + ConstructorReturnType, CreateParams, DelegateCall, + FromAccountId, }, hash::{ Blake2x128, @@ -469,36 +471,20 @@ impl TypedEnvBackend for EnvInstance { ) } - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result> - where - E: Environment, - Args: scale::Encode, - Salt: AsRef<[u8]>, - { - let _code_hash = params.code_hash(); - let _gas_limit = params.gas_limit(); - let _endowment = params.endowment(); - let _input = params.exec_input(); - let _salt_bytes = params.salt_bytes(); - unimplemented!("off-chain environment does not support contract instantiation") - } - - fn instantiate_fallible_contract( - &mut self, - params: &CreateParams, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< - core::result::Result, + >::Output, >, > where E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - ContractError: scale::Decode, + R: ConstructorReturnType, { let _code_hash = params.code_hash(); let _gas_limit = params.gas_limit(); diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 3b98fa02ec9..fec97b6ddf3 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -22,8 +22,10 @@ use crate::{ call::{ Call, CallParams, + ConstructorReturnType, CreateParams, DelegateCall, + FromAccountId, }, hash::{ Blake2x128, @@ -479,69 +481,20 @@ impl TypedEnvBackend for EnvInstance { } } - fn instantiate_contract( + fn instantiate_contract( &mut self, - params: &CreateParams, - ) -> Result> - where - E: Environment, - Args: scale::Encode, - Salt: AsRef<[u8]>, - { - let mut scoped = self.scoped_buffer(); - let gas_limit = params.gas_limit(); - let enc_code_hash = scoped.take_encoded(params.code_hash()); - let enc_endowment = scoped.take_encoded(params.endowment()); - let enc_input = scoped.take_encoded(params.exec_input()); - // We support `AccountId` types with an encoding that requires up to - // 1024 bytes. Beyond that limit ink! contracts will trap for now. - // In the default configuration encoded `AccountId` require 32 bytes. - let out_address = &mut scoped.take(1024); - let salt = params.salt_bytes().as_ref(); - let out_return_value = &mut scoped.take_rest(); - - let instantiate_result = ext::instantiate( - enc_code_hash, - gas_limit, - enc_endowment, - enc_input, - out_address, - out_return_value, - salt, - ); - - match instantiate_result { - Ok(()) => { - let account_id = scale::Decode::decode(&mut &out_address[..])?; - Ok(Ok(account_id)) - } - Err(ext::Error::CalleeReverted) => { - // We don't wrap manually with an extra `Err` like we do in the `Ok` case since the - // buffer already comes back in the form of `Err(LangError)` (assuming it's encoded - // by the ink! codegen and not the contract). - let out = ink_primitives::ConstructorResult::::decode( - &mut &out_return_value[..], - )?; - assert!(out.is_err(), "The callee reverted, but did not encode an error in the output buffer."); - Ok(out) - } - Err(actual_error) => Err(actual_error.into()), - } - } - - fn instantiate_fallible_contract( - &mut self, - params: &CreateParams, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< - core::result::Result, + >::Output, >, > where E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - ContractError: scale::Decode, + RetType: ConstructorReturnType, { let mut scoped = self.scoped_buffer(); let gas_limit = params.gas_limit(); @@ -565,21 +518,11 @@ impl TypedEnvBackend for EnvInstance { salt, ); - match instantiate_result { - Ok(()) => { - let account_id: E::AccountId = - scale::Decode::decode(&mut &out_address[..])?; - Ok(ink_primitives::ConstructorResult::Ok(Ok(account_id))) - } - Err(ext::Error::CalleeReverted) => { - crate::engine::decode_fallible_constructor_reverted_return_value::< - _, - E, - ContractError, - >(&mut &out_return_value[..]) - } - Err(actual_error) => Err(actual_error.into()), - } + crate::engine::decode_instantiate_result::<_, E, ContractRef, RetType>( + instantiate_result.map_err(Into::into), + &mut &out_address[..], + &mut &out_return_value[..], + ) } fn terminate_contract(&mut self, beneficiary: E::AccountId) -> ! diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index e9d9be7dcf1..2c56eba77fe 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -73,6 +73,7 @@ mod arithmetic; mod backend; pub mod call; pub mod chain_extension; +mod contract; mod engine; mod error; pub mod hash; @@ -97,6 +98,10 @@ pub use self::{ CallFlags, ReturnFlags, }, + contract::{ + ContractEnv, + ContractReference, + }, error::{ Error, Result, diff --git a/crates/ink/codegen/src/generator/as_dependency/call_builder.rs b/crates/ink/codegen/src/generator/as_dependency/call_builder.rs index e85e52b0ee3..06ce61e4425 100644 --- a/crates/ink/codegen/src/generator/as_dependency/call_builder.rs +++ b/crates/ink/codegen/src/generator/as_dependency/call_builder.rs @@ -104,8 +104,8 @@ impl CallBuilder<'_> { type Type = #cb_ident; } - impl ::ink::reflect::ContractEnv for #cb_ident { - type Env = <#storage_ident as ::ink::reflect::ContractEnv>::Env; + impl ::ink::env::ContractEnv for #cb_ident { + type Env = <#storage_ident as ::ink::env::ContractEnv>::Env; } }; ) diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index a1242d5cdeb..42537d034d9 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -105,12 +105,40 @@ impl ContractRef<'_> { } const _: () = { - impl ::ink::reflect::ContractReference for #storage_ident { + impl ::ink::env::ContractReference for #storage_ident { type Type = #ref_ident; } - impl ::ink::reflect::ContractEnv for #ref_ident { - type Env = <#storage_ident as ::ink::reflect::ContractEnv>::Env; + impl ::ink::env::call::ConstructorReturnType<#ref_ident> for #storage_ident { + type Output = #ref_ident; + type Error = (); + + fn ok(value: #ref_ident) -> Self::Output { + value + } + } + + impl ::ink::env::call::ConstructorReturnType<#ref_ident> + for ::core::result::Result<#storage_ident, E> + where + E: ::scale::Decode + { + const IS_RESULT: bool = true; + + type Output = ::core::result::Result<#ref_ident, E>; + type Error = E; + + fn ok(value: #ref_ident) -> Self::Output { + ::core::result::Result::Ok(value) + } + + fn err(err: Self::Error) -> ::core::option::Option { + ::core::option::Option::Some(::core::result::Result::Err(err)) + } + } + + impl ::ink::env::ContractEnv for #ref_ident { + type Env = <#storage_ident as ::ink::env::ContractEnv>::Env; } }; ) @@ -415,6 +443,7 @@ impl ContractRef<'_> { #( #input_bindings : #input_types ),* ) -> ::ink::env::call::CreateBuilder< Environment, + Self, ::ink::env::call::utils::Unset, ::ink::env::call::utils::Unset, ::ink::env::call::utils::Unset, @@ -422,7 +451,7 @@ impl ContractRef<'_> { ::ink::env::call::utils::Unset<::ink::env::call::state::Salt>, ::ink::env::call::utils::Set<::ink::env::call::utils::ReturnType<#ret_type>>, > { - ::ink::env::call::build_create::() + ::ink::env::call::build_create::() .exec_input( ::ink::env::call::ExecutionInput::new( ::ink::env::call::Selector::new([ #( #selector_bytes ),* ]) diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 4246d982103..e7c896dcc43 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -171,7 +171,7 @@ impl Dispatch<'_> { quote_spanned!(span=> { ::core::primitive::u32::from_be_bytes( - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR ) @@ -352,12 +352,12 @@ impl Dispatch<'_> { let mutates = message.receiver().is_ref_mut(); let local_id = message.local_id().hex_padded_suffixed(); let payable = quote! {{ - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE }}; let selector = quote! {{ - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR }}; @@ -416,7 +416,7 @@ impl Dispatch<'_> { #[allow(clippy::nonminimal_bool)] fn deploy() { if !#any_constructor_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::env::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } @@ -454,7 +454,7 @@ impl Dispatch<'_> { #[allow(clippy::nonminimal_bool)] fn call() { if !#any_message_accept_payment { - ::ink::codegen::deny_payment::<<#storage_ident as ::ink::reflect::ContractEnv>::Env>() + ::ink::codegen::deny_payment::<<#storage_ident as ::ink::env::ContractEnv>::Env>() .unwrap_or_else(|error| ::core::panic!("{}", error)) } @@ -618,7 +618,7 @@ impl Dispatch<'_> { Self::#constructor_ident(input) => { if #any_constructor_accept_payment && #deny_payment { ::ink::codegen::deny_payment::< - <#storage_ident as ::ink::reflect::ContractEnv>::Env>()?; + <#storage_ident as ::ink::env::ContractEnv>::Env>()?; } let result: #constructor_output = #constructor_callable(input); @@ -822,7 +822,7 @@ impl Dispatch<'_> { Self::#message_ident(input) => { if #any_message_accept_payment && #deny_payment { ::ink::codegen::deny_payment::< - <#storage_ident as ::ink::reflect::ContractEnv>::Env>()?; + <#storage_ident as ::ink::env::ContractEnv>::Env>()?; } let result: #message_output = #message_callable(&mut contract, input); diff --git a/crates/ink/codegen/src/generator/env.rs b/crates/ink/codegen/src/generator/env.rs index a9ebd428731..31361cd9cbd 100644 --- a/crates/ink/codegen/src/generator/env.rs +++ b/crates/ink/codegen/src/generator/env.rs @@ -28,17 +28,17 @@ impl GenerateCode for Env<'_> { let env = self.contract.config().env(); let storage_ident = self.contract.module().storage().ident(); quote! { - impl ::ink::reflect::ContractEnv for #storage_ident { + impl ::ink::env::ContractEnv for #storage_ident { type Env = #env; } - type Environment = <#storage_ident as ::ink::reflect::ContractEnv>::Env; + type Environment = <#storage_ident as ::ink::env::ContractEnv>::Env; - type AccountId = <<#storage_ident as ::ink::reflect::ContractEnv>::Env as ::ink::env::Environment>::AccountId; - type Balance = <<#storage_ident as ::ink::reflect::ContractEnv>::Env as ::ink::env::Environment>::Balance; - type Hash = <<#storage_ident as ::ink::reflect::ContractEnv>::Env as ::ink::env::Environment>::Hash; - type Timestamp = <<#storage_ident as ::ink::reflect::ContractEnv>::Env as ::ink::env::Environment>::Timestamp; - type BlockNumber = <<#storage_ident as ::ink::reflect::ContractEnv>::Env as ::ink::env::Environment>::BlockNumber; + type AccountId = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::AccountId; + type Balance = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::Balance; + type Hash = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::Hash; + type Timestamp = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::Timestamp; + type BlockNumber = <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::BlockNumber; } } } diff --git a/crates/ink/codegen/src/generator/events.rs b/crates/ink/codegen/src/generator/events.rs index 62344eab8fb..1dd1613b49d 100644 --- a/crates/ink/codegen/src/generator/events.rs +++ b/crates/ink/codegen/src/generator/events.rs @@ -153,7 +153,7 @@ impl<'a> Events<'a> { let event_ident = event.ident(); let len_topics = event.fields().filter(|event| event.is_topic).count(); let max_len_topics = quote_spanned!(span=> - <<#storage_ident as ::ink::reflect::ContractEnv>::Env + <<#storage_ident as ::ink::env::ContractEnv>::Env as ::ink::env::Environment>::MAX_EVENT_TOPICS ); quote_spanned!(span=> diff --git a/crates/ink/codegen/src/generator/item_impls.rs b/crates/ink/codegen/src/generator/item_impls.rs index 673c6fe9779..819d452bd4d 100644 --- a/crates/ink/codegen/src/generator/item_impls.rs +++ b/crates/ink/codegen/src/generator/item_impls.rs @@ -91,7 +91,7 @@ impl ItemImpls<'_> { let message_guard_payable = message.is_payable().then(|| { quote_spanned!(message_span=> const _: ::ink::codegen::TraitMessagePayable<{ - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#message_local_id>>::PAYABLE }> = ::ink::codegen::TraitMessagePayable::; @@ -102,7 +102,7 @@ impl ItemImpls<'_> { quote_spanned!(message_span=> const _: ::ink::codegen::TraitMessageSelector<{ ::core::primitive::u32::from_be_bytes( - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#message_local_id>>::SELECTOR ) diff --git a/crates/ink/codegen/src/generator/metadata.rs b/crates/ink/codegen/src/generator/metadata.rs index 57e1b03a16f..717f0c35705 100644 --- a/crates/ink/codegen/src/generator/metadata.rs +++ b/crates/ink/codegen/src/generator/metadata.rs @@ -288,12 +288,12 @@ impl Metadata<'_> { let mutates = message.receiver().is_ref_mut(); let local_id = message.local_id().hex_padded_suffixed(); let is_payable = quote! {{ - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE }}; let selector = quote! {{ - <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::reflect::ContractEnv>::Env> + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR }}; diff --git a/crates/ink/codegen/src/generator/storage.rs b/crates/ink/codegen/src/generator/storage.rs index dcc38a3b0f5..90d05f970b3 100644 --- a/crates/ink/codegen/src/generator/storage.rs +++ b/crates/ink/codegen/src/generator/storage.rs @@ -62,7 +62,7 @@ impl Storage<'_> { const _: () = { impl<'a> ::ink::codegen::Env for &'a #storage_ident { type EnvAccess = ::ink::EnvAccess< - 'a, <#storage_ident as ::ink::reflect::ContractEnv>::Env>; + 'a, <#storage_ident as ::ink::env::ContractEnv>::Env>; fn env(self) -> Self::EnvAccess { <::EnvAccess @@ -72,7 +72,7 @@ impl Storage<'_> { impl<'a> ::ink::codegen::StaticEnv for #storage_ident { type EnvAccess = ::ink::EnvAccess< - 'static, <#storage_ident as ::ink::reflect::ContractEnv>::Env>; + 'static, <#storage_ident as ::ink::env::ContractEnv>::Env>; fn env() -> Self::EnvAccess { <::EnvAccess diff --git a/crates/ink/codegen/src/generator/trait_def/call_builder.rs b/crates/ink/codegen/src/generator/trait_def/call_builder.rs index 4cfa4a716b7..34221367b36 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_builder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_builder.rs @@ -247,7 +247,7 @@ impl CallBuilder<'_> { let builder_ident = self.ident(); let message_impls = self.generate_ink_trait_impl_messages(); quote_spanned!(span=> - impl ::ink::reflect::ContractEnv for #builder_ident + impl ::ink::env::ContractEnv for #builder_ident where E: ::ink::env::Environment, { diff --git a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs index 8fd6fc85995..9eebbce3e60 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs @@ -274,7 +274,7 @@ impl CallForwarder<'_> { let forwarder_ident = self.ident(); let message_impls = self.generate_ink_trait_impl_messages(); quote_spanned!(span=> - impl ::ink::reflect::ContractEnv for #forwarder_ident + impl ::ink::env::ContractEnv for #forwarder_ident where E: ::ink::env::Environment, { diff --git a/crates/ink/codegen/src/generator/trait_def/definition.rs b/crates/ink/codegen/src/generator/trait_def/definition.rs index f29de733f72..ea50c1b6d92 100644 --- a/crates/ink/codegen/src/generator/trait_def/definition.rs +++ b/crates/ink/codegen/src/generator/trait_def/definition.rs @@ -59,7 +59,7 @@ impl TraitDefinition<'_> { .map(Self::generate_for_message); quote_spanned!(span => #(#attrs)* - pub trait #ident: ::ink::reflect::ContractEnv { + pub trait #ident: ::ink::env::ContractEnv { /// Holds general and global information about the trait. #[doc(hidden)] #[allow(non_camel_case_types)] diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 59b3ad376b8..89cbcf82556 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -18,8 +18,10 @@ use ink_env::{ call::{ Call, CallParams, + ConstructorReturnType, CreateParams, DelegateCall, + FromAccountId, }, hash::{ CryptoHash, @@ -443,8 +445,8 @@ where /// /// /// Instantiates another contract. /// #[ink(message)] - /// pub fn instantiate_contract(&self) -> AccountId { - /// let create_params = build_create::() + /// pub fn instantiate_contract(&self) -> MyContractRef { + /// let create_params = build_create::() /// .code_hash(Hash::from([0x42; 32])) /// .gas_limit(4000) /// .endowment(25) @@ -478,108 +480,21 @@ where /// # Note /// /// For more details visit: [`ink_env::instantiate_contract`] - pub fn instantiate_contract( + pub fn instantiate_contract( self, - params: &CreateParams, - ) -> Result> - where - Args: scale::Encode, - Salt: AsRef<[u8]>, - { - ink_env::instantiate_contract::(params) - } - - /// Attempts to instantiate a contract, returning the execution result back to the caller. - /// - /// # Example - /// - /// ``` - /// # #[ink::contract] - /// # pub mod my_contract { - /// # // In order for this to actually work with another contract we'd need a way - /// # // to turn the `ink-as-dependency` crate feature on in doctests, which we - /// # // can't do. - /// # // - /// # // Instead we use our own contract's `Ref`, which is fine for this example - /// # // (just need something that implements the `ContractRef` trait). - /// # pub mod other_contract { - /// # pub use super::MyContractRef as OtherContractRef; - /// # pub use super::ConstructorError as OtherConstructorError; - /// # } - /// use ink::env::{ - /// DefaultEnvironment, - /// call::{build_create, Selector, ExecutionInput} - /// }; - /// use other_contract::{OtherContractRef, OtherConstructorError}; - /// - /// # #[derive(scale::Encode, scale::Decode, Debug)] - /// # #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - /// # pub struct ConstructorError; - /// # - /// # #[ink(storage)] - /// # pub struct MyContract { } - /// # - /// # impl MyContract { - /// # #[ink(constructor)] - /// # pub fn try_new() -> Result { - /// # Ok(Self {}) - /// # } - /// # - /// /// Attempts to instantiate a contract, returning the `AccountId` back to the caller. - /// #[ink(message)] - /// pub fn instantiate_fallible_contract(&self) -> AccountId { - /// let create_params = build_create::() - /// .code_hash(Hash::from([0x42; 32])) - /// .gas_limit(4000) - /// .endowment(25) - /// .exec_input( - /// ExecutionInput::new(Selector::new(ink::selector_bytes!("try_new"))) - /// .push_arg(42) - /// .push_arg(true) - /// .push_arg(&[0x10u8; 32]), - /// ) - /// .salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE]) - /// .returns::>() - /// .params(); - /// self.env() - /// .instantiate_fallible_contract(&create_params) - /// .unwrap_or_else(|error| { - /// panic!( - /// "Received an error from the Contracts pallet while instantiating: {:?}", - /// error - /// ) - /// }) - /// .unwrap_or_else(|error| panic!("Received a `LangError` while instatiating: {:?}", error)) - /// .unwrap_or_else(|error: ConstructorError| { - /// panic!( - /// "Received a `ConstructorError` while instatiating: {:?}", - /// error - /// ) - /// }) - /// } - /// # - /// # } - /// # } - /// ``` - /// - /// # Note - /// - /// For more details visit: [`ink_env::instantiate_fallible_contract`] - pub fn instantiate_fallible_contract( - self, - params: &CreateParams, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< - core::result::Result, + >::Output, >, > where - E: Environment, + ContractRef: FromAccountId, Args: scale::Encode, Salt: AsRef<[u8]>, - ContractError: scale::Decode, + R: ConstructorReturnType, { - ink_env::instantiate_fallible_contract::(params) + ink_env::instantiate_contract::(params) } /// Invokes a contract message and returns its result. diff --git a/crates/ink/src/reflect/contract.rs b/crates/ink/src/reflect/contract.rs index 3488980a3cf..5c81a4f82e1 100644 --- a/crates/ink/src/reflect/contract.rs +++ b/crates/ink/src/reflect/contract.rs @@ -47,138 +47,3 @@ pub trait ContractName { /// The name of the ink! smart contract. const NAME: &'static str; } - -/// Stores the used host environment type of the ink! smart contract. -/// -/// # Note -/// -/// The used host environment can be altered using the `env` configuration -/// parameter in the `#[ink::contract]` parameters. For example if the user -/// wanted to use an environment type definition called `MyEnvironment` they -/// issue the ink! smart contract as follows: -/// -/// ```no_compile -/// #[ink::contract(env = MyEnvironment)] -/// ``` -/// -/// # Usage: Default Environment -/// -/// ``` -/// -/// #[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) {} -/// } -/// } -/// -/// use contract::Contract; -/// -/// # use ink::reflect::ContractEnv; -/// # use ink::codegen::utils::IsSameType; -/// -/// // The following line only compiles successfully if both -/// // `ink_env::DefaultEnvironment` and `::Env` -/// // are of the same type. -/// const _: IsSameType<::Env> = -/// >::new(); -/// ``` -/// -/// # Usage: Custom Environment -/// -/// ``` -/// # use ink_env::{Environment, DefaultEnvironment}; -/// -/// pub struct CustomEnvironment {} -/// -/// impl Environment for CustomEnvironment { -/// const MAX_EVENT_TOPICS: usize = 4; -/// -/// type AccountId = ::AccountId; -/// type Balance = u64; -/// type Hash = ::Hash; -/// type BlockNumber = u32; -/// type Timestamp = u64; -/// type ChainExtension = ::ChainExtension; -/// } -/// -/// #[ink::contract(env = super::CustomEnvironment)] -/// pub mod contract { -/// #[ink(storage)] -/// pub struct Contract {} -/// -/// impl Contract { -/// #[ink(constructor)] -/// pub fn constructor() -> Self { Self {} } -/// -/// #[ink(message)] -/// pub fn message(&self) {} -/// } -/// } -/// -/// use contract::Contract; -/// # use ink::reflect::ContractEnv; -/// # use ink::codegen::utils::IsSameType; -/// -/// // The following line only compiles successfully if both -/// // `CustomEnvironment` and `::Env` -/// // are of the same type. -/// const _: IsSameType<::Env> = -/// >::new(); -/// -/// fn main() {} -/// ``` -pub trait ContractEnv { - /// The environment type. - type Env: ::ink_env::Environment; -} - -/// Refers to the generated ink! smart contract reference type. -/// -/// # Note -/// -/// Given an ink! storage struct with identifier `Contract` the ink! codegen produces -/// the ink! root type `Contract` and the ink! reference type `ContractRef`. -/// -/// This trait exists so that users can avoid using a generated identifier to refer to -/// the generated reference type of the ink! smart contract. -/// -/// # Usage -/// -/// ``` -/// -/// #[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) {} -/// } -/// } -/// -/// use contract::{Contract, ContractRef}; -/// # use ink::codegen::utils::IsSameType; -/// # use ink::reflect::ContractReference; -/// -/// // The following line only compiles successfully if both -/// // `ContractReference` and `::Type` -/// // are of the same type. -/// const _: IsSameType<::Type> = -/// >::new(); -/// ``` -pub trait ContractReference { - /// The generated contract reference type. - type Type; -} diff --git a/crates/ink/src/reflect/mod.rs b/crates/ink/src/reflect/mod.rs index 65f15f2e6db..0ebdb294f24 100644 --- a/crates/ink/src/reflect/mod.rs +++ b/crates/ink/src/reflect/mod.rs @@ -29,11 +29,7 @@ mod event; mod trait_def; pub use self::{ - contract::{ - ContractEnv, - ContractName, - ContractReference, - }, + contract::ContractName, dispatch::{ ConstructorOutput, ConstructorOutputValue, diff --git a/crates/ink/src/reflect/trait_def/registry.rs b/crates/ink/src/reflect/trait_def/registry.rs index 005d8eb1cc4..933b70324c8 100644 --- a/crates/ink/src/reflect/trait_def/registry.rs +++ b/crates/ink/src/reflect/trait_def/registry.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::reflect::ContractEnv; use core::marker::PhantomData; +use ink_env::ContractEnv; /// Type that is guaranteed by ink! to implement all ink! trait definitions. /// diff --git a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr index 19d2d86df6d..0fcf8ce9f45 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr @@ -11,6 +11,24 @@ note: required by a bound in `return_value` | R: scale::Encode, | ^^^^^^^^^^^^^ required by this bound in `return_value` +error[E0277]: the trait bound `contract::Error: WrapperTypeDecode` is not satisfied + --> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9 + | +13 | pub fn constructor() -> Result { + | ^^^ the trait `WrapperTypeDecode` is not implemented for `contract::Error` + | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + = note: required for `contract::Error` to implement `parity_scale_codec::Decode` + = note: required for `Result` to implement `ConstructorReturnType` +note: required by a bound in `CreateBuilder::>>::returns` + --> $WORKSPACE/crates/env/src/call/create_builder.rs + | + | R: ConstructorReturnType, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `CreateBuilder::>>::returns` + error[E0277]: the trait bound `contract::Error: TypeInfo` is not satisfied --> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:4:16 | diff --git a/crates/ink/tests/ui/contract/pass/constructor-return-result-cross-contract.rs b/crates/ink/tests/ui/contract/pass/constructor-return-result-cross-contract.rs new file mode 100644 index 00000000000..984c053d8b6 --- /dev/null +++ b/crates/ink/tests/ui/contract/pass/constructor-return-result-cross-contract.rs @@ -0,0 +1,83 @@ +#[ink::contract] +mod contract_callee { + #[ink(storage)] + pub struct Callee {} + + #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] + #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))] + pub enum Error { + Foo, + } + + impl Callee { + #[ink(constructor)] + pub fn new_self() -> Self { + Self {} + } + + #[ink(constructor)] + pub fn new_storage_name() -> Callee { + Callee {} + } + + #[ink(constructor)] + pub fn new_result_self() -> Result { + Ok(Self {}) + } + + #[ink(constructor)] + pub fn new_result_storage_name() -> Result { + Ok(Callee {}) + } + + #[ink(message)] + pub fn message(&self) {} + } +} + +fn main() { + use contract_callee::{ + CalleeRef, + Error, + }; + + // fn new_self() -> Self + let _: fn() -> CalleeRef = || { + CalleeRef::new_self() + .code_hash(ink_primitives::Clear::CLEAR_HASH) + .gas_limit(4000) + .endowment(25) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate() + }; + + // fn new_storage_name() -> Callee + let _: fn() -> CalleeRef = || { + CalleeRef::new_storage_name() + .code_hash(ink_primitives::Clear::CLEAR_HASH) + .gas_limit(4000) + .endowment(25) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate() + }; + + // fn new_result_self() -> Result + let _: fn() -> Result = || { + CalleeRef::new_result_self() + .code_hash(ink_primitives::Clear::CLEAR_HASH) + .gas_limit(4000) + .endowment(25) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate() + }; + + // fn new_result_storage_name() -> Result + let _: fn() -> Result = || { + CalleeRef::new_result_self() + .code_hash(ink_primitives::Clear::CLEAR_HASH) + .gas_limit(4000) + .endowment(25) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate() + }; +} diff --git a/crates/ink/tests/ui/trait_def/pass/using-env-types.rs b/crates/ink/tests/ui/trait_def/pass/using-env-types.rs index 2822af913e4..e1f913f3099 100644 --- a/crates/ink/tests/ui/trait_def/pass/using-env-types.rs +++ b/crates/ink/tests/ui/trait_def/pass/using-env-types.rs @@ -1,4 +1,4 @@ -use ink::reflect::ContractEnv; +use ink::env::ContractEnv; use ink_env::Environment; #[ink::trait_definition] diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index 6b4638d01a1..fd2b80d35e6 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -17,10 +17,10 @@ #[ink::contract] mod call_builder { + use constructors_return_value::ConstructorsReturnValueRef; use ink::env::{ call::{ build_call, - build_create, Call, ExecutionInput, Selector, @@ -99,16 +99,16 @@ mod call_builder { selector: [u8; 4], init_value: bool, ) -> Option { - let result = build_create::() + let mut params = ConstructorsReturnValueRef::new(init_value) .code_hash(code_hash) .gas_limit(0) .endowment(0) - .exec_input( - ExecutionInput::new(Selector::new(selector)).push_arg(init_value), - ) .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) - .returns::() - .params() + .params(); + + params.update_selector(Selector::new(selector)); + + let result = params .try_instantiate() .expect("Error from the Contracts pallet."); @@ -140,20 +140,17 @@ mod call_builder { ink::LangError, >, > { - let lang_result = build_create::() + let mut params = ConstructorsReturnValueRef::try_new(init_value) .code_hash(code_hash) .gas_limit(0) .endowment(0) - .exec_input( - ExecutionInput::new(Selector::new(selector)).push_arg(init_value), - ) .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) - .returns::>() - .params() - .try_instantiate_fallible() + .params(); + + params.update_selector(Selector::new(selector)); + + let lang_result = params + .try_instantiate() .expect("Error from the Contracts pallet."); Some(lang_result.map(|contract_result| { diff --git a/examples/lang-err-integration-tests/constructors-return-value/lib.rs b/examples/lang-err-integration-tests/constructors-return-value/lib.rs index 018df58ddf9..111b1daa2c3 100644 --- a/examples/lang-err-integration-tests/constructors-return-value/lib.rs +++ b/examples/lang-err-integration-tests/constructors-return-value/lib.rs @@ -34,7 +34,7 @@ pub mod constructors_return_value { } } - /// A constructor which reverts and fills the output buffer with an erronenously encoded + /// A constructor which reverts and fills the output buffer with an erroneously encoded /// return value. #[ink(constructor)] pub fn revert_new(_init_value: bool) -> Self { @@ -44,7 +44,7 @@ pub mod constructors_return_value { ) } - /// A constructor which reverts and fills the output buffer with an erronenously encoded + /// A constructor which reverts and fills the output buffer with an erroneously encoded /// return value. #[ink(constructor)] pub fn try_revert_new(init_value: bool) -> Result { diff --git a/examples/lang-err-integration-tests/contract-ref/lib.rs b/examples/lang-err-integration-tests/contract-ref/lib.rs index b38d3ff3dd1..a6dac8aee35 100755 --- a/examples/lang-err-integration-tests/contract-ref/lib.rs +++ b/examples/lang-err-integration-tests/contract-ref/lib.rs @@ -29,7 +29,7 @@ mod contract_ref { .endowment(0) .code_hash(flipper_code_hash) .salt_bytes(salt) - .instantiate_fallible() + .instantiate() .unwrap_or_else(|error| { panic!( "Received an error from the Flipper constructor while instantiating \ diff --git a/examples/multisig/lib.rs b/examples/multisig/lib.rs index ea3b814e273..250db845d3c 100755 --- a/examples/multisig/lib.rs +++ b/examples/multisig/lib.rs @@ -535,7 +535,7 @@ mod multisig { self.ensure_confirmed(trans_id); let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); assert!(self.env().transferred_value() == t.transferred_value); - let result = build_call::<::Env>() + let result = build_call::<::Env>() .call_type( Call::new() .callee(t.callee) @@ -573,7 +573,7 @@ mod multisig { ) -> Result, Error> { self.ensure_confirmed(trans_id); let t = self.take_transaction(trans_id).expect(WRONG_TRANSACTION_ID); - let result = build_call::<::Env>() + let result = build_call::<::Env>() .call_type( Call::new() .callee(t.callee) From 8321e6fe43f9e895971271d5ac07e13b05a8d65c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 24 Jan 2023 19:24:40 +0000 Subject: [PATCH 18/18] `[ink_e2e]` method to generate and fund unique accounts (#1615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle `LangError` from instantiate (fails for success case) This commit lets us grab what's in the output buffer after our call to `instantiate`, however we are unable to succesfully decode the `AccountId` from the success case. * Change generic in `CreateBuilder` to be more consistent * Remove extra generic parameter I accidently introduced this not knowing that the generic `C` was for the return type * Remove generic return type parameter from `CreateBuidler` codegen * Hardcode assumption that `instantiate` returns a `ConstructorResult` * Update `CreateBuilder` codegen to just return `Self` * Remove generic usage to fix formatting * Unwrap `ConstructorResult` in `contract-ref` E2E test * Clean up some comments * Bring back the assumption that we expect an `AccountId` Is supposed to help give better error messages if somebody uses a wrong type with the builder * Remove unused method * Update doc tests for new builder pattern * Clean up some comments * Fix Clippy warning * Fix typo * Add `try_instantiate` method to `CreateBuilder` * Remove unneeded `unwrap` * Remove debug logging * Update doc test * Fix some typos Co-authored-by: Andrew Jones * Mention panicking behaviour of `instantiate` methods * Improve error messages from wrong `returns()` type * Actually check return values from `call_instantiate` * Add test showing a reverting constructor with `Ok` in error buffer * Check that we're only returning `LangError`s if the contract reverted * Clean up the manual encoding test a bit * Add test for constructors which return a contract level error * Add `CreateBuilder` message which calls a fallible constructor * Add test which calls falliable constructor for success case * Get verbose `instantiate_contract_with_result` decoding past typechecker * Add `try_instantiate_with_result` to `CreateBuilder` * Clean up decoding logic for output from `seal_instatiate` * Small cleanups in `call-builder` E2E tests * RustFmt `env_access` * Remove unused import * Flip decoding logic so that it's more strict initially Otherwise we may end up decoding a `Result` too eagerly and end up in a wrong branch. * Add test which revert a fallible constructor * Remove note about removing `assert` statement We still need this to prevent someone from manually encoding an `Ok` value into the return buffer. * Check return value from fallible constructor tests * Update E2E Builder typedef to match changes * Update E2E test for new call syntax * Use `selector_bytes!` macro in more places * Change order to accounts used in tests The tests started failing due to nonce issues, re-ordering the accounts seems to help with that. * Update function names to use `fallible` * Add note about docs * Update `ContractRef` codegen to use fallible constructor return types * Stop returning an `AccountId` directly from `CreateBuilder::try_instantiate_fallible` This matches the behaviour of the other intantiate methods * Add panicking version of `try_instantiate_fallible` * Add test for using fallible constructors through ContractRefs * Add test for instantiation failure too * Add `instantiate_fallible` to `CreateParams` * Add a couple of missing docs * Convert `call-builder` test return type to `AccountId` * Extract reverted fallible constructor fn for testing * Fmt * Add fallible_constructor_reverted_lang_error FAILs * Rename tests * Add test for a decode error * Rename some tests * Make `Result` types more explicit * Add another test * Clean up decoding match statement * Andrew was right * Small cleanups to naming and imports * Couple more import and comment fixes * Use decode trait method directly * Fix `call-builder` E2E tests This now accounts for the better error handling in the `CalleeReverted` case * Remove leading colons from non-codegen contexts * Add doc test for `instantiate_fallible_contract` * Add doc test to `build_create` function * Remove leftover trait bound We can't use this with fallible constructors * Remove a few more leading colons * Panic in case where we get `Ok` encoded into error buffer * Add some links to env docs * Add more docs to `call-builder` E2E tests * Use correct path in `call-builder` docs * Remove fallible create_builder.rs methods * WIP experiment * InstantiateResult blanket impl for T and Result * Introduce ContractRef type parameter * Fix up env access * WIP... * Make it compile * Add ContractStorage parameter * Remove commented out instantiate_fallible_contract * Convert to env Error in helper * Return Decode errors in case of invalid Result first byte * Fix impls::instantiate_contract * Remove ContractStorage generic parameter * Fix env access * Use generated constructor ref, introduces update_selector * Fix e2e * Use return_value() method in e2e test * Remove commented out code * Typos * Clippy * Rename some instantiate_fallible * Restore `returns` method * Remove ContractReference Result impl * WIP implementing ConstructorReturnType * Reorder ContractRef parameter, move ContractRef and ContractEnv trait to env crate * Fmt and fix * Remove E param from build_create * Fix up build_create * Fix up e2e creat builder * Implement ContstructorReturnType for the storage_ident * Fmt * Fix envaccess test * Fully qualify Result in macro * More fully qualify Result in macro * Fix up build_create examples * Add test for different combos of Self and struct name * Fix ui test * Fmt * Remove unused assoc type * Change error fn to return Option * Remove commented out code * Fmt * Fix `call-builder` E2E test compilation * Fix `contract-ref` E2E test compilation * ConstructorReturnType comments * Fix up return types after merge * Fmt * Clippy * Fix create_builder tests * Fix some of the comment links * Unwrap errors from default `instantiate_fallible` codepath * Fix `contract-ref` E2E test * Wrap long line * Remove TODO * Fix instatiation doc test * Fix cross-contract compile test * Clean up some comments * Fix `contract-ref` compilation * Remove outdated doc * Update comment * Another comment fix * Bump `contract-metadata` Fixes some inconsistent errors between Clippy and `rustc` * Remove fallible create_builder.rs methods * WIP experiment * InstantiateResult blanket impl for T and Result * Introduce ContractRef type parameter * Fix up env access * WIP... * Make it compile * Add ContractStorage parameter * Remove commented out instantiate_fallible_contract * Convert to env Error in helper * Return Decode errors in case of invalid Result first byte * Fix impls::instantiate_contract * Remove ContractStorage generic parameter * Fix env access * Use generated constructor ref, introduces update_selector * Fix e2e * Remove commented out code * Typos * Clippy * Rename some instantiate_fallible * Restore `returns` method * Remove ContractReference Result impl * WIP implementing ConstructorReturnType * Reorder ContractRef parameter, move ContractRef and ContractEnv trait to env crate * Fmt and fix * Remove E param from build_create * Fix up build_create * Fix up e2e creat builder * Implement ContstructorReturnType for the storage_ident * Fmt * Fix envaccess test * Fully qualify Result in macro * More fully qualify Result in macro * Fix up build_create examples * Add test for different combos of Self and struct name * Fix ui test * Fmt * Remove unused assoc type * Change error fn to return Option * Remove commented out code * Fmt * ConstructorReturnType comments * Fix `contract-ref` E2E test compilation * Fix up return types after merge * Fmt * Clippy * Fix create_builder tests * Fix cross-contract compile test * Clean up some comments * Remove outdated doc * Update comment * Another comment fix * Wrap long line * Remove TODO * Bump `contract-metadata` Fixes some inconsistent errors between Clippy and `rustc` * Fix `CreateBuilder` compilation * Fix one of the doc tests * Clean up doc tests a bit * WIP create accounts * Try transfer balance * WIP try creating and funding account for single test. * Update all tests to use create_and_fund_account * Fix error * Fmt * SP * Clippy * Remove commented out code * Update crates/e2e/src/xts.rs Co-authored-by: Michael Müller Co-authored-by: Hernando Castano Co-authored-by: Hernando Castano Co-authored-by: Michael Müller --- crates/e2e/src/client.rs | 61 ++++++++-- crates/e2e/src/xts.rs | 58 +++++++++- .../call-builder/lib.rs | 106 +++++++++++------- 3 files changed, 171 insertions(+), 54 deletions(-) diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index d38433e6e4c..5dd976b1917 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -21,10 +21,6 @@ use super::{ log_error, log_info, sr25519, - xts::{ - Call, - InstantiateWithCode, - }, CodeUploadResult, ContractExecResult, ContractInstantiateResult, @@ -35,6 +31,7 @@ use contract_metadata::ContractMetadata; use ink_env::Environment; use ink_primitives::MessageResult; +use sp_core::Pair; use sp_runtime::traits::{ IdentifyAccount, Verify, @@ -55,7 +52,10 @@ use subxt::{ ValueDef, }, }, - tx::ExtrinsicParams, + tx::{ + ExtrinsicParams, + PairSigner, + }, }; /// Result of a contract instantiation. @@ -300,11 +300,8 @@ where E: Environment, E::AccountId: Debug, - E::Balance: Debug + scale::Encode + serde::Serialize, + E::Balance: Debug + scale::HasCompact + serde::Serialize, E::Hash: Debug + scale::Encode, - - Call: scale::Encode, - InstantiateWithCode: scale::Encode, { /// Creates a new [`Client`] instance. pub async fn new(url: &str, contracts: impl IntoIterator) -> Self { @@ -342,6 +339,52 @@ where } } + /// Generate a new keypair and fund with the given amount from the origin account. + /// + /// Because many tests may execute this in parallel, transfers may fail due to a race condition + /// with account indices. Therefore this will reattempt transfers a number of times. + pub async fn create_and_fund_account( + &self, + origin: &Signer, + amount: E::Balance, + ) -> Signer + where + E::Balance: Clone, + C::AccountId: Clone + core::fmt::Display, + { + let (pair, _, _) = ::generate_with_phrase(None); + let account_id = + ::Signer::from(pair.public()).into_account(); + + for _ in 0..6 { + let transfer_result = self + .api + .try_transfer_balance(origin, account_id.clone(), amount) + .await; + match transfer_result { + Ok(_) => { + log_info(&format!( + "transfer from {} to {} succeeded", + origin.account_id(), + account_id, + )); + break + } + Err(err) => { + log_info(&format!( + "transfer from {} to {} failed with {:?}", + origin.account_id(), + account_id, + err + )); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + } + } + + PairSigner::new(pair) + } + /// This function extracts the metadata of the contract at the file path /// `target/ink/$contract_name.contract`. /// diff --git a/crates/e2e/src/xts.rs b/crates/e2e/src/xts.rs index 9fea3e270f2..eab05f73ae0 100644 --- a/crates/e2e/src/xts.rs +++ b/crates/e2e/src/xts.rs @@ -68,6 +68,25 @@ pub struct Call { data: Vec, } +/// A raw call to `pallet-contracts`'s `call`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct Call2 { + dest: sp_runtime::MultiAddress, + #[codec(compact)] + value: B, + gas_limit: Weight, + storage_deposit_limit: Option, + data: Vec, +} + +/// A raw call to `pallet-contracts`'s `call`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct Transfer { + dest: C::Address, + #[codec(compact)] + value: E::Balance, +} + #[derive( Debug, Clone, Copy, scale::Encode, scale::Decode, PartialEq, Eq, serde::Serialize, )] @@ -167,10 +186,7 @@ where sr25519::Signature: Into, E: Environment, - E::Balance: scale::Encode + serde::Serialize, - - Call: scale::Encode, - InstantiateWithCode: scale::Encode, + E::Balance: scale::HasCompact + serde::Serialize, { /// Creates a new [`ContractsApi`] instance. pub async fn new(client: OnlineClient, url: &str) -> Self { @@ -189,6 +205,40 @@ where } } + /// Attempt to transfer the `value` from `origin` to `dest`. + /// + /// Returns `Ok` on success, and a [`subxt::Error`] if the extrinsic is + /// invalid (e.g. out of date nonce) + pub async fn try_transfer_balance( + &self, + origin: &Signer, + dest: C::AccountId, + value: E::Balance, + ) -> Result<(), subxt::Error> { + let call = subxt::tx::StaticTxPayload::new( + "Balances", + "transfer", + Transfer:: { + dest: dest.into(), + value, + }, + Default::default(), + ) + .unvalidated(); + + let tx_progress = self + .client + .tx() + .sign_and_submit_then_watch_default(&call, origin) + .await?; + + tx_progress.wait_for_in_block().await.unwrap_or_else(|err| { + panic!("error on call `wait_for_in_block`: {:?}", err); + }); + + Ok(()) + } + /// Dry runs the instantiation of the given `code`. pub async fn instantiate_with_code_dry_run( &self, diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index fd2b80d35e6..874138c29ed 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -173,22 +173,20 @@ mod call_builder { async fn e2e_invalid_message_selector_can_be_handled( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let flipper_constructor = FlipperRef::new_default(); let flipper_acc_id = client - .instantiate( - "integration_flipper", - &ink_e2e::alice(), - flipper_constructor, - 0, - None, - ) + .instantiate("integration_flipper", &origin, flipper_constructor, 0, None) .await .expect("instantiate `flipper` failed") .account_id; @@ -196,7 +194,7 @@ mod call_builder { let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::alice(), flipper_get, 0, None) + .call(&origin, flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let initial_value = get_call_result.return_value(); @@ -205,7 +203,7 @@ mod call_builder { let call = build_message::(contract_acc_id) .call(|contract| contract.call(flipper_acc_id, selector)); let call_result = client - .call(&ink_e2e::alice(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call` failed"); @@ -219,7 +217,7 @@ mod call_builder { let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::alice(), flipper_get, 0, None) + .call(&origin, flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let flipped_value = get_call_result.return_value(); @@ -232,22 +230,20 @@ mod call_builder { async fn e2e_invalid_message_selector_panics_on_invoke( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let flipper_constructor = FlipperRef::new_default(); let flipper_acc_id = client - .instantiate( - "integration_flipper", - &ink_e2e::ferdie(), - flipper_constructor, - 0, - None, - ) + .instantiate("integration_flipper", &origin, flipper_constructor, 0, None) .await .expect("instantiate `flipper` failed") .account_id; @@ -257,7 +253,7 @@ mod call_builder { let invalid_selector = [0x00, 0x00, 0x00, 0x00]; let call = build_message::(contract_acc_id) .call(|contract| contract.invoke(flipper_acc_id, invalid_selector)); - let call_result = client.call(&ink_e2e::ferdie(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!(call_result.is_err()); let contains_err_msg = match call_result.unwrap_err() { @@ -276,15 +272,19 @@ mod call_builder { async fn e2e_create_builder_works_with_valid_selector( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::bob(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -296,7 +296,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::bob(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); @@ -313,15 +313,19 @@ mod call_builder { async fn e2e_create_builder_fails_with_invalid_selector( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::charlie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::charlie(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -333,7 +337,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::charlie(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); @@ -350,15 +354,19 @@ mod call_builder { async fn e2e_create_builder_with_infallible_revert_constructor_encodes_ok( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::dave(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::dave(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -370,7 +378,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); - let call_result = client.call(&mut ink_e2e::dave(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!( call_result.is_err(), "Call execution should've failed, but didn't." @@ -394,15 +402,19 @@ mod call_builder { async fn e2e_create_builder_can_handle_fallible_constructor_success( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::eve(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::eve(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -414,7 +426,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::eve(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call_instantiate_fallible` failed") .return_value(); @@ -431,15 +443,19 @@ mod call_builder { async fn e2e_create_builder_can_handle_fallible_constructor_error( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::ferdie(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -451,7 +467,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::ferdie(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call_instantiate_fallible` failed") .return_value(); @@ -475,15 +491,19 @@ mod call_builder { async fn e2e_create_builder_with_fallible_revert_constructor_encodes_ok( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::alice(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -494,7 +514,7 @@ mod call_builder { build_message::(contract_acc_id).call(|contract| { contract.call_instantiate_fallible(code_hash, selector, init_value) }); - let call_result = client.call(&mut ink_e2e::alice(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!( call_result.is_err(), @@ -520,15 +540,19 @@ mod call_builder { async fn e2e_create_builder_with_fallible_revert_constructor_encodes_err( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::bob(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -540,7 +564,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::bob(), call, 0, None) + .call(&origin, call, 0, None) .await .expect( "Client failed to call `call_builder::call_instantiate_fallible`.",