In the scenario where a cross-contract call from contract A to contract B fails, how can we manage the resulting errors? Specifically, if the call from A to B is unsuccessful, what are the options for updating the storage of contract A based on this outcome?
-
I'm gonna try and write a better answer soon, but you should take a look at the "try" methods from the CallBuilder: docs.rs/ink_env/4.0.1/ink_env/call/…. Basically there are three types of errors to handle: Environment errors, Language errors, and Contract errors. Storage changes aren't as big a hassle as in Substrate– HCastanoCommented Mar 17, 2023 at 3:17
1 Answer
All the code I mention here is available in this repo.
There are three types of errors that you need to may want to handle when doing cross-contract calls
- Contract Errors: defined by the smart contract you're interacting with
- Language Errors (
ink::LangError
): defined by the programming language you're using, e.g ink! - Environment Errors (
ink::env::Error
): defined by the underlying execution environment, e.gpallet-contracts
In all cases we can handle these different errors using CallBuilder::try_invoke
and
continue execution (including writing to storage) if we wish.
First, let's assume the contract we're calling into (i.e callee) has the two following message:
#[ink(message)]
pub fn contract_error(&mut self) -> Result<(), ()> {
Err(())
}
#[ink(message)]
pub fn environment_error(&mut self) {
panic!("ContractTrapped")
}
Next, in our caller contract has the following type definitions in place (it'll make things a bit easier to follow).
use ink::MessageResult;
use ink::env::Result as EnvResult;
type ContractResult = core::result::Result<(), ()>;
We can handle the different errors as follows:
Contract Error
#[ink(message)]
pub fn contract_error(&mut self, address: AccountId) {
let result = build_call::<DefaultEnvironment>()
.call(address)
.exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!(
"contract_error"
))))
.returns::<Result<(), ()>>()
.try_invoke();
match result {
EnvResult::Ok(MessageResult::Ok(ContractResult::Err(_))) => {
self.value = !self.value;
}
_ => unimplemented!(),
}
}
Language Error
#[ink(message)]
pub fn lang_error(&mut self, address: AccountId) {
let result = build_call::<DefaultEnvironment>()
.call(address)
.exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!(
"not_a_real_selector"
))))
.returns::<()>()
.try_invoke();
// Note: You can match on more specific `LangError`s here.
//
// See [`ink::LangError`] for more.
match result {
EnvResult::Ok(MessageResult::Err(_)) => {
self.value = !self.value;
}
_ => unimplemented!(),
}
}
Environment Error
#[ink(message)]
pub fn environment_error(&mut self, address: AccountId) {
let result = build_call::<DefaultEnvironment>()
.call(address)
.exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!(
"environment_error"
))))
.returns::<()>()
.try_invoke();
// Note: You can match on more specific `EnvError`s here.
//
// See [`ink::env::Error`] for more.
match result {
EnvResult::Err(_) => {
self.value = !self.value;
}
_ => unimplemented!()
}
}
-
amazing Hernando thanks for sharing! thanks to your help i also benefited from your response in this example that builds upon the basic-contract-caller example github.com/ltfschoen/InkTemplate/pull/16/commits/… although i couldn't find any documentation about
EnvError
in the docs docs.rs/ink/latest/ink/?search=EnvError, but i found the different error codes in the ink! codebase Commented Jun 19, 2023 at 3:39