From 6bc816bbe0ac09592e5f13d6f0204e59bb7093b9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 27 Feb 2024 11:07:21 +0000 Subject: [PATCH] `instantiate_v2` with additional limit parameters (#2123) * WIP Scaffolding for using instantiate_v2 * onchain impls and codegen * WIP add v1 builder method * fixes * Fix existing delegator README * Update delegator README * fmt * fmt * test for storage deposit limit exhausted * clippy * docs * docs * Remove gas_limit from doc tests * Warning * CHANGELOG.md * fix example * docs * ui test --- CHANGELOG.md | 1 + crates/e2e/src/builders.rs | 3 +- crates/env/src/api.rs | 45 ++- crates/env/src/backend.rs | 19 +- crates/env/src/call/call_builder.rs | 2 +- crates/env/src/call/create_builder.rs | 373 ++++++++++++++---- crates/env/src/call/mod.rs | 2 + crates/env/src/engine/off_chain/impls.rs | 31 +- crates/env/src/engine/on_chain/impls.rs | 54 ++- .../generator/as_dependency/contract_ref.rs | 3 +- crates/ink/src/env_access.rs | 101 ++++- ...uctor-return-result-non-codec-error.stderr | 4 +- ...onstructor-return-result-cross-contract.rs | 4 - .../cross-contract-calls/e2e_tests.rs | 101 ++++- integration-tests/cross-contract-calls/lib.rs | 42 +- .../call-builder/lib.rs | 2 - .../upgradeable-contracts/README.md | 13 +- 17 files changed, 692 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37aa5ad1e69..ca98b7e1d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [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) +- `instantiate_v2` with additional limit parameters - [#2123](https://github.com/paritytech/ink/pull/2123) - `delegate_dependency` api calls - [#2076](https://github.com/paritytech/ink/pull/2076) ### Changed diff --git a/crates/e2e/src/builders.rs b/crates/e2e/src/builders.rs index 0de77855b71..4e71ab14121 100644 --- a/crates/e2e/src/builders.rs +++ b/crates/e2e/src/builders.rs @@ -21,6 +21,7 @@ use ink_env::{ }, CreateBuilder, ExecutionInput, + LimitParamsV2, }, Environment, }; @@ -32,7 +33,7 @@ pub type CreateBuilderPartial = CreateBuilder< E, ContractRef, Unset<::Hash>, - Unset, + Set>, Unset<::Balance>, Set>, Unset, diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index a532f593c8b..da22b62db8d 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -27,6 +27,8 @@ use crate::{ CreateParams, DelegateCall, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, engine::{ EnvInstance, @@ -351,7 +353,8 @@ where /// /// # Note /// -/// This is a low level way to instantiate another smart contract. +/// This is a low level way to instantiate another smart contract, calling the latest +/// `instantiate_v2` host function. /// /// Prefer to use methods on a `ContractRef` or the /// [`CreateBuilder`](`crate::call::CreateBuilder`) @@ -366,7 +369,7 @@ where /// - If given insufficient endowment. /// - If the returned account ID failed to decode properly. pub fn instantiate_contract( - params: &CreateParams, + params: &CreateParams, Args, Salt, R>, ) -> Result< ink_primitives::ConstructorResult<>::Output>, > @@ -384,6 +387,44 @@ where }) } +/// Instantiates another contract. +/// +/// # Note +/// +/// This is a low level way to instantiate another smart contract, calling the legacy +/// `instantiate_v1` host function. +/// +/// 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_v1( + params: &CreateParams, +) -> Result< + ink_primitives::ConstructorResult<>::Output>, +> +where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType, +{ + ::on_instance(|instance| { + TypedEnvBackend::instantiate_contract_v1::( + instance, params, + ) + }) +} + /// Terminates the existence of the currently executed smart contract. /// /// This removes the calling account and transfers all remaining balance diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 55af65dc965..69260edf60e 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -21,6 +21,8 @@ use crate::{ CreateParams, DelegateCall, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, event::Event, hash::{ @@ -345,7 +347,22 @@ pub trait TypedEnvBackend: EnvBackend { /// For more details visit: [`instantiate_contract`][`crate::instantiate_contract`] fn instantiate_contract( &mut self, - params: &CreateParams, + params: &CreateParams, Args, Salt, R>, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + > + where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType; + + fn instantiate_contract_v1( + &mut self, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< >::Output, diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs index f5c31f8f58c..9429976b1bd 100644 --- a/crates/env/src/call/call_builder.rs +++ b/crates/env/src/call/call_builder.rs @@ -691,7 +691,7 @@ where /// 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. + /// for messages which initialize the builder with the new [`Call`] type. pub fn call_v1(self) -> CallBuilder>, Args, RetType> { let call_type = self.call_type.value(); CallBuilder { diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs index 2cfb62eb543..5e5230d4dd9 100644 --- a/crates/env/src/call/create_builder.rs +++ b/crates/env/src/call/create_builder.rs @@ -19,7 +19,6 @@ use crate::{ ReturnType, Set, Unset, - Unwrap, }, ExecutionInput, Selector, @@ -166,16 +165,36 @@ where } } +/// Defines the limit params for the legacy `ext::instantiate_v1` host function, +/// consisting of the `gas_limit` which is equivalent to the `ref_time_limit` in the new +/// `ext::instantiate`. +#[derive(Clone, Debug)] +pub struct LimitParamsV1 { + gas_limit: u64, +} + +/// Defines the limit params for the new `ext::instantiate` host function. +#[derive(Clone, Debug)] +pub struct LimitParamsV2 +where + E: Environment, +{ + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Option, +} + /// Builds up contract instantiations. #[derive(Debug)] -pub struct CreateParams +pub struct CreateParams where E: Environment, { /// The code hash of the created contract. code_hash: E::Hash, - /// The maximum gas costs allowed for the instantiation. - gas_limit: u64, + /// Parameters for weight and storage limits, differs for versions of the instantiate + /// host function. + limits: Limits, /// The endowment for the instantiated contract. endowment: E::Balance, /// The input data for the instantiation. @@ -188,7 +207,8 @@ where _phantom: PhantomData ContractRef>, } -impl CreateParams +impl + CreateParams where E: Environment, { @@ -198,12 +218,6 @@ where &self.code_hash } - /// The gas limit for the contract instantiation. - #[inline] - pub fn gas_limit(&self) -> u64 { - self.gas_limit - } - /// The endowment for the instantiated contract. #[inline] pub fn endowment(&self) -> &E::Balance { @@ -225,7 +239,45 @@ where } } -impl CreateParams +impl + CreateParams, Args, Salt, R> +where + E: Environment, +{ + /// Gets the `ref_time_limit` part of the weight limit for the contract instantiation. + #[inline] + pub fn ref_time_limit(&self) -> u64 { + self.limits.ref_time_limit + } + + /// Gets the `proof_time_limit` part of the weight limit for the contract + /// instantiation. + #[inline] + pub fn proof_time_limit(&self) -> u64 { + self.limits.proof_time_limit + } + + /// Gets the `storage_deposit_limit` for the contract instantiation. + #[inline] + pub fn storage_deposit_limit(&self) -> Option<&E::Balance> { + self.limits.storage_deposit_limit.as_ref() + } +} + +impl + CreateParams +where + E: Environment, +{ + /// The gas limit for the contract instantiation. + #[inline] + pub fn gas_limit(&self) -> u64 { + self.limits.gas_limit + } +} + +impl + CreateParams where E: Environment, Salt: AsRef<[u8]>, @@ -237,7 +289,8 @@ where } } -impl CreateParams +impl + CreateParams, Args, Salt, R> where E: Environment, ContractRef: FromAccountId, @@ -278,28 +331,68 @@ where ink_primitives::ConstructorResult< >::Output, >, - crate::Error, + Error, > { crate::instantiate_contract(self) } } +impl + CreateParams +where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType, +{ + /// 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`] 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) -> >::Output { + self.try_instantiate() + .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:?}") + }) + } + + /// Instantiates the contract and returns its account ID 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( + &self, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + Error, + > { + crate::instantiate_contract_v1(self) + } +} + /// Builds up contract instantiations. #[derive(Clone)] -pub struct CreateBuilder< - E, - ContractRef, - CodeHash, - GasLimit, - Endowment, - Args, - Salt, - RetType, -> where +pub struct CreateBuilder +where E: Environment, { code_hash: CodeHash, - gas_limit: GasLimit, + limits: Limits, endowment: Endowment, exec_input: Args, salt: Salt, @@ -352,7 +445,6 @@ pub struct CreateBuilder< /// # 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(ink::selector_bytes!("my_constructor"))) @@ -397,7 +489,6 @@ pub struct CreateBuilder< /// # 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(ink::selector_bytes!("my_constructor"))) @@ -415,7 +506,7 @@ pub fn build_create() -> CreateBuilder< ::Env, ContractRef, Unset<<::Env as Environment>::Hash>, - Unset, + Set::Env>>, Unset<<::Env as Environment>::Balance>, Unset>, Unset, @@ -426,7 +517,11 @@ where { CreateBuilder { code_hash: Default::default(), - gas_limit: Default::default(), + limits: Set(LimitParamsV2 { + ref_time_limit: 0, + proof_time_limit: 0, + storage_deposit_limit: None, + }), endowment: Default::default(), exec_input: Default::default(), salt: Default::default(), @@ -435,17 +530,8 @@ where } } -impl - CreateBuilder< - E, - ContractRef, - Unset, - GasLimit, - Endowment, - Args, - Salt, - RetType, - > +impl + CreateBuilder, Limits, Endowment, Args, Salt, RetType> where E: Environment, { @@ -454,19 +540,11 @@ where pub fn code_hash( self, code_hash: E::Hash, - ) -> CreateBuilder< - E, - ContractRef, - Set, - GasLimit, - Endowment, - Args, - Salt, - RetType, - > { + ) -> CreateBuilder, Limits, Endowment, Args, Salt, RetType> + { CreateBuilder { code_hash: Set(code_hash), - gas_limit: self.gas_limit, + limits: self.limits, endowment: self.endowment, exec_input: self.exec_input, salt: self.salt, @@ -477,20 +555,67 @@ where } impl - CreateBuilder, Endowment, Args, Salt, RetType> + CreateBuilder< + E, + ContractRef, + CodeHash, + Set, + Endowment, + Args, + Salt, + RetType, + > where E: Environment, { /// Sets the maximum allowed gas costs for the contract instantiation. #[inline] - pub fn gas_limit( + pub fn gas_limit(self, gas_limit: u64) -> Self { + CreateBuilder { + limits: Set(LimitParamsV1 { gas_limit }), + ..self + } + } +} + +impl + CreateBuilder< + E, + ContractRef, + CodeHash, + Set>, + Endowment, + Args, + Salt, + RetType, + > +where + E: Environment, +{ + /// Switch to the original `instantiate` host function API, which only allows the + /// `gas_limit` limit parameter (equivalent to the `ref_time_limit` in the latest + /// `instantiate_v2`). + /// + /// This method instance is used to allow usage of the generated builder methods + /// for constructors which initialize the builder with the new [`LimitParamsV2`] type. + #[inline] + pub fn instantiate_v1( self, - gas_limit: u64, - ) -> CreateBuilder, Endowment, Args, Salt, RetType> - { + ) -> CreateBuilder< + E, + ContractRef, + CodeHash, + Set, + Endowment, + Args, + Salt, + RetType, + > { CreateBuilder { code_hash: self.code_hash, - gas_limit: Set(gas_limit), + limits: Set(LimitParamsV1 { + gas_limit: self.limits.value().ref_time_limit, + }), endowment: self.endowment, exec_input: self.exec_input, salt: self.salt, @@ -498,14 +623,51 @@ where _phantom: Default::default(), } } + + /// Sets the `ref_time_limit` part of the weight limit for the contract instantiation. + #[inline] + pub fn ref_time_limit(self, ref_time_limit: u64) -> Self { + CreateBuilder { + limits: Set(LimitParamsV2 { + ref_time_limit, + ..self.limits.value() + }), + ..self + } + } + + /// Sets the `proof_time_limit` part of the weight limit for the contract + /// instantiation. + #[inline] + pub fn proof_time_limit(self, proof_time_limit: u64) -> Self { + CreateBuilder { + limits: Set(LimitParamsV2 { + proof_time_limit, + ..self.limits.value() + }), + ..self + } + } + + /// Sets the `storage_deposit_limit` for the contract instantiation. + #[inline] + pub fn storage_deposit_limit(self, storage_deposit_limit: E::Balance) -> Self { + CreateBuilder { + limits: Set(LimitParamsV2 { + storage_deposit_limit: Some(storage_deposit_limit), + ..self.limits.value() + }), + ..self + } + } } -impl +impl CreateBuilder< E, ContractRef, CodeHash, - GasLimit, + Limits, Unset, Args, Salt, @@ -523,7 +685,7 @@ where E, ContractRef, CodeHash, - GasLimit, + Limits, Set, Args, Salt, @@ -531,7 +693,7 @@ where > { CreateBuilder { code_hash: self.code_hash, - gas_limit: self.gas_limit, + limits: self.limits, endowment: Set(endowment), exec_input: self.exec_input, salt: self.salt, @@ -541,12 +703,12 @@ where } } -impl +impl CreateBuilder< E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Unset>, Salt, @@ -564,7 +726,7 @@ where E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Set>, Salt, @@ -572,7 +734,7 @@ where > { CreateBuilder { code_hash: self.code_hash, - gas_limit: self.gas_limit, + limits: self.limits, endowment: self.endowment, exec_input: Set(exec_input), salt: self.salt, @@ -582,12 +744,12 @@ where } } -impl +impl CreateBuilder< E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Args, Unset, @@ -605,7 +767,7 @@ where E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Args, Set, @@ -616,7 +778,7 @@ where { CreateBuilder { code_hash: self.code_hash, - gas_limit: self.gas_limit, + limits: self.limits, endowment: self.endowment, exec_input: self.exec_input, salt: Set(salt), @@ -626,12 +788,12 @@ where } } -impl +impl CreateBuilder< E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Args, Salt, @@ -656,7 +818,7 @@ where E, ContractRef, CodeHash, - GasLimit, + Limits, Endowment, Args, Salt, @@ -668,7 +830,7 @@ where { CreateBuilder { code_hash: self.code_hash, - gas_limit: self.gas_limit, + limits: self.limits, endowment: self.endowment, exec_input: self.exec_input, salt: self.salt, @@ -678,12 +840,12 @@ where } } -impl +impl CreateBuilder< E, ContractRef, Set, - GasLimit, + Set, Set, Set>, Set, @@ -691,14 +853,13 @@ impl > where E: Environment, - GasLimit: Unwrap, { /// 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), + limits: self.limits.value(), endowment: self.endowment.value(), exec_input: self.exec_input.value(), salt_bytes: self.salt.value(), @@ -708,12 +869,63 @@ where } } -impl +impl + CreateBuilder< + E, + ContractRef, + Set, + Set>, + Set, + Set>, + Set, + Set>, + > +where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + RetType: ConstructorReturnType, +{ + /// 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`] 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) -> >::Output { + self.params().instantiate() + } + + /// Instantiates the contract and returns its account ID 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( + self, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + Error, + > { + self.params().try_instantiate() + } +} + +impl CreateBuilder< E, ContractRef, Set, - GasLimit, + Set, Set, Set>, Set, @@ -722,7 +934,6 @@ impl where E: Environment, ContractRef: FromAccountId, - GasLimit: Unwrap, Args: scale::Encode, Salt: AsRef<[u8]>, RetType: ConstructorReturnType, diff --git a/crates/env/src/call/mod.rs b/crates/env/src/call/mod.rs index a3f97ec5de5..60c3f2ff556 100644 --- a/crates/env/src/call/mod.rs +++ b/crates/env/src/call/mod.rs @@ -55,6 +55,8 @@ pub use self::{ CreateBuilder, CreateParams, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, execution_input::ExecutionInput, selector::Selector, diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 92321ae6e9f..f400cc32966 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -22,6 +22,8 @@ use crate::{ CreateParams, DelegateCall, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, event::{ Event, @@ -478,7 +480,7 @@ impl TypedEnvBackend for EnvInstance { fn instantiate_contract( &mut self, - params: &CreateParams, + params: &CreateParams, Args, Salt, R>, ) -> Result< ink_primitives::ConstructorResult< >::Output, @@ -492,7 +494,32 @@ impl TypedEnvBackend for EnvInstance { R: ConstructorReturnType, { let _code_hash = params.code_hash(); - let _gas_limit = params.gas_limit(); + let _ref_time_limit = params.ref_time_limit(); + let _proof_time_limit = params.proof_time_limit(); + let _storage_deposit_limit = params.storage_deposit_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_contract_v1( + &mut self, + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + > + where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType, + { + let _code_hash = params.code_hash(); + let _ref_time_limit = params.gas_limit(); let _endowment = params.endowment(); let _input = params.exec_input(); let _salt_bytes = params.salt_bytes(); diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 10f5af7d155..97717bcfdd6 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -25,6 +25,8 @@ use crate::{ CreateParams, DelegateCall, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, event::{ Event, @@ -546,7 +548,57 @@ impl TypedEnvBackend for EnvInstance { fn instantiate_contract( &mut self, - params: &CreateParams, + params: &CreateParams, Args, Salt, RetType>, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + > + where + E: Environment, + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + RetType: ConstructorReturnType, + { + let mut scoped = 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| &*scoped.take_encoded(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_v2( + enc_code_hash, + ref_time_limit, + proof_time_limit, + storage_deposit_limit, + enc_endowment, + enc_input, + Some(out_address), + Some(out_return_value), + salt, + ); + + crate::engine::decode_instantiate_result::<_, E, ContractRef, RetType>( + instantiate_result.map_err(Into::into), + &mut &out_address[..], + &mut &out_return_value[..], + ) + } + + fn instantiate_contract_v1( + &mut self, + params: &CreateParams, ) -> Result< ink_primitives::ConstructorResult< >::Output, 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 0801345fb60..e646f665cfc 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -444,6 +444,7 @@ impl ContractRef<'_> { let selector_bytes = constructor.composed_selector().hex_lits(); let input_bindings = generator::input_bindings(constructor.inputs()); let input_types = generator::input_types(constructor.inputs()); + let storage_ident = self.contract.module().storage().ident(); let arg_list = generator::generate_argument_list(input_types.iter().cloned()); let ret_type = constructor .output() @@ -459,7 +460,7 @@ impl ContractRef<'_> { Environment, Self, ::ink::env::call::utils::Unset, - ::ink::env::call::utils::Unset, + ::ink::env::call::utils::Set<::ink::env::call::LimitParamsV2<<#storage_ident as ::ink::env::ContractEnv>::Env>>, ::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>, diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index a19f9ed7b8b..a60df70dbd7 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -23,6 +23,8 @@ use ink_env::{ CreateParams, DelegateCall, FromAccountId, + LimitParamsV1, + LimitParamsV2, }, hash::{ CryptoHash, @@ -422,7 +424,10 @@ where ink_env::emit_event::(event) } - /// Instantiates another contract. + /// Instantiates another contract using the supplied code hash. + /// + /// Invokes the `instantiate_v2` host function which allows passing all weight and + /// storage limit parameters. /// /// # Example /// @@ -459,7 +464,9 @@ where /// pub fn instantiate_contract(&self) -> MyContractRef { /// let create_params = build_create::() /// .code_hash(Hash::from([0x42; 32])) - /// .gas_limit(4000) + /// .ref_time_limit(500_000_000) + /// .proof_time_limit(100_000) + /// .storage_deposit_limit(500_000_000_000) /// .endowment(25) /// .exec_input( /// ExecutionInput::new(Selector::new(ink::selector_bytes!("new"))) @@ -493,7 +500,7 @@ where /// For more details visit: [`ink_env::instantiate_contract`] pub fn instantiate_contract( self, - params: &CreateParams, + params: &CreateParams, Args, Salt, R>, ) -> Result< ink_primitives::ConstructorResult< >::Output, @@ -508,6 +515,94 @@ where ink_env::instantiate_contract::(params) } + /// Instantiates another contract using the supplied code hash. + /// + /// # 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; + /// # } + /// use ink::env::{ + /// DefaultEnvironment, + /// call::{build_create, Selector, ExecutionInput} + /// }; + /// use other_contract::OtherContractRef; + /// # + /// # #[ink(storage)] + /// # pub struct MyContract { } + /// # + /// # impl MyContract { + /// # #[ink(constructor)] + /// # pub fn new() -> Self { + /// # Self {} + /// # } + /// # + /// + /// /// Instantiates another contract. + /// #[ink(message)] + /// pub fn instantiate_contract(&self) -> MyContractRef { + /// let create_params = build_create::() + /// .instantiate_v1() + /// .code_hash(Hash::from([0x42; 32])) + /// .gas_limit(500_000_000) + /// .endowment(25) + /// .exec_input( + /// ExecutionInput::new(Selector::new(ink::selector_bytes!("new"))) + /// .push_arg(42) + /// .push_arg(true) + /// .push_arg(&[0x10u8; 32]), + /// ) + /// .salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE]) + /// .returns::() + /// .params(); + /// self.env() + /// .instantiate_contract_v1(&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)) + /// } + /// # + /// # } + /// # } + /// ``` + /// + /// See [our `delegator` example](https://github.com/paritytech/ink/tree/master/integration-tests/integration%20tests/examples/delegator) + /// for a complete contract example. + /// + /// # Note + /// + /// For more details visit: [`ink_env::instantiate_contract_v1`] + + pub fn instantiate_contract_v1( + self, + params: &CreateParams, + ) -> Result< + ink_primitives::ConstructorResult< + >::Output, + >, + > + where + ContractRef: FromAccountId, + Args: scale::Encode, + Salt: AsRef<[u8]>, + R: ConstructorReturnType, + { + ink_env::instantiate_contract_v1::(params) + } + /// Invokes a contract message and returns its result. /// /// # Example 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 f5573446a5a..ce7891f77f5 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 @@ -33,14 +33,14 @@ error[E0277]: the trait bound `contract::Error: WrapperTypeDecode` is not satisf Arc = note: required for `contract::Error` to implement `ink::parity_scale_codec::Decode` = note: required for `Result` to implement `ConstructorReturnType` -note: required by a bound in `CreateBuilder::>>::returns` +note: required by a bound in `CreateBuilder::>>::returns` --> $WORKSPACE/crates/env/src/call/create_builder.rs | | pub fn returns( | ------- required by a bound in this associated function ... | R: ConstructorReturnType, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `CreateBuilder::>>::returns` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 index 3f20047f990..fd1ed118173 100644 --- 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 @@ -45,7 +45,6 @@ fn main() { 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() @@ -55,7 +54,6 @@ fn main() { 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() @@ -65,7 +63,6 @@ fn main() { 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() @@ -75,7 +72,6 @@ fn main() { 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/integration-tests/cross-contract-calls/e2e_tests.rs b/integration-tests/cross-contract-calls/e2e_tests.rs index d26301dc694..ea66516d204 100644 --- a/integration-tests/cross-contract-calls/e2e_tests.rs +++ b/integration-tests/cross-contract-calls/e2e_tests.rs @@ -12,7 +12,7 @@ async fn flip_and_get(mut client: Client) -> E2EResult<()> { .await .expect("other_contract upload failed"); - let mut constructor = CrossContractCallsRef::new(other_contract_code.code_hash); + let mut constructor = CrossContractCallsRef::new_v1(other_contract_code.code_hash); let contract = client .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) .submit() @@ -29,7 +29,102 @@ async fn flip_and_get(mut client: Client) -> E2EResult<()> { .expect("Calling `flip_and_get` failed") .return_value(); - assert_eq!(result, false); + assert!(!result); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_with_insufficient_storage_deposit_limit( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 100_000_000_000; + + let mut constructor = CrossContractCallsRef::new_v2_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + let Err(ink_e2e::Error::InstantiateDryRun(err)) = contract else { + panic!("instantiate should have failed at the dry run"); + }; + + // insufficient storage deposit limit + assert!( + err.error + .to_string() + .contains("StorageDepositLimitExhausted"), + "should have failed with StorageDepositLimitExhausted" + ); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_with_sufficient_limits( + mut client: Client, +) -> E2EResult<()> { + // given + let other_contract_code = client + .upload("other-contract", &ink_e2e::alice()) + .submit() + .await + .expect("other_contract upload failed"); + + const REF_TIME_LIMIT: u64 = 500_000_000; + const PROOF_TIME_LIMIT: u64 = 100_000; + const STORAGE_DEPOSIT_LIMIT: u128 = 500_000_000_000; + + let mut constructor = CrossContractCallsRef::new_v2_with_limits( + other_contract_code.code_hash, + REF_TIME_LIMIT, + PROOF_TIME_LIMIT, + STORAGE_DEPOSIT_LIMIT, + ); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); + + Ok(()) +} + +#[ink_e2e::test] +async fn instantiate_v2_no_limits( + 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_v2_no_limits(other_contract_code.code_hash); + let contract = client + .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) + .submit() + .await; + + assert!(contract.is_ok(), "{}", contract.err().unwrap()); Ok(()) } @@ -43,7 +138,7 @@ async fn flip_and_get_v2(mut client: Client) -> E2EResult<() .await .expect("other_contract upload failed"); - let mut constructor = CrossContractCallsRef::new(other_contract_code.code_hash); + let mut constructor = CrossContractCallsRef::new_v1(other_contract_code.code_hash); let contract = client .instantiate("cross-contract-calls", &ink_e2e::alice(), &mut constructor) .submit() diff --git a/integration-tests/cross-contract-calls/lib.rs b/integration-tests/cross-contract-calls/lib.rs index 3cd90fc1371..335617158d8 100755 --- a/integration-tests/cross-contract-calls/lib.rs +++ b/integration-tests/cross-contract-calls/lib.rs @@ -11,11 +11,47 @@ mod cross_contract_calls { } impl CrossContractCalls { - /// Initializes the contract by instantiating the code at the given code hash and - /// storing the resulting account id. + /// Initializes the contract by instantiating the code at the given code hash via + /// `instantiate_v2` host function with the supplied weight and storage + /// limits. #[ink(constructor)] - pub fn new(other_contract_code_hash: Hash) -> Self { + pub fn new_v2_with_limits( + other_contract_code_hash: Hash, + ref_time_limit: u64, + proof_time_limit: u64, + storage_deposit_limit: Balance, + ) -> Self { + let other_contract = OtherContractRef::new(true) + .code_hash(other_contract_code_hash) + .endowment(0) + .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) + .ref_time_limit(ref_time_limit) + .proof_time_limit(proof_time_limit) + .storage_deposit_limit(storage_deposit_limit) + .instantiate(); + + Self { other_contract } + } + + /// Initializes the contract by instantiating the code at the given code hash via + /// the `instantiate_v2` host function with no weight or storage limits. + #[ink(constructor)] + pub fn new_v2_no_limits(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 } + } + + /// Initializes the contract by instantiating the code at the given code hash via + /// the original `instantiate` host function. + #[ink(constructor)] + pub fn new_v1(other_contract_code_hash: Hash) -> Self { let other_contract = OtherContractRef::new(true) + .instantiate_v1() .code_hash(other_contract_code_hash) .endowment(0) .salt_bytes([0xDE, 0xAD, 0xBE, 0xEF]) diff --git a/integration-tests/lang-err-integration-tests/call-builder/lib.rs b/integration-tests/lang-err-integration-tests/call-builder/lib.rs index c6cbf7bc48e..9aee41f00e8 100755 --- a/integration-tests/lang-err-integration-tests/call-builder/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder/lib.rs @@ -104,7 +104,6 @@ mod call_builder { ) -> Option { let mut params = ConstructorsReturnValueRef::new(init_value) .code_hash(code_hash) - .gas_limit(0) .endowment(0) .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) .params(); @@ -146,7 +145,6 @@ mod call_builder { > { let mut params = ConstructorsReturnValueRef::try_new(init_value) .code_hash(code_hash) - .gas_limit(0) .endowment(0) .salt_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]) .params(); diff --git a/integration-tests/upgradeable-contracts/README.md b/integration-tests/upgradeable-contracts/README.md index 74a25d764ce..8b83108277a 100644 --- a/integration-tests/upgradeable-contracts/README.md +++ b/integration-tests/upgradeable-contracts/README.md @@ -29,7 +29,7 @@ called again, since it will fail to load the migrated storage. ## [Delegator](delegator/) -Delegator patter is based around a low level cross contract call function `delegate_call`. +The Delegator pattern is based around the low level host function `delegate_call`. It allows a contract to delegate its execution to some on-chain uploaded code. It is different from a traditional cross-contract call @@ -49,3 +49,14 @@ This is because `Lazy` and `Mapping` interact with the storage directly instead If your storage is completely layoutless (it only contains `Lazy` and `Mapping` fields), the order of fields and layout do not need to match for the same reason as mentioned above. +### Delegate dependency locks + +The `delegator` contract depends upon the contract code to which it delegates. Since code +can be deleted by anybody if there are no instances of the contract on the chain, this +would break the `delegator` contract. To prevent this, the `delegator` contract utilizes +the `lock_delegate_dependency` and `unlock_delegate_dependency` host functions. Calling +`lock_delegate_dependency` will prevent the code at the given hash from being deleted, +until `unlock_delegate_dependency` is called from within the `delegator` contract instance. +Note that these two methods can be called by anybody executing the contract, so it is the +responsibility of the contract developer to ensure correct access control. +