Skip to content

Commit

Permalink
Clean up CallBuilder return() type (#1525)
Browse files Browse the repository at this point in the history
* Assume that `CallBuilder`s return a `MessageResult`

* Add `try_*` variants to `CallBuilder`

* Add doc test showing how to handle `LangError` from `build_call`

* Remove TODO related to `delegate_call`

* Account for `LangError` in E2E tests

* Improve `return_value` error message

* Remove extra `expect` from E2E tests

* Add test for checking that `fire` panics on `LangError`

* Fix spelling

* Remove extra `unwrap` in more E2E tests

* Fix ERC-1155 example

* Fix `invoke_contract` doc test

* RustFmt

* Fix `delegator` example

* Update ERC-20 tests

* Indicate that doc test panics in off-chain env

* Forgot some commas 🤦

* Get `Flipper` example compiling again

* Remove more unwraps

* Update UI tests

* Print out `LangError` when panicking after `invoke`

* Bump `scale` to fix UI tests
  • Loading branch information
HCastano committed Jan 23, 2023
1 parent a4191eb commit 2fc4a61
Show file tree
Hide file tree
Showing 20 changed files with 267 additions and 117 deletions.
24 changes: 16 additions & 8 deletions crates/e2e/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use super::{
};
use contract_metadata::ContractMetadata;
use ink_env::Environment;
use ink_primitives::MessageResult;

use sp_runtime::traits::{
IdentifyAccount,
Expand Down Expand Up @@ -124,7 +125,7 @@ pub struct CallResult<C: subxt::Config, E: Environment, V> {
pub events: ExtrinsicEvents<C>,
/// Contains the result of decoding the return value of the called
/// function.
pub value: Result<V, scale::Error>,
pub value: Result<MessageResult<V>, scale::Error>,
/// Returns the bytes of the encoded dry-run return value.
pub data: Vec<u8>,
}
Expand All @@ -139,12 +140,19 @@ where
/// Panics if the value could not be decoded. The raw bytes can be accessed
/// via [`return_data`].
pub fn return_value(self) -> V {
self.value.unwrap_or_else(|err| {
panic!(
"decoding dry run result to ink! message return type failed: {}",
err
)
})
self.value
.unwrap_or_else(|env_err| {
panic!(
"Decoding dry run result to ink! message return type failed: {}",
env_err
)
})
.unwrap_or_else(|lang_err| {
panic!(
"Encountered a `LangError` while decoding dry run result to ink! message: {:?}",
lang_err
)
})
}

/// Returns true if the specified event was triggered by the call.
Expand Down Expand Up @@ -655,7 +663,7 @@ where
}

let bytes = &dry_run.result.as_ref().unwrap().data;
let value: Result<RetType, scale::Error> =
let value: Result<MessageResult<RetType>, scale::Error> =
scale::Decode::decode(&mut bytes.as_ref());

Ok(CallResult {
Expand Down
4 changes: 3 additions & 1 deletion crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ where
/// - If the called contract execution has trapped.
/// - If the called contract ran out of gas upon execution.
/// - If the returned value failed to decode properly.
pub fn invoke_contract<E, Args, R>(params: &CallParams<E, Call<E>, Args, R>) -> Result<R>
pub fn invoke_contract<E, Args, R>(
params: &CallParams<E, Call<E>, Args, R>,
) -> Result<ink_primitives::MessageResult<R>>
where
E: Environment,
Args: scale::Encode,
Expand Down
2 changes: 1 addition & 1 deletion crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ pub trait TypedEnvBackend: EnvBackend {
fn invoke_contract<E, Args, R>(
&mut self,
call_data: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<ink_primitives::MessageResult<R>>
where
E: Environment,
Args: scale::Encode,
Expand Down
94 changes: 91 additions & 3 deletions crates/env/src/call/call_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,27 @@ where
/// Invokes the contract with the given built-up call parameters.
///
/// Returns the result of the contract execution.
///
/// # Panics
///
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
/// those use the [`try_invoke`][`CallParams::try_invoke`] method instead.
pub fn invoke(&self) -> Result<R, crate::Error> {
crate::invoke_contract(self).map(|inner| {
inner.unwrap_or_else(|lang_error| {
panic!("Cross-contract call failed with {:?}", lang_error)
})
})
}

/// Invokes the contract with the given built-up call parameters.
///
/// Returns the result of the contract execution.
///
/// # Note
///
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
pub fn try_invoke(&self) -> Result<ink_primitives::MessageResult<R>, crate::Error> {
crate::invoke_contract(self)
}
}
Expand Down Expand Up @@ -173,7 +193,7 @@ where
/// )
/// .returns::<()>()
/// .fire()
/// .unwrap();
/// .expect("Got an error from the Contract's pallet.");
/// ```
///
/// ## Example 2: With Return Value
Expand Down Expand Up @@ -209,7 +229,7 @@ where
/// )
/// .returns::<i32>()
/// .fire()
/// .unwrap();
/// .expect("Got an error from the Contract's pallet.");
/// ```
///
/// ## Example 3: Delegate call
Expand Down Expand Up @@ -237,7 +257,47 @@ where
/// )
/// .returns::<i32>()
/// .fire()
/// .unwrap();
/// .expect("Got an error from the Contract's pallet.");
/// ```
///
/// # Handling `LangError`s
///
/// It is also important to note that there are certain types of errors which can happen during
/// cross-contract calls which can be handled know as [`LangError`][`ink_primitives::LangError`].
///
/// If you want to handle these errors use the [`CallBuilder::try_fire`] methods instead of the
/// [`CallBuilder::fire`] ones.
///
/// **Note:** The shown examples panic because there is currently no cross-calling
/// support in the off-chain testing environment. However, this code
/// should work fine in on-chain environments.
///
/// ## Example: Handling a `LangError`
///
/// ```should_panic
/// # use ::ink_env::{
/// # Environment,
/// # DefaultEnvironment,
/// # call::{build_call, Selector, ExecutionInput}
/// # };
/// # use ink_env::call::Call;
/// # type AccountId = <DefaultEnvironment as Environment>::AccountId;
/// # type Balance = <DefaultEnvironment as Environment>::Balance;
/// let call_result = build_call::<DefaultEnvironment>()
/// .call_type(
/// Call::new()
/// .callee(AccountId::from([0x42; 32]))
/// .gas_limit(5000)
/// .transferred_value(10),
/// )
/// .try_fire()
/// .expect("Got an error from the Contract's pallet.");
///
/// match call_result {
/// Ok(_) => unimplemented!(),
/// Err(e @ ink_primitives::LangError::CouldNotReadInput) => unimplemented!(),
/// Err(_) => unimplemented!(),
/// }
/// ```
#[allow(clippy::type_complexity)]
pub fn build_call<E>() -> CallBuilder<
Expand Down Expand Up @@ -597,9 +657,23 @@ where
E: Environment,
{
/// Invokes the cross-chain function call.
///
/// # Panics
///
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
/// those use the [`try_fire`][`CallBuilder::try_fire`] method instead.
pub fn fire(self) -> Result<(), Error> {
self.params().invoke()
}

/// Invokes the cross-chain function call.
///
/// # Note
///
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
pub fn try_fire(self) -> Result<ink_primitives::MessageResult<()>, Error> {
self.params().try_invoke()
}
}

impl<E>
Expand All @@ -626,9 +700,23 @@ where
R: scale::Decode,
{
/// Invokes the cross-chain function call and returns the result.
///
/// # Panics
///
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
/// those use the [`try_fire`][`CallBuilder::try_fire`] method instead.
pub fn fire(self) -> Result<R, Error> {
self.params().invoke()
}

/// Invokes the cross-chain function call and returns the result.
///
/// # Note
///
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
pub fn try_fire(self) -> Result<ink_primitives::MessageResult<R>, Error> {
self.params().try_invoke()
}
}

impl<E, Args, R>
Expand Down
2 changes: 1 addition & 1 deletion crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ impl TypedEnvBackend for EnvInstance {
fn invoke_contract<E, Args, R>(
&mut self,
params: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<ink_primitives::MessageResult<R>>
where
E: Environment,
Args: scale::Encode,
Expand Down
2 changes: 1 addition & 1 deletion crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ impl TypedEnvBackend for EnvInstance {
fn invoke_contract<E, Args, R>(
&mut self,
params: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<ink_primitives::MessageResult<R>>
where
E: Environment,
Args: scale::Encode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,10 @@ impl CallBuilder<'_> {
let input_types = generator::input_types(message.inputs());
let arg_list = generator::generate_argument_list(input_types.iter().cloned());
let mut_tok = callable.receiver().is_ref_mut().then(|| quote! { mut });
let return_type = message.wrapped_output();
let return_type = message
.output()
.map(quote::ToTokens::to_token_stream)
.unwrap_or_else(|| quote::quote! { () });
let output_span = return_type.span();
let output_type = quote_spanned!(output_span=>
::ink::env::call::CallBuilder<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ impl ContractRef<'_> {
) -> #wrapped_output_type {
<Self as ::ink::codegen::TraitCallBuilder>::#call_operator(self)
.#message_ident( #( #input_bindings ),* )
.fire()
.try_fire()
.unwrap_or_else(|error| ::core::panic!(
"encountered error while calling {}::{}: {:?}",
::core::stringify!(#storage_ident),
Expand Down
7 changes: 5 additions & 2 deletions crates/ink/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,10 @@ where
/// )
/// .returns::<i32>()
/// .params();
/// self.env().invoke_contract(&call_params).unwrap_or_else(|err| panic!("call invocation must succeed: {:?}", err))
///
/// self.env().invoke_contract(&call_params)
/// .unwrap_or_else(|env_err| panic!("Received an error from the Environment: {:?}", env_err))
/// .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err))
/// }
/// #
/// # }
Expand All @@ -549,7 +552,7 @@ where
pub fn invoke_contract<Args, R>(
self,
params: &CallParams<E, Call<E>, Args, R>,
) -> Result<R>
) -> Result<ink_primitives::MessageResult<R>>
where
Args: scale::Encode,
R: scale::Decode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ note: required by a bound in `ExecutionInput::<ArgumentList<ArgumentListEnd, Arg
| T: scale::Encode,
| ^^^^^^^^^^^^^ required by this bound in `ExecutionInput::<ArgumentList<ArgumentListEnd, ArgumentListEnd>>::push_arg`

error[E0599]: the method `fire` exists for struct `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ink::ink_env::call::utils::Argument<NonCodecType>, ArgumentList<ArgumentListEnd, ArgumentListEnd>>>>, Set<ReturnType<Result<(), LangError>>>>`, but its trait bounds were not satisfied
error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ink::ink_env::call::utils::Argument<NonCodecType>, ArgumentList<ArgumentListEnd, ArgumentListEnd>>>>, Set<ReturnType<()>>>`, but its trait bounds were not satisfied
--> tests/ui/contract/fail/message-input-non-codec.rs:16:9
|
16 | pub fn message(&self, _input: NonCodecType) {}
| ^^^ method cannot be called on `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ink::ink_env::call::utils::Argument<NonCodecType>, ArgumentList<ArgumentListEnd, ArgumentListEnd>>>>, Set<ReturnType<Result<(), LangError>>>>` due to unsatisfied trait bounds
| ^^^ method cannot be called on `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ink::ink_env::call::utils::Argument<NonCodecType>, ArgumentList<ArgumentListEnd, ArgumentListEnd>>>>, Set<ReturnType<()>>>` due to unsatisfied trait bounds
|
::: $WORKSPACE/crates/env/src/call/execution_input.rs
|
Expand Down
19 changes: 11 additions & 8 deletions crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ note: required by a bound in `return_value`
| R: scale::Encode,
| ^^^^^^^^^^^^^ required by this bound in `return_value`

error[E0599]: the method `fire` exists for struct `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<Result<NonCodecType, LangError>>>>`, but its trait bounds were not satisfied
error[E0599]: the method `try_fire` exists for struct `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodecType>>>`, but its trait bounds were not satisfied
--> tests/ui/contract/fail/message-returns-non-codec.rs:16:9
|
4 | pub struct NonCodecType;
| ----------------------- doesn't satisfy `NonCodecType: parity_scale_codec::Decode`
...
16 | pub fn message(&self) -> NonCodecType {
| ^^^ method cannot be called on `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<Result<NonCodecType, LangError>>>>` due to unsatisfied trait bounds
|
::: $RUST/core/src/result.rs
|
| pub enum Result<T, E> {
| --------------------- doesn't satisfy `_: parity_scale_codec::Decode`
| ^^^ method cannot be called on `ink::ink_env::call::CallBuilder<DefaultEnvironment, Set<Call<DefaultEnvironment>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodecType>>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Result<NonCodecType, LangError>: parity_scale_codec::Decode`
`NonCodecType: parity_scale_codec::Decode`
note: the following trait must be implemented
--> $CARGO/parity-scale-codec-3.2.2/src/codec.rs
|
| pub trait Decode: Sized {
| ^^^^^^^^^^^^^^^^^^^^^^^
66 changes: 33 additions & 33 deletions crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied
--> tests/ui/trait_def/fail/message_output_non_codec.rs:6:26
|
6 | fn message(&self) -> NonCodec;
| ^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec`
|
= help: the following other types implement trait `WrapperTypeEncode`:
&T
&mut T
Arc<T>
Box<T>
Cow<'a, T>
Rc<T>
String
Vec<T>
parity_scale_codec::Ref<'a, T, U>
= note: required for `NonCodec` to implement `Encode`
--> tests/ui/trait_def/fail/message_output_non_codec.rs:6:26
|
6 | fn message(&self) -> NonCodec;
| ^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec`
|
= help: the following other types implement trait `WrapperTypeEncode`:
&T
&mut T
Arc<T>
Box<T>
Cow<'a, T>
Rc<T>
String
Vec<T>
parity_scale_codec::Ref<'a, T, U>
= note: required for `NonCodec` to implement `Encode`
note: required by a bound in `DispatchOutput`
--> src/codegen/dispatch/type_check.rs
|
| T: scale::Encode + 'static;
| ^^^^^^^^^^^^^ required by this bound in `DispatchOutput`
--> src/codegen/dispatch/type_check.rs
|
| T: scale::Encode + 'static;
| ^^^^^^^^^^^^^ required by this bound in `DispatchOutput`

error[E0599]: the method `fire` exists for struct `CallBuilder<E, Set<Call<E>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodec>>>`, but its trait bounds were not satisfied
--> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5
|
1 | pub struct NonCodec;
| ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode`
--> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5
|
1 | pub struct NonCodec;
| ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode`
...
5 | #[ink(message)]
| ^ method cannot be called on `CallBuilder<E, Set<Call<E>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodec>>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`NonCodec: parity_scale_codec::Decode`
5 | #[ink(message)]
| ^ method cannot be called on `CallBuilder<E, Set<Call<E>>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodec>>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`NonCodec: parity_scale_codec::Decode`
note: the following trait must be implemented
--> $CARGO/parity-scale-codec-3.2.1/src/codec.rs
|
| pub trait Decode: Sized {
| ^^^^^^^^^^^^^^^^^^^^^^^
--> $CARGO/parity-scale-codec-3.2.2/src/codec.rs
|
| pub trait Decode: Sized {
| ^^^^^^^^^^^^^^^^^^^^^^^
Loading

0 comments on commit 2fc4a61

Please sign in to comment.