Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return LangErrors from constructors #1504

Merged
merged 32 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
536b500
Return `ConstructorResult` in `deploy()`
HCastano Nov 16, 2022
1ab55d9
Wrap return type with `Result` in metadata
HCastano Nov 16, 2022
79dff48
Check that constructor's return Results in tests
HCastano Nov 16, 2022
228604c
Add test showing that `Result`s are decoded correctly
HCastano Nov 16, 2022
bb4c1a2
Correctly generate metadata for constructors
HCastano Nov 17, 2022
15439a4
Merge branch 'master' into hc-impl-lang-error-for-constructors
HCastano Nov 17, 2022
27b6b97
Fix some nitpicks from Andrew's PR
HCastano Nov 17, 2022
eb10da6
Wrap dispatch return types with `Ok`
HCastano Nov 17, 2022
8b1aa66
Manually wrap metadata return with `ConstructorResult`
HCastano Nov 17, 2022
36556e7
Fix existing constructor integration tests
HCastano Nov 17, 2022
e61576e
Remove constructor related test from `integration-flipper`
HCastano Nov 17, 2022
a3e0b24
Fix compile tests
HCastano Nov 17, 2022
fb8d293
Add `ident` to dictionary
HCastano Nov 17, 2022
394ffca
Simplify code
athei Nov 18, 2022
2f026a2
Driveby: Also simplify call dispatch
athei Nov 18, 2022
c501d84
Small fixes to type paths
HCastano Nov 18, 2022
c307516
Check that actual instantiate call fails
HCastano Nov 18, 2022
aaebac4
Add some tests using the `CreateBuilder`
HCastano Nov 18, 2022
b63ef91
Clean up the `create_builder` tests
HCastano Nov 19, 2022
0c37262
Merge branch 'master' into hc-impl-lang-error-for-constructors
HCastano Nov 21, 2022
ec6253d
Remove unused method
HCastano Nov 21, 2022
d56b728
Format code for generating constructor return type
HCastano Nov 21, 2022
053f570
Add `constructors-return-value` to CI
HCastano Nov 21, 2022
7305887
Move shared imports out of messages
HCastano Nov 21, 2022
0ef75f9
Appease Clippy
HCastano Nov 21, 2022
4607a67
Appease Clippy
HCastano Nov 21, 2022
f791bfb
Try flipping order of tests
HCastano Nov 21, 2022
5d0f164
Change message name
HCastano Nov 21, 2022
e8b4e75
Revert last two changes
HCastano Nov 21, 2022
cab6c98
Try moving message related test to different module
HCastano Nov 21, 2022
4dc9973
Revert "Try moving message related test to different module"
HCastano Nov 21, 2022
ad1cab7
Try different type for account ID
HCastano Nov 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ evaluable
fuzzer
getter
growable
ident
interoperate
invariants
kB
Expand Down
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions crates/e2e/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 55 additions & 51 deletions crates/ink/codegen/src/generator/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ),* )
Expand Down Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this control flow again: from a readability perspective, I wonder whether it would be better to have this inside the Ok arm of the match. It seems unusual because of the semantics of return_value returning immediately !. A casual reader might expect this statement to always be executed. Just a note for something to consider when we get to refactoring this dispatch logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that seems fair enough. I'll leave it for a follow-up since we'll want to change it for messages too

.unwrap_or_else(|error| {
::core::panic!("dispatching ink! message failed: {}", error)
})
}

#[cfg(not(test))]
Expand All @@ -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
Expand Down Expand Up @@ -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(|_| ())),
);
}
)
});
Expand Down Expand Up @@ -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),
)
}
)
Expand Down
27 changes: 11 additions & 16 deletions crates/ink/codegen/src/generator/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"))
})
)
}

Expand Down
1 change: 1 addition & 0 deletions crates/ink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub use ink_macro::{
trait_definition,
};
pub use ink_primitives::{
ConstructorResult,
LangError,
MessageResult,
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error[E0277]: the trait bound `Result<(), &contract::Error>: Encode` is not satisfied
error[E0277]: the trait bound `Result<Result<(), &contract::Error>, LangError>: Encode` is not satisfied
--> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9
|
13 | pub fn constructor() -> Result<Self, Error> {
| ^^^ the trait `Encode` is not implemented for `Result<(), &contract::Error>`
| ^^^ the trait `Encode` is not implemented for `Result<Result<(), &contract::Error>, LangError>`
|
= help: the trait `Encode` is implemented for `Result<T, E>`
note: required by a bound in `return_value`
Expand All @@ -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<Result<(), contract::Error>, LangError>` to implement `TypeInfo`
note: required by a bound in `TypeSpec::with_name_str`
--> $WORKSPACE/crates/metadata/src/specs.rs
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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"
);

Expand Down
4 changes: 4 additions & 0 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ pub enum LangError {
/// The `Result` type for ink! messages.
#[doc(hidden)]
pub type MessageResult<T> = ::core::result::Result<T, LangError>;

/// The `Result` type for ink! constructors.
#[doc(hidden)]
pub type ConstructorResult<T> = ::core::result::Result<T, LangError>;
6 changes: 6 additions & 0 deletions examples/lang-err-integration-tests/call-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand All @@ -28,6 +31,9 @@ std = [
"ink/std",
"scale/std",
"scale-info/std",

"constructors_return_value/std",
"integration_flipper/std",
]
ink-as-dependency = []
e2e-tests = []