From 507d01dcb2848c3deb16691bac9ec0c15bcda900 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 21 Nov 2022 16:48:04 -0500 Subject: [PATCH] Return `LangError`s from constructors (#1504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Return `ConstructorResult` in `deploy()` * Wrap return type with `Result` in metadata * Check that constructor's return Results in tests * Add test showing that `Result`s are decoded correctly * Correctly generate metadata for constructors * Fix some nitpicks from Andrew's PR * Wrap dispatch return types with `Ok` These can't return dispatch errors atm. I may want to clean up how this is done later. * Manually wrap metadata return with `ConstructorResult` * Fix existing constructor integration tests * Remove constructor related test from `integration-flipper` * Fix compile tests * Add `ident` to dictionary * Simplify code * Driveby: Also simplify call dispatch * Small fixes to type paths * Check that actual instantiate call fails * Add some tests using the `CreateBuilder` * Clean up the `create_builder` tests * Remove unused method * Format code for generating constructor return type * Add `constructors-return-value` to CI * Move shared imports out of messages * Appease Clippy * Appease Clippy * Try flipping order of tests * Change message name Maybe it's a naming clash? * Revert last two changes * Try moving message related test to different module * Revert "Try moving message related test to different module" This reverts commit cab6c980bd731a25de43dd94156165d9f236631b. * Try different type for account ID Co-authored-by: Alexander Theißen --- .config/cargo_spellcheck.dic | 1 + .gitlab-ci.yml | 2 +- crates/e2e/src/client.rs | 6 +- crates/ink/codegen/src/generator/dispatch.rs | 106 ++++++------ crates/ink/codegen/src/generator/metadata.rs | 27 ++-- crates/ink/src/lib.rs | 1 + ...uctor-return-result-non-codec-error.stderr | 6 +- .../pass/constructor-return-result-alias.rs | 35 +++- crates/primitives/src/lib.rs | 4 + .../call-builder/Cargo.toml | 6 + .../call-builder/lib.rs | 152 ++++++++++++++++-- .../constructors-return-value/lib.rs | 50 ++++-- 12 files changed, 289 insertions(+), 107 deletions(-) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index aaf325fb8a3..463c3ac3121 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -46,6 +46,7 @@ evaluable fuzzer getter growable +ident interoperate invariants kB diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3bf7ff351dd..a77ca824997 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,7 +34,7 @@ variables: ALL_CRATES: "${PURELY_STD_CRATES} ${ALSO_WASM_CRATES}" DELEGATOR_SUBCONTRACTS: "accumulator adder subber" UPGRADEABLE_CONTRACTS: "forward-calls set-code-hash" - LANG_ERR_INTEGRATION_CONTRACTS: "integration-flipper call-builder contract-ref" + LANG_ERR_INTEGRATION_CONTRACTS: "integration-flipper call-builder contract-ref constructors-return-value" # TODO `cargo clippy --verbose --all-targets --all-features` for this crate # currently fails on `stable`, but succeeds on `nightly`. This is due to # this fix not yet in stable: /~https://github.com/rust-lang/rust-clippy/issues/8895. diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index 65a06aa3b25..1843cc87c9c 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -341,9 +341,9 @@ where .instantiate_with_code_dry_run( value, storage_deposit_limit, - code.clone(), - data.clone(), - salt.clone(), + code, + data, + salt, signer, ) .await diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 53de261db10..4246d982103 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -270,8 +270,8 @@ impl Dispatch<'_> { type Input = #input_tuple_type; type Output = #output_type; type Storage = #storage_ident; - type Error = #constructor_return_type :: Error; - const IS_RESULT: ::core::primitive::bool = #constructor_return_type :: IS_RESULT; + type Error = #constructor_return_type::Error; + const IS_RESULT: ::core::primitive::bool = #constructor_return_type::IS_RESULT; const CALLABLE: fn(Self::Input) -> Self::Output = |#input_tuple_bindings| { #storage_ident::#constructor_ident(#( #input_bindings ),* ) @@ -420,16 +420,33 @@ impl Dispatch<'_> { .unwrap_or_else(|error| ::core::panic!("{}", error)) } - ::ink::env::decode_input::< - <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type>() - .map_err(|_| ::ink::reflect::DispatchError::CouldNotReadInput) - .and_then(|decoder| { - <<#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type - as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(decoder) - }) - .unwrap_or_else(|error| { - ::core::panic!("dispatching ink! constructor failed: {}", error) - }) + let dispatchable = match ::ink::env::decode_input::< + <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type, + >() { + ::core::result::Result::Ok(decoded_dispatchable) => { + decoded_dispatchable + } + ::core::result::Result::Err(_decoding_error) => { + let error = ::ink::ConstructorResult::Err(::ink::LangError::CouldNotReadInput); + + // At this point we're unable to set the `Ok` variant to be the any "real" + // constructor output since we were unable to figure out what the caller wanted + // to dispatch in the first place, so we set it to `()`. + // + // This is okay since we're going to only be encoding the `Err` variant + // into the output buffer anyways. + ::ink::env::return_value::<::ink::ConstructorResult<()>>( + ::ink::env::ReturnFlags::new_with_reverted(true), + &error, + ); + } + }; + + <<#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type + as ::ink::reflect::ExecuteDispatchable>::execute_dispatchable(dispatchable) + .unwrap_or_else(|error| { + ::core::panic!("dispatching ink! message failed: {}", error) + }) } #[cfg(not(test))] @@ -448,7 +465,7 @@ impl Dispatch<'_> { decoded_dispatchable } ::core::result::Result::Err(_decoding_error) => { - let error = ::core::result::Result::Err(::ink::LangError::CouldNotReadInput); + let error = ::ink::MessageResult::Err(::ink::LangError::CouldNotReadInput); // At this point we're unable to set the `Ok` variant to be the any "real" // message output since we were unable to figure out what the caller wanted @@ -606,30 +623,25 @@ impl Dispatch<'_> { let result: #constructor_output = #constructor_callable(input); let output_value = ::ink::reflect::ConstructorOutputValue::new(result); + let output_result = #constructor_value::as_result(&output_value); - match #constructor_value :: as_result(&output_value) { - ::core::result::Result::Ok(contract) => { - ::ink::env::set_contract_storage::<::ink::primitives::Key, #storage_ident>( - &<#storage_ident as ::ink::storage::traits::StorageKey>::KEY, - contract, - ); - // only fallible constructors return success `Ok` back to the caller. - if #constructor_value :: IS_RESULT { - ::ink::env::return_value::<::core::result::Result<&(), ()>>( - ::ink::env::ReturnFlags::new_with_reverted(false), - &::core::result::Result::Ok(&()) - ) - } else { - ::core::result::Result::Ok(()) - } - }, - ::core::result::Result::Err(err) => { - ::ink::env::return_value::<::core::result::Result<(), & #constructor_value :: Error>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &::core::result::Result::Err(err) - ) - } + if let ::core::result::Result::Ok(contract) = output_result.as_ref() { + ::ink::env::set_contract_storage::<::ink::primitives::Key, #storage_ident>( + &<#storage_ident as ::ink::storage::traits::StorageKey>::KEY, + contract, + ); } + + ::ink::env::return_value::< + ::ink::ConstructorResult< + ::core::result::Result<(), &#constructor_value::Error> + >, + >( + ::ink::env::ReturnFlags::new_with_reverted(output_result.is_err()), + // Currently no `LangError`s are raised at this level of the + // dispatch logic so `Ok` is always returned to the caller. + &::ink::ConstructorResult::Ok(output_result.map(|_| ())), + ); } ) }); @@ -814,27 +826,19 @@ impl Dispatch<'_> { } let result: #message_output = #message_callable(&mut contract, input); - let failure = ::ink::is_result_type!(#message_output) + let is_reverted = ::ink::is_result_type!(#message_output) && ::ink::is_result_err!(result); - // Currently no `LangError`s are raised at this level of the dispatch logic - // so `Ok` is always returned to the caller. - let return_value = ::core::result::Result::Ok(result); - - if failure { - // We return early here since there is no need to push back the - // intermediate results of the contract - the transaction is going to be - // reverted anyways. - ::ink::env::return_value::<::ink::MessageResult::<#message_output>>( - ::ink::env::ReturnFlags::new_with_reverted(true), - &return_value - ) + // no need to push back results: transaction gets reverted anyways + if !is_reverted { + push_contract(contract, #mutates_storage); } - push_contract(contract, #mutates_storage); - ::ink::env::return_value::<::ink::MessageResult::<#message_output>>( - ::ink::env::ReturnFlags::new_with_reverted(false), &return_value + ::ink::env::ReturnFlags::new_with_reverted(is_reverted), + // Currently no `LangError`s are raised at this level of the + // dispatch logic so `Ok` is always returned to the caller. + &::ink::MessageResult::Ok(result), ) } ) diff --git a/crates/ink/codegen/src/generator/metadata.rs b/crates/ink/codegen/src/generator/metadata.rs index 979e53b16c2..d685c99785f 100644 --- a/crates/ink/codegen/src/generator/metadata.rs +++ b/crates/ink/codegen/src/generator/metadata.rs @@ -334,7 +334,7 @@ impl Metadata<'_> { } } - /// Generates ink! metadata for the storage with given selector and indentation. + /// Generates ink! metadata for the storage with given selector and ident. fn generate_constructor_return_type( storage_ident: &Ident, selector_id: u32, @@ -343,22 +343,17 @@ impl Metadata<'_> { let constructor_info = quote_spanned!(span => < #storage_ident as ::ink::reflect::DispatchableConstructorInfo<#selector_id>> ); + quote_spanned!(span=> - ::ink::metadata::ReturnTypeSpec::new( - if #constructor_info ::IS_RESULT { - ::core::option::Option::Some(::ink::metadata::TypeSpec::with_name_str::< - ::core::result::Result< - (), - #constructor_info ::Error - > - >( - "core::result::Result" - ) - ) - } else { - ::core::option::Option::None - } - ) + ::ink::metadata::ReturnTypeSpec::new(if #constructor_info::IS_RESULT { + ::core::option::Option::Some(::ink::metadata::TypeSpec::with_name_str::< + ::ink::ConstructorResult<::core::result::Result<(), #constructor_info::Error>>, + >("ink_primitives::ConstructorResult")) + } else { + ::core::option::Option::Some(::ink::metadata::TypeSpec::with_name_str::< + ::ink::ConstructorResult<()>, + >("ink_primitives::ConstructorResult")) + }) ) } diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index a426811b119..14224167417 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -72,6 +72,7 @@ pub use ink_macro::{ trait_definition, }; pub use ink_primitives::{ + ConstructorResult, LangError, MessageResult, }; 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 f6256d78c59..19d2d86df6d 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 @@ -1,8 +1,8 @@ -error[E0277]: the trait bound `Result<(), &contract::Error>: Encode` is not satisfied +error[E0277]: the trait bound `Result, LangError>: Encode` is not satisfied --> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9 | 13 | pub fn constructor() -> Result { - | ^^^ the trait `Encode` is not implemented for `Result<(), &contract::Error>` + | ^^^ the trait `Encode` is not implemented for `Result, LangError>` | = help: the trait `Encode` is implemented for `Result` note: required by a bound in `return_value` @@ -28,6 +28,8 @@ error[E0277]: the trait bound `contract::Error: TypeInfo` is not satisfied (A, B, C, D, E, F) and $N others = note: required for `Result<(), contract::Error>` to implement `TypeInfo` + = note: 1 redundant requirement hidden + = note: required for `Result, LangError>` to implement `TypeInfo` note: required by a bound in `TypeSpec::with_name_str` --> $WORKSPACE/crates/metadata/src/specs.rs | diff --git a/crates/ink/tests/ui/contract/pass/constructor-return-result-alias.rs b/crates/ink/tests/ui/contract/pass/constructor-return-result-alias.rs index 08f48c4a710..4fe4f90f794 100644 --- a/crates/ink/tests/ui/contract/pass/constructor-return-result-alias.rs +++ b/crates/ink/tests/ui/contract/pass/constructor-return-result-alias.rs @@ -58,7 +58,7 @@ fn main() { assert_eq!("constructor", constructor.label()); let type_spec = constructor.return_type().opt_type().unwrap(); assert_eq!( - "core::result::Result", + "ink_primitives::ConstructorResult", format!("{}", type_spec.display_name()) ); let ty = metadata.registry().resolve(type_spec.ty().id()).unwrap(); @@ -68,16 +68,39 @@ fn main() { scale_info::TypeDef::Variant(variant) => { assert_eq!(2, variant.variants().len()); - let ok_variant = &variant.variants()[0]; - let ok_field = &ok_variant.fields()[0]; - let ok_ty = metadata.registry().resolve(ok_field.ty().id()).unwrap(); + // Outer Result + let outer_ok_variant = &variant.variants()[0]; + let outer_ok_field = &outer_ok_variant.fields()[0]; + let outer_ok_ty = metadata + .registry() + .resolve(outer_ok_field.ty().id()) + .unwrap(); + assert_eq!("Ok", outer_ok_variant.name()); + + // Inner Result + let inner_ok_ty = match outer_ok_ty.type_def() { + scale_info::TypeDef::Variant(variant) => { + assert_eq!(2, variant.variants().len()); + + let inner_ok_variant = &variant.variants()[0]; + assert_eq!("Ok", inner_ok_variant.name()); + + let inner_ok_field = &inner_ok_variant.fields()[0]; + metadata + .registry() + .resolve(inner_ok_field.ty().id()) + .unwrap() + } + td => panic!("Expected a Variant type def enum, got {:?}", td), + }; + let unit_ty = scale_info::TypeDef::Tuple( scale_info::TypeDefTuple::new_portable(vec![]), ); - assert_eq!("Ok", ok_variant.name()); + assert_eq!( &unit_ty, - ok_ty.type_def(), + inner_ok_ty.type_def(), "Ok variant should be a unit `()` type" ); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e4d5608e2b9..1e18f825e41 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -59,3 +59,7 @@ pub enum LangError { /// The `Result` type for ink! messages. #[doc(hidden)] pub type MessageResult = ::core::result::Result; + +/// The `Result` type for ink! constructors. +#[doc(hidden)] +pub type ConstructorResult = ::core::result::Result; diff --git a/examples/lang-err-integration-tests/call-builder/Cargo.toml b/examples/lang-err-integration-tests/call-builder/Cargo.toml index 25aaf12a4e3..01bdb28cc37 100755 --- a/examples/lang-err-integration-tests/call-builder/Cargo.toml +++ b/examples/lang-err-integration-tests/call-builder/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 } +constructors_return_value = { path = "../constructors-return-value", default-features = false, features = ["ink-as-dependency"] } +integration_flipper = { path = "../integration-flipper", default-features = false, features = ["ink-as-dependency"] } + [dev-dependencies] ink_e2e = { path = "../../../crates/e2e" } @@ -28,6 +31,9 @@ std = [ "ink/std", "scale/std", "scale-info/std", + + "constructors_return_value/std", + "integration_flipper/std", ] ink-as-dependency = [] e2e-tests = [] diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index a56fc3b5f36..f0f132b735a 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -9,6 +9,14 @@ #[ink::contract] mod call_builder { + use ink::env::{ + call::{ + Call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }; #[ink(storage)] #[derive(Default)] @@ -30,15 +38,7 @@ mod call_builder { address: AccountId, selector: [u8; 4], ) -> Option<::ink::LangError> { - use ink::env::{ - call::{ - build_call, - Call, - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }; + use ink::env::call::build_call; let result = build_call::() .call_type(Call::new().callee(address)) @@ -55,14 +55,42 @@ mod call_builder { } } } + + #[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() + .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). + result.ok().map(|id| ink::ToAccountId::to_account_id(&id)) + } } #[cfg(all(test, feature = "e2e-tests"))] mod e2e_tests { type E2EResult = std::result::Result>; - #[ink_e2e::test(additional_contracts = "../integration-flipper/Cargo.toml")] - async fn e2e_invalid_selector_can_be_handled( + #[ink_e2e::test( + additional_contracts = "../integration-flipper/Cargo.toml ../constructors-return-value/Cargo.toml" + )] + async fn e2e_invalid_message_selector_can_be_handled( mut client: ink_e2e::Client, ) -> E2EResult<()> { use call_builder::contract_types::ink_primitives::{ @@ -98,7 +126,9 @@ mod call_builder { .value .expect("Input is valid, call must not fail."); - let flipper_ink_acc_id = E2EAccountId(flipper_acc_id.clone().into()); + let flipper_ink_acc_id = + ink::primitives::AccountId::try_from(flipper_acc_id.clone().as_ref()) + .unwrap(); let invalid_selector = [0x00, 0x00, 0x00, 0x00]; let call_result = client .call( @@ -137,5 +167,103 @@ mod call_builder { 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, + ) -> E2EResult<()> { + let constructor = call_builder::constructors::new(); + let contract_acc_id = client + .instantiate(&mut ink_e2e::dave(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload( + &mut ink_e2e::dave(), + constructors_return_value::CONTRACT_PATH, + None, + ) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let new_selector = [0x9B, 0xAE, 0x9D, 0x5E]; + let call_result = client + .call( + &mut ink_e2e::dave(), + contract_acc_id.clone(), + call_builder::messages::call_instantiate( + ink_e2e::utils::runtime_hash_to_ink_hash::< + ink::env::DefaultEnvironment, + >(&code_hash), + new_selector, + true, + ), + 0, + None, + ) + .await + .expect("Client failed to call `call_builder::call_instantiate`.") + .value + .expect("Dispatching `call_builder::call_instantiate` failed."); + + assert!( + call_result.is_some(), + "Call using valid selector failed, when it should've succeeded." + ); + + Ok(()) + } + + #[ink_e2e::test(additional_contracts = "../constructors-return-value/Cargo.toml")] + async fn e2e_create_builder_fails_with_invalid_selector( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let constructor = call_builder::constructors::new(); + let contract_acc_id = client + .instantiate(&mut ink_e2e::eve(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let code_hash = client + .upload( + &mut ink_e2e::eve(), + constructors_return_value::CONTRACT_PATH, + None, + ) + .await + .expect("upload `constructors_return_value` failed") + .code_hash; + + let invalid_selector = [0x00, 0x00, 0x00, 0x00]; + let call_result = client + .call( + &mut ink_e2e::eve(), + contract_acc_id.clone(), + call_builder::messages::call_instantiate( + ink_e2e::utils::runtime_hash_to_ink_hash::< + ink::env::DefaultEnvironment, + >(&code_hash), + invalid_selector, + true, + ), + 0, + None, + ) + .await + .expect("Client failed to call `call_builder::call_instantiate`.") + .value + .expect("Dispatching `call_builder::call_instantiate` failed."); + + assert!( + call_result.is_none(), + "Call using invalid selector succeeded, when it should've failed." + ); + + 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 5763288a0a4..76fbae6e614 100644 --- a/examples/lang-err-integration-tests/constructors-return-value/lib.rs +++ b/examples/lang-err-integration-tests/constructors-return-value/lib.rs @@ -13,7 +13,7 @@ pub mod constructors_return_value { value: bool, } - #[derive(scale::Encode, scale::Decode)] + #[derive(scale::Encode, scale::Decode, Debug)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct ConstructorError; @@ -47,6 +47,7 @@ pub mod constructors_return_value { use std::any::TypeId; #[test] + #[allow(clippy::assertions_on_constants)] fn infallible_constructor_reflection() { const ID: u32 = >::IDS[0]; - assert_eq!( - >::IS_RESULT, - false + assert!( + !>::IS_RESULT, ); assert_eq!( TypeId::of::< @@ -68,6 +68,7 @@ pub mod constructors_return_value { } #[test] + #[allow(clippy::assertions_on_constants)] fn fallible_constructor_reflection() { const ID: u32 = >::IDS[1]; - assert_eq!( + assert!( >::IS_RESULT, - true ); assert_eq!( TypeId::of::< @@ -107,9 +107,12 @@ pub mod constructors_return_value { .result .expect("Instantiate dry run should succeed"); + let data = infallible_constructor_result.result.data; + let decoded_result = Result::<(), ::ink::LangError>::decode(&mut &data[..]) + .expect("Failed to decode constructor Result"); assert!( - infallible_constructor_result.result.data.is_empty(), - "Infallible constructor should return no data" + decoded_result.is_ok(), + "Constructor dispatch should have succeeded" ); let success = client @@ -134,13 +137,19 @@ pub mod constructors_return_value { .result .expect("Instantiate dry run should succeed"); - let decoded_result = Result::<(), super::ConstructorError>::decode( - &mut &result.result.data[..], - ) + let decoded_result = Result::< + Result<(), super::ConstructorError>, + ink::LangError, + >::decode(&mut &result.result.data[..]) .expect("Failed to decode fallible constructor Result"); assert!( decoded_result.is_ok(), + "Constructor dispatch should have succeeded" + ); + + assert!( + decoded_result.unwrap().is_ok(), "Fallible constructor should have succeeded" ); @@ -183,13 +192,19 @@ pub mod constructors_return_value { .result .expect("Instantiate dry run should succeed"); - let decoded_result = Result::<(), super::ConstructorError>::decode( - &mut &result.result.data[..], - ) + let decoded_result = Result::< + Result<(), super::ConstructorError>, + ink::LangError, + >::decode(&mut &result.result.data[..]) .expect("Failed to decode fallible constructor Result"); assert!( - decoded_result.is_err(), + decoded_result.is_ok(), + "Constructor dispatch should have succeeded" + ); + + assert!( + decoded_result.unwrap().is_err(), "Fallible constructor should have failed" ); @@ -197,7 +212,10 @@ pub mod constructors_return_value { .instantiate(&mut ink_e2e::charlie(), constructor, 0, None) .await; - assert!(result.is_err(), "Constructor should fail"); + assert!( + matches!(result, Err(ink_e2e::Error::InstantiateExtrinsic(_))), + "Constructor should fail" + ); Ok(()) }