5

Background

I have two contracts A and B, I can do cross-contract call from A to B, or from B to A. But it failed when I try to do cross-contract call like this: A -> B -> A.

Test code

I developed a contract for test, repo is https://github.com/hthuang996/test/tree/main/ink/flipper. There are four methods.

#[ink(message)]
pub fn send_message(&mut self, addr1: AccountId, addr2: AccountId, m: u8) {
    ink_env::call::build_call::<ink_env::DefaultEnvironment>()
        .call_type(
            ink_env::call::Call::new()
                .callee(addr1)
                .gas_limit(0)
                .transferred_value(0))
        .exec_input(
            // call receive_message
            ink_env::call::ExecutionInput::new(ink_env::call::Selector::new([0x3a, 0x6e, 0x96, 0x96]))
            .push_arg(addr2)
            .push_arg(m)
        )
        .returns::<()>()
        .fire().
        unwrap();
}

#[ink(message)]
pub fn receive_message(&mut self, addr: AccountId, i: u8) {
    self.message = i;
    ink_env::call::build_call::<ink_env::DefaultEnvironment>()
        .call_type(
            ink_env::call::Call::new()
                .callee(addr)
                .gas_limit(0)
                .transferred_value(0))
        .exec_input(
            // call receive_message2
            ink_env::call::ExecutionInput::new(ink_env::call::Selector::new([0x03, 0x0e, 0x11, 0xd0]))
            .push_arg(i)
        )
        .returns::<()>()
        .fire().
        unwrap();
}

#[ink(message)]
pub fn receive_message2(&mut self, i: u8) {
    self.message = i;
}

#[ink(message)]
pub fn get_message(& self) -> u8 {
    self.message
}

Test step

Here I use substrate-contracts-node.

Step 1

Build the contract.

Step 2

Deploy three contracts named A, B, C with the same wasm file.

Step 3

Call send_message of A, params are B, A, 8.

Result

The transaction failed.

But If I change 3 as shown below:
Call send_message of A, params are B, C, 8.

The transaction was successful, and get_message of B, C were both 8.

Question

Is there any error in my code, or the circular call between multi contracts is impossible in ink!?

1

2 Answers 2

2

What you call "circular" is usually called "reentrant": The control flow enters A a second time in the same call stack. This is disallowed by default for security reasons. In order to make your code work you need to explicitly allow it:

#[ink(message)]
pub fn send_message(&mut self, addr1: AccountId, addr2: AccountId, m: u8) {
    ink_env::call::build_call::<ink_env::DefaultEnvironment>()
        .call_type(
            ink_env::call::Call::new()
                .callee(addr1)
                .gas_limit(0)
                .transferred_value(0))
        .exec_input(
            // call receive_message
            ink_env::call::ExecutionInput::new(ink_env::call::Selector::new([0x3a, 0x6e, 0x96, 0x96]))
            .push_arg(addr2)
            .push_arg(m)
        )
        .call_flags(CallFlags::default().set_allow_reentry(true))
        .returns::<()>()
        .fire().
        unwrap();
}

2
  • 1
    Great, the transaction was successful after I add the call flag. But the message field of contract A was not set, namely message is still 0. Commented Jun 18, 2022 at 13:21
  • I met the same problem, and I found maybe the reason is related to the call_flags(CallFlags::default().set_allow_reentry(true)). I will express some details in the following.
    – Shawn Z
    Commented Jun 19, 2022 at 9:40
0

I found that if we use call_flags(CallFlags::default().set_allow_reentry(true)) we can make our smart contract be called in the way of cross contract call by itself. Of course this is meaningless, but maybe it's the same reason causes @george huang's problem. The related code is as following:

#[ink(message)]
        pub fn receive_message(&mut self, addr: AccountId, i: u32) {

            ink_env::call::build_call::<ink_env::DefaultEnvironment>()
                .call_type(
                    ink_env::call::Call::new()
                        .callee(addr)
                        .gas_limit(0)
                        .transferred_value(0))
                .exec_input(
                    // call `update_message` of contract `addr`
                    ink_env::call::ExecutionInput::new(ink_env::call::Selector::new([0x03, 0x2a, 0x6f, 0x29]))
                    .push_arg(i)
                )
                .call_flags(ink_env::CallFlags::default().set_allow_reentry(true))
                .returns::<()>()
                .fire().
                unwrap();
        }

        #[ink(message)]
        pub fn update_message(&mut self, i: u32) {
            Self::env().emit_event(EventRecv2{
                triggered: true,
            });
            self.message = i;
        }

I call receive_message but the input parameter addr is just the same smart contract, that is, I use set_allow_reentry(true) to make my smart contract be able to call itself in a cross-contract call way.

The result is the same as @george huang met, we cannot update the status of the smart contract in update_message. I'm sure update_message is executed because the event EventRecv2 is caught.

So is this a bug? Or should we make some special settings to solve this problem?

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.