Skip to content

Commit e2715f7

Browse files
authored
Clean up CallBuilder return() type (#1525)
* 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
1 parent 8c3f65c commit e2715f7

File tree

20 files changed

+261
-116
lines changed

20 files changed

+261
-116
lines changed

crates/e2e/src/client.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use super::{
3333
};
3434
use contract_metadata::ContractMetadata;
3535
use ink_env::Environment;
36+
use ink_primitives::MessageResult;
3637

3738
use sp_runtime::traits::{
3839
IdentifyAccount,
@@ -124,7 +125,7 @@ pub struct CallResult<C: subxt::Config, E: Environment, V> {
124125
pub events: ExtrinsicEvents<C>,
125126
/// Contains the result of decoding the return value of the called
126127
/// function.
127-
pub value: Result<V, scale::Error>,
128+
pub value: Result<MessageResult<V>, scale::Error>,
128129
/// Returns the bytes of the encoded dry-run return value.
129130
pub data: Vec<u8>,
130131
}
@@ -139,12 +140,19 @@ where
139140
/// Panics if the value could not be decoded. The raw bytes can be accessed
140141
/// via [`return_data`].
141142
pub fn return_value(self) -> V {
142-
self.value.unwrap_or_else(|err| {
143-
panic!(
144-
"decoding dry run result to ink! message return type failed: {}",
145-
err
146-
)
147-
})
143+
self.value
144+
.unwrap_or_else(|env_err| {
145+
panic!(
146+
"Decoding dry run result to ink! message return type failed: {}",
147+
env_err
148+
)
149+
})
150+
.unwrap_or_else(|lang_err| {
151+
panic!(
152+
"Encountered a `LangError` while decoding dry run result to ink! message: {:?}",
153+
lang_err
154+
)
155+
})
148156
}
149157

150158
/// Returns true if the specified event was triggered by the call.
@@ -655,7 +663,7 @@ where
655663
}
656664

657665
let bytes = &dry_run.result.as_ref().unwrap().data;
658-
let value: Result<RetType, scale::Error> =
666+
let value: Result<MessageResult<RetType>, scale::Error> =
659667
scale::Decode::decode(&mut bytes.as_ref());
660668

661669
Ok(CallResult {

crates/env/src/api.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ where
268268
/// - If the called contract execution has trapped.
269269
/// - If the called contract ran out of gas upon execution.
270270
/// - If the returned value failed to decode properly.
271-
pub fn invoke_contract<E, Args, R>(params: &CallParams<E, Call<E>, Args, R>) -> Result<R>
271+
pub fn invoke_contract<E, Args, R>(
272+
params: &CallParams<E, Call<E>, Args, R>,
273+
) -> Result<ink_primitives::MessageResult<R>>
272274
where
273275
E: Environment,
274276
Args: scale::Encode,

crates/env/src/backend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ pub trait TypedEnvBackend: EnvBackend {
414414
fn invoke_contract<E, Args, R>(
415415
&mut self,
416416
call_data: &CallParams<E, Call<E>, Args, R>,
417-
) -> Result<R>
417+
) -> Result<ink_primitives::MessageResult<R>>
418418
where
419419
E: Environment,
420420
Args: scale::Encode,

crates/env/src/call/call_builder.rs

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,27 @@ where
109109
/// Invokes the contract with the given built-up call parameters.
110110
///
111111
/// Returns the result of the contract execution.
112+
///
113+
/// # Panics
114+
///
115+
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
116+
/// those use the [`try_invoke`][`CallParams::try_invoke`] method instead.
112117
pub fn invoke(&self) -> Result<R, crate::Error> {
118+
crate::invoke_contract(self).map(|inner| {
119+
inner.unwrap_or_else(|lang_error| {
120+
panic!("Cross-contract call failed with {:?}", lang_error)
121+
})
122+
})
123+
}
124+
125+
/// Invokes the contract with the given built-up call parameters.
126+
///
127+
/// Returns the result of the contract execution.
128+
///
129+
/// # Note
130+
///
131+
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
132+
pub fn try_invoke(&self) -> Result<ink_primitives::MessageResult<R>, crate::Error> {
113133
crate::invoke_contract(self)
114134
}
115135
}
@@ -173,7 +193,7 @@ where
173193
/// )
174194
/// .returns::<()>()
175195
/// .fire()
176-
/// .unwrap();
196+
/// .expect("Got an error from the Contract's pallet.");
177197
/// ```
178198
///
179199
/// ## Example 2: With Return Value
@@ -209,7 +229,7 @@ where
209229
/// )
210230
/// .returns::<i32>()
211231
/// .fire()
212-
/// .unwrap();
232+
/// .expect("Got an error from the Contract's pallet.");
213233
/// ```
214234
///
215235
/// ## Example 3: Delegate call
@@ -237,7 +257,47 @@ where
237257
/// )
238258
/// .returns::<i32>()
239259
/// .fire()
240-
/// .unwrap();
260+
/// .expect("Got an error from the Contract's pallet.");
261+
/// ```
262+
///
263+
/// # Handling `LangError`s
264+
///
265+
/// It is also important to note that there are certain types of errors which can happen during
266+
/// cross-contract calls which can be handled know as [`LangError`][`ink_primitives::LangError`].
267+
///
268+
/// If you want to handle these errors use the [`CallBuilder::try_fire`] methods instead of the
269+
/// [`CallBuilder::fire`] ones.
270+
///
271+
/// **Note:** The shown examples panic because there is currently no cross-calling
272+
/// support in the off-chain testing environment. However, this code
273+
/// should work fine in on-chain environments.
274+
///
275+
/// ## Example: Handling a `LangError`
276+
///
277+
/// ```should_panic
278+
/// # use ::ink_env::{
279+
/// # Environment,
280+
/// # DefaultEnvironment,
281+
/// # call::{build_call, Selector, ExecutionInput}
282+
/// # };
283+
/// # use ink_env::call::Call;
284+
/// # type AccountId = <DefaultEnvironment as Environment>::AccountId;
285+
/// # type Balance = <DefaultEnvironment as Environment>::Balance;
286+
/// let call_result = build_call::<DefaultEnvironment>()
287+
/// .call_type(
288+
/// Call::new()
289+
/// .callee(AccountId::from([0x42; 32]))
290+
/// .gas_limit(5000)
291+
/// .transferred_value(10),
292+
/// )
293+
/// .try_fire()
294+
/// .expect("Got an error from the Contract's pallet.");
295+
///
296+
/// match call_result {
297+
/// Ok(_) => unimplemented!(),
298+
/// Err(e @ ink_primitives::LangError::CouldNotReadInput) => unimplemented!(),
299+
/// Err(_) => unimplemented!(),
300+
/// }
241301
/// ```
242302
#[allow(clippy::type_complexity)]
243303
pub fn build_call<E>() -> CallBuilder<
@@ -597,9 +657,23 @@ where
597657
E: Environment,
598658
{
599659
/// Invokes the cross-chain function call.
660+
///
661+
/// # Panics
662+
///
663+
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
664+
/// those use the [`try_fire`][`CallBuilder::try_fire`] method instead.
600665
pub fn fire(self) -> Result<(), Error> {
601666
self.params().invoke()
602667
}
668+
669+
/// Invokes the cross-chain function call.
670+
///
671+
/// # Note
672+
///
673+
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
674+
pub fn try_fire(self) -> Result<ink_primitives::MessageResult<()>, Error> {
675+
self.params().try_invoke()
676+
}
603677
}
604678

605679
impl<E>
@@ -626,9 +700,23 @@ where
626700
R: scale::Decode,
627701
{
628702
/// Invokes the cross-chain function call and returns the result.
703+
///
704+
/// # Panics
705+
///
706+
/// This method panics if it encounters an [`ink_primitives::LangError`]. If you want to handle
707+
/// those use the [`try_fire`][`CallBuilder::try_fire`] method instead.
629708
pub fn fire(self) -> Result<R, Error> {
630709
self.params().invoke()
631710
}
711+
712+
/// Invokes the cross-chain function call and returns the result.
713+
///
714+
/// # Note
715+
///
716+
/// On failure this returns an [`ink_primitives::LangError`] which can be handled by the caller.
717+
pub fn try_fire(self) -> Result<ink_primitives::MessageResult<R>, Error> {
718+
self.params().try_invoke()
719+
}
632720
}
633721

634722
impl<E, Args, R>

crates/env/src/engine/off_chain/impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ impl TypedEnvBackend for EnvInstance {
440440
fn invoke_contract<E, Args, R>(
441441
&mut self,
442442
params: &CallParams<E, Call<E>, Args, R>,
443-
) -> Result<R>
443+
) -> Result<ink_primitives::MessageResult<R>>
444444
where
445445
E: Environment,
446446
Args: scale::Encode,

crates/env/src/engine/on_chain/impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ impl TypedEnvBackend for EnvInstance {
415415
fn invoke_contract<E, Args, R>(
416416
&mut self,
417417
params: &CallParams<E, Call<E>, Args, R>,
418-
) -> Result<R>
418+
) -> Result<ink_primitives::MessageResult<R>>
419419
where
420420
E: Environment,
421421
Args: scale::Encode,

crates/ink/codegen/src/generator/as_dependency/call_builder.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,10 @@ impl CallBuilder<'_> {
369369
let input_types = generator::input_types(message.inputs());
370370
let arg_list = generator::generate_argument_list(input_types.iter().cloned());
371371
let mut_tok = callable.receiver().is_ref_mut().then(|| quote! { mut });
372-
let return_type = message.wrapped_output();
372+
let return_type = message
373+
.output()
374+
.map(quote::ToTokens::to_token_stream)
375+
.unwrap_or_else(|| quote::quote! { () });
373376
let output_span = return_type.span();
374377
let output_type = quote_spanned!(output_span=>
375378
::ink::env::call::CallBuilder<

crates/ink/codegen/src/generator/as_dependency/contract_ref.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ impl ContractRef<'_> {
370370
) -> #wrapped_output_type {
371371
<Self as ::ink::codegen::TraitCallBuilder>::#call_operator(self)
372372
.#message_ident( #( #input_bindings ),* )
373-
.fire()
373+
.try_fire()
374374
.unwrap_or_else(|error| ::core::panic!(
375375
"encountered error while calling {}::{}: {:?}",
376376
::core::stringify!(#storage_ident),

crates/ink/src/env_access.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,10 @@ where
519519
/// )
520520
/// .returns::<i32>()
521521
/// .params();
522-
/// self.env().invoke_contract(&call_params).unwrap_or_else(|err| panic!("call invocation must succeed: {:?}", err))
522+
///
523+
/// self.env().invoke_contract(&call_params)
524+
/// .unwrap_or_else(|env_err| panic!("Received an error from the Environment: {:?}", env_err))
525+
/// .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {:?}", lang_err))
523526
/// }
524527
/// #
525528
/// # }
@@ -532,7 +535,7 @@ where
532535
pub fn invoke_contract<Args, R>(
533536
self,
534537
params: &CallParams<E, Call<E>, Args, R>,
535-
) -> Result<R>
538+
) -> Result<ink_primitives::MessageResult<R>>
536539
where
537540
Args: scale::Encode,
538541
R: scale::Decode,

crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ note: required by a bound in `ExecutionInput::<ArgumentList<ArgumentListEnd, Arg
5353
| T: scale::Encode,
5454
| ^^^^^^^^^^^^^ required by this bound in `ExecutionInput::<ArgumentList<ArgumentListEnd, ArgumentListEnd>>::push_arg`
5555

56-
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
56+
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
5757
--> tests/ui/contract/fail/message-input-non-codec.rs:16:9
5858
|
5959
16 | pub fn message(&self, _input: NonCodecType) {}
60-
| ^^^ 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
60+
| ^^^ 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
6161
|
6262
::: $WORKSPACE/crates/env/src/call/execution_input.rs
6363
|

0 commit comments

Comments
 (0)