7

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?

1
  • 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
    – HCastano
    Commented Mar 17, 2023 at 3:17

1 Answer 1

7

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

  1. Contract Errors: defined by the smart contract you're interacting with
  2. Language Errors (ink::LangError): defined by the programming language you're using, e.g ink!
  3. Environment Errors (ink::env::Error): defined by the underlying execution environment, e.g pallet-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!()
    }
}
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.