From dfcfd85522f0bc0bd2522f4701073fb5ae969074 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 8 Feb 2024 12:32:30 +0000 Subject: [PATCH] `call_v2` cross-contract calls with additional limit parameters (#2077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init call_v2 methods and types * add e2e tests to basic-contract-caller example * Add storage_deposit * Fix basic-contract-caller e2e tests * Remove `basic_contract_caller` integration test, moved to #1909 * WIP adding cross_contract_calls test * Add `integration-test` for possible migration pattern (#1909) * WIP * Update versions * WIP * WIP migration * WIP * Make test pass * Move e2e tests mod to own file * Update comment * Update example for new e2e API * Update integration-tests/upgradeable-contracts/set-code-hash-migration/lib.rs Co-authored-by: Michael Müller * Top level gitignore * Fix tests update comments * Update upgradeable contracts README.md * spelling --------- Co-authored-by: Michael Müller * Add `basic-contract-caller` E2E test (#2085) * add e2e tests to basic-contract-caller example * Fix basic-contract-caller e2e tests * Remove `call_builder` change * Remove `basic_contract_caller` integration test, moved to #1909 * Revert "Remove `basic_contract_caller` integration test, moved to #1909" This reverts commit 8f3ab318035735545af513893556658b709c5244. * fmt * Only need one .gitignore * WIP adding create v2 API * Revert "WIP adding create v2 API" This reverts commit bd83e1861c0db2af428e6b10f9a1a44d318b40d5. * WIP e2e tests * Add CallV2 builder methods * Pass weight limit as params * Allow deprecated * Add storage_deposit_limit * Clippy * Use struct update syntax * Remove space * CHANGELOG * fmt * Import OtherContract directly instead of reexporting * Make other_contract pub * Revert prev * docs * top level gitignore for integration-tests * Remove unused setters * Use ContractRef * integration-test comments * Rename to `call_v2` * Comments and builder method * SP * comments * fix doc test --------- Co-authored-by: Michael Müller --- .config/cargo_spellcheck.dic | 2 + CHANGELOG.md | 1 + crates/env/src/api.rs | 36 ++ crates/env/src/backend.rs | 19 + crates/env/src/call/call_builder.rs | 352 +++++++++++++++++- crates/env/src/call/mod.rs | 1 + crates/env/src/engine/off_chain/impls.rs | 13 + crates/env/src/engine/on_chain/impls.rs | 52 ++- crates/ink/src/env_access.rs | 75 +++- integration-tests/.gitignore | 2 + .../basic-contract-caller/.gitignore | 9 - .../basic-contract-caller/e2e_tests.rs | 35 -- .../basic-contract-caller/lib.rs | 44 --- .../other-contract/.gitignore | 9 - .../Cargo.toml | 2 +- .../cross-contract-calls/e2e_tests.rs | 84 +++++ integration-tests/cross-contract-calls/lib.rs | 80 ++++ .../other-contract/Cargo.toml | 0 .../other-contract/lib.rs | 9 +- integration-tests/erc1155/lib.rs | 2 +- integration-tests/multisig/lib.rs | 14 +- 21 files changed, 707 insertions(+), 134 deletions(-) create mode 100755 integration-tests/.gitignore delete mode 100755 integration-tests/basic-contract-caller/.gitignore delete mode 100644 integration-tests/basic-contract-caller/e2e_tests.rs delete mode 100755 integration-tests/basic-contract-caller/lib.rs delete mode 100755 integration-tests/basic-contract-caller/other-contract/.gitignore rename integration-tests/{basic-contract-caller => cross-contract-calls}/Cargo.toml (96%) create mode 100644 integration-tests/cross-contract-calls/e2e_tests.rs create mode 100755 integration-tests/cross-contract-calls/lib.rs rename integration-tests/{basic-contract-caller => cross-contract-calls}/other-contract/Cargo.toml (100%) rename integration-tests/{basic-contract-caller => cross-contract-calls}/other-contract/lib.rs (68%) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 5d6a9cfb7be..4831da94b3e 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -109,6 +109,8 @@ instantiation/S layout/JG namespace/S parameterize/SD +parachain/S +picosecond/S runtime/S storable struct/S diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c19452bc3..ce2d2283b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Linter] `non_fallible_api` lint - [#2004](https://github.com/paritytech/ink/pull/2004) - [Linter] Publish the linting crates on crates.io - [#2060](https://github.com/paritytech/ink/pull/2060) - [E2E] Added `create_call_builder` for testing existing contracts - [#2075](https://github.com/paritytech/ink/pull/2075) +- `call_v2` cross-contract calls with additional limit parameters - [#2077](https://github.com/paritytech/ink/pull/2077) ### Changed - `Mapping`: Reflect all possible failure cases in comments ‒ [#2079](https://github.com/paritytech/ink/pull/2079) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 61682408185..daac732e899 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -22,6 +22,7 @@ use crate::{ call::{ Call, CallParams, + CallV1, ConstructorReturnType, CreateParams, DelegateCall, @@ -265,6 +266,9 @@ where /// This is a low level way to evaluate another smart contract. /// Prefer to use the ink! guided and type safe approach to using this. /// +/// **This will call into the original version of the host function. It is recommended to +/// use [`invoke_contract`] to use the latest version if the target runtime supports it.** +/// /// # Errors /// /// - If the called account does not exist. @@ -273,6 +277,38 @@ 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_v1( + params: &CallParams, Args, R>, +) -> Result> +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + ::on_instance(|instance| { + TypedEnvBackend::invoke_contract_v1::(instance, params) + }) +} + +/// Invokes a contract message and returns its result. +/// +/// # Note +/// +/// **This will call into the latest version of the host function which allows setting new +/// weight and storage limit parameters.** +/// +/// This is a low level way to evaluate another smart contract. +/// Prefer to use the ink! guided and type safe approach to using this. +/// +/// # Errors +/// +/// - If the called account does not exist. +/// - If the called account is not a contract. +/// - If arguments passed to the called contract message are invalid. +/// - If the called contract execution has trapped. +/// - If the called contract ran out of gas, proof time, or storage deposit upon +/// execution. +/// - If the returned value failed to decode properly. pub fn invoke_contract( params: &CallParams, Args, R>, ) -> Result> diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index d5da075d807..43597082b37 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -16,6 +16,7 @@ use crate::{ call::{ Call, CallParams, + CallV1, ConstructorReturnType, CreateParams, DelegateCall, @@ -294,6 +295,24 @@ pub trait TypedEnvBackend: EnvBackend { /// /// # Note /// + /// **This will call into the original `call` host function.** + /// + /// For more details visit: [`invoke_contract`][`crate::invoke_contract_v1`] + fn invoke_contract_v1( + &mut self, + call_data: &CallParams, Args, R>, + ) -> Result> + where + E: Environment, + Args: scale::Encode, + R: scale::Decode; + + /// Invokes a contract message and returns its result. + /// + /// # Note + /// + /// **This will call into the latest `call_v2` host function.** + /// /// For more details visit: [`invoke_contract`][`crate::invoke_contract`] fn invoke_contract( &mut self, diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index 55d98219bb4..f5c31f8f58c 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -65,7 +65,7 @@ where } } -impl CallParams, Args, R> +impl CallParams, Args, R> where E: Environment, { @@ -88,6 +88,41 @@ where } } +impl CallParams, Args, R> +where + E: Environment, +{ + /// Returns the account ID of the called contract instance. + #[inline] + pub fn callee(&self) -> &E::AccountId { + &self.call_type.callee + } + + /// Returns the chosen ref time limit for the called contract execution. + #[inline] + pub fn ref_time_limit(&self) -> u64 { + self.call_type.ref_time_limit + } + + /// Returns the chosen proof time limit for the called contract execution. + #[inline] + pub fn proof_time_limit(&self) -> u64 { + self.call_type.proof_time_limit + } + + /// Returns the chosen storage deposit limit for the called contract execution. + #[inline] + pub fn storage_deposit_limit(&self) -> Option<&E::Balance> { + self.call_type.storage_deposit_limit.as_ref() + } + + /// Returns the transferred value for the called contract. + #[inline] + pub fn transferred_value(&self) -> &E::Balance { + &self.call_type.transferred_value + } +} + impl CallParams, Args, R> where E: Environment, @@ -99,6 +134,45 @@ where } } +impl CallParams, Args, R> +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + /// 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::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_v1(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. + /// + /// Returns the result of the contract execution. + /// + /// # 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. + pub fn try_invoke(&self) -> Result, crate::Error> { + crate::invoke_contract_v1(self) + } +} + impl CallParams, Args, R> where E: Environment, @@ -204,11 +278,11 @@ where /// # DefaultEnvironment, /// # call::{build_call, Selector, ExecutionInput} /// # }; -/// # use ink_env::call::Call; +/// # use ink_env::call::CallV1; /// # type AccountId = ::AccountId; /// # type Balance = ::Balance; /// build_call::() -/// .call(AccountId::from([0x42; 32])) +/// .call_v1(AccountId::from([0x42; 32])) /// .gas_limit(5000) /// .transferred_value(10) /// .exec_input( @@ -236,11 +310,11 @@ where /// # use ::ink_env::{ /// # Environment, /// # DefaultEnvironment, -/// # call::{build_call, Selector, ExecutionInput, Call}, +/// # call::{build_call, Selector, ExecutionInput, CallV1}, /// # }; /// # type AccountId = ::AccountId; /// let my_return_value: i32 = build_call::() -/// .call_type(Call::new(AccountId::from([0x42; 32]))) +/// .call_type(CallV1::new(AccountId::from([0x42; 32]))) /// .gas_limit(5000) /// .transferred_value(10) /// .exec_input( @@ -300,11 +374,11 @@ where /// # DefaultEnvironment, /// # call::{build_call, Selector, ExecutionInput} /// # }; -/// # use ink_env::call::Call; +/// # use ink_env::call::CallV1; /// # type AccountId = ::AccountId; /// # type Balance = ::Balance; /// let call_result = build_call::() -/// .call(AccountId::from([0x42; 32])) +/// .call_v1(AccountId::from([0x42; 32])) /// .gas_limit(5000) /// .transferred_value(10) /// .try_invoke() @@ -319,7 +393,7 @@ where #[allow(clippy::type_complexity)] pub fn build_call() -> CallBuilder< E, - Unset>, + Unset>, Unset>, Unset>, > @@ -335,17 +409,19 @@ where } } -/// The default call type for cross-contract calls. Performs a cross-contract call to +/// The legacy call type for cross-contract calls. Performs a cross-contract call to /// `callee` with gas limit `gas_limit`, transferring `transferred_value` of currency. +/// +/// Calls into the original `call` host function. #[derive(Clone)] -pub struct Call { +pub struct CallV1 { callee: E::AccountId, gas_limit: Gas, transferred_value: E::Balance, } -impl Call { - /// Returns a clean builder for [`Call`]. +impl CallV1 { + /// Returns a clean builder for [`CallV1`]. pub fn new(callee: E::AccountId) -> Self { Self { callee, @@ -355,13 +431,13 @@ impl Call { } } -impl Call +impl CallV1 where E: Environment, { /// Sets the `gas_limit` for the current cross-contract call. pub fn gas_limit(self, gas_limit: Gas) -> Self { - Call { + CallV1 { callee: self.callee, gas_limit, transferred_value: self.transferred_value, @@ -370,7 +446,7 @@ where /// Sets the `transferred_value` for the current cross-contract call. pub fn transferred_value(self, transferred_value: E::Balance) -> Self { - Call { + CallV1 { callee: self.callee, gas_limit: self.gas_limit, transferred_value, @@ -378,6 +454,31 @@ where } } +/// The default call type for cross-contract calls, for calling into the latest `call_v2` +/// host function. This adds the additional weight limit parameter `proof_time_limit` as +/// well as `storage_deposit_limit`. +#[derive(Clone)] +pub struct Call { + callee: E::AccountId, + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Option, + transferred_value: E::Balance, +} + +impl Call { + /// Returns a clean builder for [`CallV1`]. + pub fn new(callee: E::AccountId) -> Self { + Self { + callee, + ref_time_limit: Default::default(), + proof_time_limit: Default::default(), + storage_deposit_limit: None, + transferred_value: E::Balance::zero(), + } + } +} + /// The `delegatecall` call type. Performs a call with the given code hash. #[derive(Clone)] pub struct DelegateCall { @@ -500,7 +601,23 @@ impl CallBuilder, Args, RetType> where E: Environment, { - /// Prepares the `CallBuilder` for a cross-contract [`Call`]. + /// Prepares the `CallBuilder` for a cross-contract [`CallV1`], calling into the + /// original `call` host function. + pub fn call_v1( + self, + callee: E::AccountId, + ) -> CallBuilder>, Args, RetType> { + CallBuilder { + call_type: Set(CallV1::new(callee)), + call_flags: self.call_flags, + exec_input: self.exec_input, + return_type: self.return_type, + _phantom: Default::default(), + } + } + + /// Prepares the `CallBuilder` for a cross-contract [`Call`] to the latest `call_v2` + /// host function. pub fn call( self, callee: E::AccountId, @@ -529,7 +646,7 @@ where } } -impl CallBuilder>, Args, RetType> +impl CallBuilder>, Args, RetType> where E: Environment, { @@ -537,7 +654,7 @@ where pub fn gas_limit(self, gas_limit: Gas) -> Self { let call_type = self.call_type.value(); CallBuilder { - call_type: Set(Call { + call_type: Set(CallV1 { callee: call_type.callee, gas_limit, transferred_value: call_type.transferred_value, @@ -553,7 +670,7 @@ where pub fn transferred_value(self, transferred_value: E::Balance) -> Self { let call_type = self.call_type.value(); CallBuilder { - call_type: Set(Call { + call_type: Set(CallV1 { callee: call_type.callee, gas_limit: call_type.gas_limit, transferred_value, @@ -566,6 +683,101 @@ where } } +impl CallBuilder>, Args, RetType> +where + E: Environment, +{ + /// Switch to the original `call` host function API, which only allows the `gas_limit` + /// limit parameter (equivalent to the `ref_time_limit` in the latest `call_v2`). + /// + /// This method instance is used to allow usage of the generated call builder methods + /// for messages which initialize the builder with the original [`CallV1`] type. + pub fn call_v1(self) -> CallBuilder>, Args, RetType> { + let call_type = self.call_type.value(); + CallBuilder { + call_type: Set(CallV1 { + callee: call_type.callee, + gas_limit: call_type.ref_time_limit, + transferred_value: call_type.transferred_value, + }), + call_flags: self.call_flags, + exec_input: self.exec_input, + return_type: self.return_type, + _phantom: Default::default(), + } + } + + /// Sets the `ref_time_limit` part of the weight limit for the current cross-contract + /// call. + /// + /// `ref_time` refers to the amount of computational time that can be + /// used for execution, in picoseconds. You can find more info + /// [here](https://use.ink/basics/cross-contract-calling/). + pub fn ref_time_limit(self, ref_time_limit: Gas) -> Self { + let call_type = self.call_type.value(); + CallBuilder { + call_type: Set(Call { + ref_time_limit, + ..call_type + }), + ..self + } + } + + /// Sets the `proof_time_limit` part of the weight limit for the current + /// cross-contract call. + /// + /// `proof_time` refers to the amount of storage in bytes that a transaction + /// is allowed to read. You can find more info + /// [here](https://use.ink/basics/cross-contract-calling/). + /// + /// **Note** + /// + /// This limit is only relevant for parachains, not for standalone chains which do not + /// require sending a Proof-of-validity to the relay chain. + pub fn proof_time_limit(self, proof_time_limit: Gas) -> Self { + let call_type = self.call_type.value(); + CallBuilder { + call_type: Set(Call { + proof_time_limit, + ..call_type + }), + ..self + } + } + + /// Sets the `storage_deposit_limit` for the current cross-contract call. + /// + /// The `storage_deposit_limit` specifies the amount of user funds that + /// can be charged for creating storage. You can find more info + /// [here](https://use.ink/basics/cross-contract-calling/). + pub fn storage_deposit_limit(self, storage_deposit_limit: E::Balance) -> Self { + let call_type = self.call_type.value(); + CallBuilder { + call_type: Set(Call { + storage_deposit_limit: Some(storage_deposit_limit), + ..call_type + }), + ..self + } + } + + /// Sets the `transferred_value` for the current cross-contract call. + /// + /// This value specifies the amount of user funds that are transferred + /// to the other contract with this call. + pub fn transferred_value(self, transferred_value: E::Balance) -> Self { + let call_type = self.call_type.value(); + CallBuilder { + call_type: Set(Call { + transferred_value, + ..call_type + }), + ..self + } + } +} + impl CallBuilder>, Args, RetType> where E: Environment, @@ -582,6 +794,23 @@ where } } +impl + CallBuilder>, Set>, Set>> +where + E: Environment, +{ + /// Finalizes the call builder to call a function. + pub fn params(self) -> CallParams, Args, RetType> { + CallParams { + call_type: self.call_type.value(), + call_flags: self.call_flags, + _return_type: Default::default(), + exec_input: self.exec_input.value(), + _phantom: self._phantom, + } + } +} + impl CallBuilder>, Set>, Set>> where @@ -621,6 +850,28 @@ where } } +impl + CallBuilder< + E, + Set>, + Unset>, + Unset, + > +where + E: Environment, +{ + /// Finalizes the call builder to call a function. + pub fn params(self) -> CallParams, EmptyArgumentList, ()> { + CallParams { + call_type: self.call_type.value(), + call_flags: self.call_flags, + _return_type: Default::default(), + exec_input: Default::default(), + _phantom: self._phantom, + } + } +} + impl CallBuilder>, Unset>, Unset> where @@ -660,6 +911,39 @@ where } } +impl + CallBuilder< + E, + Set>, + Unset>, + Unset>, + > +where + E: Environment, +{ + /// Invokes the cross-chain function call. + /// + /// # 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_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) { + self.params().invoke() + } + + /// Invokes the cross-chain function call. + /// + /// # 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. + pub fn try_invoke(self) -> Result, Error> { + self.params().try_invoke() + } +} + impl CallBuilder< E, @@ -725,6 +1009,36 @@ where } } +impl + CallBuilder>, Set>, Set>> +where + E: Environment, + Args: scale::Encode, + R: scale::Decode, +{ + /// Invokes the cross-chain function call and returns the result. + /// + /// # 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_invoke`][`CallBuilder::try_invoke`] method instead. + pub fn invoke(self) -> R { + self.params().invoke() + } + + /// Invokes the cross-chain function call and returns the result. + /// + /// # 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. + pub fn try_invoke(self) -> Result, Error> { + self.params().try_invoke() + } +} + impl CallBuilder>, Set>, Set>> where diff --git a/crates/env/src/call/mod.rs b/crates/env/src/call/mod.rs index a7979214002..a3f97ec5de5 100644 --- a/crates/env/src/call/mod.rs +++ b/crates/env/src/call/mod.rs @@ -45,6 +45,7 @@ pub use self::{ Call, CallBuilder, CallParams, + CallV1, DelegateCall, }, create_builder::{ diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index df1a3321028..2e567004f2b 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -17,6 +17,7 @@ use crate::{ call::{ Call, CallParams, + CallV1, ConstructorReturnType, CreateParams, DelegateCall, @@ -436,6 +437,18 @@ impl TypedEnvBackend for EnvInstance { self.engine.deposit_event(&enc_topics[..], enc_data); } + fn invoke_contract_v1( + &mut self, + _params: &CallParams, Args, R>, + ) -> Result> + where + E: Environment, + Args: scale::Encode, + R: scale::Decode, + { + unimplemented!("off-chain environment does not support contract invocation") + } + fn invoke_contract( &mut self, _params: &CallParams, Args, R>, diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index b1c39c37794..ce36767ab90 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -20,6 +20,7 @@ use crate::{ call::{ Call, CallParams, + CallV1, ConstructorReturnType, CreateParams, DelegateCall, @@ -423,9 +424,9 @@ impl TypedEnvBackend for EnvInstance { ext::deposit_event(enc_topics, enc_data); } - fn invoke_contract( + fn invoke_contract_v1( &mut self, - params: &CallParams, Args, R>, + params: &CallParams, Args, R>, ) -> Result> where E: Environment, @@ -463,6 +464,53 @@ impl TypedEnvBackend for EnvInstance { } } + fn invoke_contract( + &mut self, + params: &CallParams, Args, R>, + ) -> Result> + where + E: Environment, + Args: scale::Encode, + R: scale::Decode, + { + let mut scope = self.scoped_buffer(); + let ref_time_limit = params.ref_time_limit(); + let proof_time_limit = params.proof_time_limit(); + let storage_deposit_limit = params + .storage_deposit_limit() + .map(|limit| &*scope.take_encoded(limit)); + let enc_callee = scope.take_encoded(params.callee()); + let enc_transferred_value = scope.take_encoded(params.transferred_value()); + let call_flags = params.call_flags(); + let enc_input = if !call_flags.contains(CallFlags::FORWARD_INPUT) + && !call_flags.contains(CallFlags::CLONE_INPUT) + { + scope.take_encoded(params.exec_input()) + } else { + &mut [] + }; + let output = &mut scope.take_rest(); + let flags = params.call_flags(); + #[allow(deprecated)] + let call_result = ext::call_v2( + *flags, + enc_callee, + ref_time_limit, + proof_time_limit, + storage_deposit_limit, + enc_transferred_value, + enc_input, + Some(output), + ); + match call_result { + Ok(()) | Err(ReturnErrorCode::CalleeReverted) => { + let decoded = scale::DecodeAll::decode_all(&mut &output[..])?; + Ok(decoded) + } + Err(actual_error) => Err(actual_error.into()), + } + } + fn invoke_contract_delegate( &mut self, params: &CallParams, Args, R>, diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index a7a705882d8..7dc17992ae9 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -18,6 +18,7 @@ use ink_env::{ call::{ Call, CallParams, + CallV1, ConstructorReturnType, CreateParams, DelegateCall, @@ -517,7 +518,7 @@ where /// use ink::env::{ /// call::{ /// build_call, - /// Call, + /// CallV1, /// ExecutionInput, /// Selector, /// }, @@ -539,7 +540,7 @@ where /// pub fn invoke_contract(&self) -> i32 { /// let call_params = build_call::() /// .call_type( - /// Call::new(AccountId::from([0x42; 32])) + /// CallV1::new(AccountId::from([0x42; 32])) /// .gas_limit(5000) /// .transferred_value(10), /// ) @@ -553,6 +554,76 @@ where /// .params(); /// /// self.env() + /// .invoke_contract_v1(&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)) + /// } + /// # + /// # } + /// # } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::invoke_contract_v1`] + pub fn invoke_contract_v1( + self, + params: &CallParams, Args, R>, + ) -> Result> + where + Args: scale::Encode, + R: scale::Decode, + { + ink_env::invoke_contract_v1::(params) + } + + /// Invokes a contract message and returns its result. + /// + /// # Example + /// + /// ``` + /// # #[ink::contract] + /// # pub mod my_contract { + /// use ink::env::{ + /// call::{ + /// build_call, + /// CallV1, + /// ExecutionInput, + /// Selector, + /// }, + /// DefaultEnvironment, + /// }; + /// + /// # + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// /// Invokes a contract message and fetches the result. + /// #[ink(message)] + /// pub fn invoke_contract_v2(&self) -> i32 { + /// let call_params = build_call::() + /// .call(AccountId::from([0x42; 32])) + /// .ref_time_limit(500_000_000) + /// .proof_time_limit(100_000) + /// .storage_deposit_limit(1_000_000_000) + /// .exec_input( + /// ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE])) + /// .push_arg(42u8) + /// .push_arg(true) + /// .push_arg(&[0x10u8; 32]), + /// ) + /// .returns::() + /// .params(); + /// + /// self.env() /// .invoke_contract(&call_params) /// .unwrap_or_else(|env_err| { /// panic!("Received an error from the Environment: {:?}", env_err) diff --git a/integration-tests/.gitignore b/integration-tests/.gitignore new file mode 100755 index 00000000000..4531c3e8a6b --- /dev/null +++ b/integration-tests/.gitignore @@ -0,0 +1,2 @@ +**/target/ +Cargo.lock diff --git a/integration-tests/basic-contract-caller/.gitignore b/integration-tests/basic-contract-caller/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/basic-contract-caller/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/basic-contract-caller/e2e_tests.rs b/integration-tests/basic-contract-caller/e2e_tests.rs deleted file mode 100644 index e9ce86d4bc1..00000000000 --- a/integration-tests/basic-contract-caller/e2e_tests.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::basic_contract_caller::*; -use ink_e2e::ContractsBackend; - -type E2EResult = std::result::Result>; - -#[ink_e2e::test] -async fn flip_and_get(mut client: Client) -> E2EResult<()> { - // given - let other_contract_code = client - .upload("other-contract", &ink_e2e::alice()) - .submit() - .await - .expect("other_contract upload failed"); - - let mut constructor = BasicContractCallerRef::new(other_contract_code.code_hash); - let contract = client - .instantiate("basic-contract-caller", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("basic-contract-caller instantiate failed"); - let mut call_builder = contract.call_builder::(); - let call = call_builder.flip_and_get(); - - // when - let result = client - .call(&ink_e2e::alice(), &call) - .submit() - .await - .expect("Calling `flip_and_get` failed") - .return_value(); - - assert_eq!(result, false); - - Ok(()) -} diff --git a/integration-tests/basic-contract-caller/lib.rs b/integration-tests/basic-contract-caller/lib.rs deleted file mode 100755 index 78c999d7c26..00000000000 --- a/integration-tests/basic-contract-caller/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod basic_contract_caller { - /// We import the generated `ContractRef` of our other contract. - /// - /// Note that the other contract must have re-exported it (`pub use - /// OtherContractRef`) for us to have access to it. - use other_contract::OtherContractRef; - - #[ink(storage)] - pub struct BasicContractCaller { - /// We specify that our contract will store a reference to the `OtherContract`. - other_contract: OtherContractRef, - } - - impl BasicContractCaller { - /// In order to use the `OtherContract` we first need to **instantiate** it. - /// - /// To do this we will use the uploaded `code_hash` of `OtherContract`. - #[ink(constructor)] - pub fn new(other_contract_code_hash: Hash) -> Self { - let other_contract = OtherContractRef::new(true) - .code_hash(other_contract_code_hash) - .endowment(0) - .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) - .instantiate(); - - Self { other_contract } - } - - /// Using the `ContractRef` we can call all the messages of the `OtherContract` as - /// if they were normal Rust methods (because at the end of the day, they - /// are!). - #[ink(message)] - pub fn flip_and_get(&mut self) -> bool { - self.other_contract.flip(); - self.other_contract.get() - } - } -} - -#[cfg(all(test, feature = "e2e-tests"))] -mod e2e_tests; diff --git a/integration-tests/basic-contract-caller/other-contract/.gitignore b/integration-tests/basic-contract-caller/other-contract/.gitignore deleted file mode 100755 index 8de8f877e47..00000000000 --- a/integration-tests/basic-contract-caller/other-contract/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/integration-tests/basic-contract-caller/Cargo.toml b/integration-tests/cross-contract-calls/Cargo.toml similarity index 96% rename from integration-tests/basic-contract-caller/Cargo.toml rename to integration-tests/cross-contract-calls/Cargo.toml index 0781400a2f4..ad17489b138 100755 --- a/integration-tests/basic-contract-caller/Cargo.toml +++ b/integration-tests/cross-contract-calls/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic-contract-caller" +name = "cross-contract-calls" version = "5.0.0-rc" authors = ["Parity Technologies "] edition = "2021" diff --git a/integration-tests/cross-contract-calls/e2e_tests.rs b/integration-tests/cross-contract-calls/e2e_tests.rs new file mode 100644 index 00000000000..d26301dc694 --- /dev/null +++ b/integration-tests/cross-contract-calls/e2e_tests.rs @@ -0,0 +1,84 @@ +use super::cross_contract_calls::*; +use ink_e2e::ContractsBackend; + +type E2EResult = std::result::Result>; + +#[ink_e2e::test] +async fn flip_and_get(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = CrossContractCallsRef::new(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("basic-contract-caller instantiate failed"); + let mut call_builder = contract.call_builder::(); + let call = call_builder.flip_and_get_v1(); + + // when + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert_eq!(result, false); + + Ok(()) +} + +#[ink_e2e::test] +async fn flip_and_get_v2(mut client: Client) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + let mut constructor = CrossContractCallsRef::new(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("cross-contract-calls instantiate failed"); + let mut call_builder = contract.call_builder::(); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 1_000_000_000; + + // when + let call = call_builder.flip_and_get_invoke_v2_with_limits( + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert!(!result); + + let call = call_builder.flip_and_get_invoke_v2_no_weight_limit(); + let result = client + .call(&ink_e2e::alice(), &call) + .submit() + .await + .expect("Calling `flip_and_get` failed") + .return_value(); + + assert!(result); + + Ok(()) +} diff --git a/integration-tests/cross-contract-calls/lib.rs b/integration-tests/cross-contract-calls/lib.rs new file mode 100755 index 00000000000..3cd90fc1371 --- /dev/null +++ b/integration-tests/cross-contract-calls/lib.rs @@ -0,0 +1,80 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod cross_contract_calls { + use ink::codegen::TraitCallBuilder; + use other_contract::OtherContractRef; + + #[ink(storage)] + pub struct CrossContractCalls { + other_contract: OtherContractRef, + } + + impl CrossContractCalls { + /// Initializes the contract by instantiating the code at the given code hash and + /// storing the resulting account id. + #[ink(constructor)] + pub fn new(other_contract_code_hash: Hash) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .instantiate(); + + Self { other_contract } + } + + /// Basic invocation of the other contract via the contract reference. + /// + /// *Note* this will invoke the original `call` (V1) host function, which will be + /// deprecated in the future. + #[ink(message)] + pub fn flip_and_get_v1(&mut self) -> bool { + let call_builder = self.other_contract.call_mut(); + + call_builder.flip().call_v1().invoke(); + call_builder.get().call_v1().invoke() + } + + /// Use the new `call_v2` host function via the call builder to forward calls to + /// the other contract, initially calling `flip` and then `get` to return the + /// result. + /// + /// This demonstrates how to set the new weight and storage limit parameters via + /// the call builder API. + #[ink(message)] + pub fn flip_and_get_invoke_v2_with_limits( + &mut self, + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Balance, + ) -> bool { + let call_builder = self.other_contract.call_mut(); + + call_builder + .flip() + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke(); + + call_builder + .get() + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .invoke() + } + + /// Demonstrate that the `call_v2` succeeds without having specified the weight + /// and storage limit parameters + #[ink(message)] + pub fn flip_and_get_invoke_v2_no_weight_limit(&mut self) -> bool { + self.other_contract.flip(); + self.other_contract.get() + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests; diff --git a/integration-tests/basic-contract-caller/other-contract/Cargo.toml b/integration-tests/cross-contract-calls/other-contract/Cargo.toml similarity index 100% rename from integration-tests/basic-contract-caller/other-contract/Cargo.toml rename to integration-tests/cross-contract-calls/other-contract/Cargo.toml diff --git a/integration-tests/basic-contract-caller/other-contract/lib.rs b/integration-tests/cross-contract-calls/other-contract/lib.rs similarity index 68% rename from integration-tests/basic-contract-caller/other-contract/lib.rs rename to integration-tests/cross-contract-calls/other-contract/lib.rs index 53e51019476..ed280b63812 100755 --- a/integration-tests/basic-contract-caller/other-contract/lib.rs +++ b/integration-tests/cross-contract-calls/other-contract/lib.rs @@ -1,10 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] -/// Re-export the `ContractRef` generated by the ink! codegen. -/// -/// This let's other crates which pull this contract in as a dependency to interact -/// with this contract in a type-safe way. -pub use self::other_contract::OtherContractRef; +pub use self::other_contract::{ + OtherContract, + OtherContractRef, +}; #[ink::contract] mod other_contract { diff --git a/integration-tests/erc1155/lib.rs b/integration-tests/erc1155/lib.rs index ba06c1ea052..f6cbf56971d 100644 --- a/integration-tests/erc1155/lib.rs +++ b/integration-tests/erc1155/lib.rs @@ -383,7 +383,7 @@ mod erc1155 { // reject this transfer. If they reject it we need to revert the call. let result = build_call::() .call(to) - .gas_limit(5000) + .ref_time_limit(5000) .exec_input( ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR)) .push_arg(caller) diff --git a/integration-tests/multisig/lib.rs b/integration-tests/multisig/lib.rs index 623919891e5..f7c0bf62146 100755 --- a/integration-tests/multisig/lib.rs +++ b/integration-tests/multisig/lib.rs @@ -127,7 +127,7 @@ mod multisig { /// The amount of chain balance that is transferred to the callee. pub transferred_value: Balance, /// Gas limit for the execution of the call. - pub gas_limit: u64, + pub ref_time_limit: u64, /// If set to true the transaction will be allowed to re-enter the multisig /// contract. Re-entrancy can lead to vulnerabilities. Use at your own /// risk. @@ -335,7 +335,7 @@ mod multisig { /// selector: selector_bytes!("add_owner"), /// input: add_owner_args.encode(), /// transferred_value: 0, - /// gas_limit: 0, + /// ref_time_limit: 0, /// allow_reentry: true, /// }; /// @@ -345,7 +345,7 @@ mod multisig { /// // are `[86, 244, 13, 223]`. /// let (id, _status) = ink::env::call::build_call::() /// .call_type(Call::new(wallet_id)) - /// .gas_limit(0) + /// .ref_time_limit(0) /// .exec_input( /// ExecutionInput::new(Selector::new([86, 244, 13, 223])) /// .push_arg(&transaction_candidate), @@ -359,7 +359,7 @@ mod multisig { /// // are `[185, 50, 225, 236]`. /// ink::env::call::build_call::() /// .call_type(Call::new(wallet_id)) - /// .gas_limit(0) + /// .ref_time_limit(0) /// .exec_input(ExecutionInput::new(Selector::new([185, 50, 225, 236])).push_arg(&id)) /// .returns::<()>() /// .invoke(); @@ -553,7 +553,7 @@ mod multisig { let result = build_call::<::Env>() .call(t.callee) - .gas_limit(t.gas_limit) + .ref_time_limit(t.ref_time_limit) .transferred_value(t.transferred_value) .call_flags(call_flags) .exec_input( @@ -594,7 +594,7 @@ mod multisig { let result = build_call::<::Env>() .call(t.callee) - .gas_limit(t.gas_limit) + .ref_time_limit(t.ref_time_limit) .transferred_value(t.transferred_value) .call_flags(call_flags) .exec_input( @@ -758,7 +758,7 @@ mod multisig { selector: ink::selector_bytes!("change_requirement"), input: call_args.encode(), transferred_value: 0, - gas_limit: 1000000, + ref_time_limit: 1000000, allow_reentry: false, } }