5

Can someone tell me how can you execute a smart contract method inside your custom pallet?

You can call your smart contract tx/query through the RPC API but how can you call it inside a custom pallet?

<pallet_contracts::Module<T>>::call().transfer();

1 Answer 1

6

It's pretty straightforward to call a contract from a pallet. I'm going to assume you're calling an ink! contract with the following message for the purposes of this answer.

#[ink(message)]
pub fn flip(&mut self, foo: u32) {
    todo!()
}

I'm also gonna assume this contract has already been deployed on-chain.

You'll need a couple of things:

  • Some sort of coupling to pallet-contracts
  • Knowledge about the contract's message selector and arguments

With that you'll able to use pallet_contracts::bare_call to call into our contract.

Coupling

Your custom pallet needs to have access to the interface from the Contracts pallet. It can do this in one of two ways:

I'm not gonna get into the details here, but just know that you have to use one of them. We'll use tight coupling for our example.

Selector and Arguments

The message selector is the identifier used to pick out which of the many functions in your smart contract should be called. Assuming you have an ink! contract, you can find this info in the metadata generated by cargo-contract.

When you do cargo +nightly contract build you'll get something like:

Your contract artifacts are ready. You can find them in:
/private/tmp/flipper/target/ink

  - flipper.contract (code + metadata)
  - flipper.wasm (the contract's code)
  - metadata.json (the contract's metadata)

In the metadata.json file you'll find a section like this:

"messages": [
  {
    "args": [
      {
        "label": "foo",
        "type": {
          "displayName": [
            "u32"
          ],
          "type": 4
        }
      }
    ],
    "docs": [],
    "label": "flip",
    "mutates": true,
    "payable": false,
    "returnType": null,
    "selector": "0x633aa551"
  }
]

Find the message you want to call and take a note of the selector bytes. If your function has any arguments you'll also need to take a note of what those are.

Crafting a bare_call

The bare_call function takes an input_data buffer. This buffer needs to be of the form:

[ MESSAGE_SELECTOR | SCALE_ENCODED_ARG_1 | ... | SCALE_ENCODED_ARG_N ]

We can craft this as follows:

let mut selector: Vec<u8> = [0x63, 0x3A, 0xA5, 0x51].into();
let mut message_arg = 15663040u32.encode();

let mut data = Vec::new();
data.append(&mut selector);
data.append(&mut message_arg);

Now, to put it call together. In our pallet we'll need a call (so a function within the #[pallet::call] macro) which will itself call bare_call:

pub fn call_contract(
    origin: OriginFor<T>,
    dest: T::AccountId, // <- This is the address of the deployed contract we're calling
) -> DispatchResult {
    let who = ensure_signed(origin)?;

    // Amount to transfer to the message. Not gonna transfer anything here, so we'll
    // leave this as `0`.
    let value: BalanceOf<T> = Default::default();

    // You'll have to play around with this depending on your contract. I don't recommend
    // hardcoding it but for demo purposes this'll do the trick
    let gas_limit = 10_000_000_000;
    let debug = false;

    // Remember, we pulled this out from the `metadata.json` file.
    //
    // Again, probably shouldn't be hardcoded but :shrug:
    let mut selector: Vec<u8> = [0x63, 0x3A, 0xA5, 0x51].into();
    let mut message_arg = 15663040u32.encode();

    let mut data = Vec::new();
    data.append(&mut selector);
    data.append(&mut message_arg);

    // This ends up calling our contract!
    pallet_contracts::Pallet::<T>::bare_call(
        who,
        dest.clone(),
        value,
        gas_limit,
        data,
        debug,
    )
    .result?;

    Ok(())
}

Further Reading

6
  • Thanks so much, you are a godsent.
    – hgminerva
    Commented Jul 31, 2022 at 4:36
  • @hgminerva do you mind marking this as answered :D?
    – HCastano
    Commented Aug 1, 2022 at 22:42
  • sorry I forgot, Done!
    – hgminerva
    Commented Aug 2, 2022 at 0:48
  • How about the opposite way? Can I call methods in a pallet inside a smart contract?
    – marethyu
    Commented Sep 6, 2022 at 7:55
  • @marethyu yes, you can with chain extensions. See here substrate.stackexchange.com/questions/1951/…
    – HCastano
    Commented Sep 6, 2022 at 23:40

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.