3

I'm trying to use the Scheduler pallet to schedule a call to the Contracts pallet as part of a chain extension implementation.

I've managed to get the following to compile and execute:

// In `runtime/src/chain_extension.rs`
pub struct MyExtension;

impl<T> ChainExtension<T> for MyExtension
where
    T: pallet_contracts::Config + pallet_scheduler::Config,
{
    fn call<E>(
        func_id: u32,
        env: Environment<'_, '_, E, InitState>,
    ) -> Result<RetVal, DispatchError>
{
...

    let call: <T as pallet_scheduler::Config>::Call =
        frame_system::Call::remark {
            remark: 0u32.encode(),
        }
        .into();

    pallet_scheduler::Pallet::<T>::schedule(
        RawOrigin::Signed(caller).into(),
        at.into(),
        None,
        Default::default(),
        call,
    )?;

...
    }
}

Now, when I try and craft a Call to the Contracts pallet like so:

let call = pallet_contracts::Call::remove_code {
    code_hash: Default::default(),
};

let call = crate::Box::new(MaybeHashed::Value(call));

It fails with the following error:

     Compiling contracts-node-runtime v0.16.0 (/home/hernando/remote-builds/17766829191224950667/runtime)
  error[E0308]: mismatched types
     --> /home/hernando/remote-builds/17766829191224950667/runtime/src/chain_extension.rs:151:21
      |
  146 |                 pallet_scheduler::Pallet::<T>::schedule(
      |                 --------------------------------------- arguments to this function are incorrect
  ...
  151 |                     call,
      |                     ^^^^ expected associated type, found enum `pallet_contracts::Call`
      |
      = note: expected struct `sp_std::prelude::Box<MaybeHashed<<T as pallet_scheduler::Config>::Call, <T as SysConfig>::Hash>>`
                 found struct `sp_std::prelude::Box<MaybeHashed<pallet_contracts::Call<_>, _>>`
      = help: consider constraining the associated type `<T as pallet_scheduler::Config>::Call` to `pallet_contracts::Call<_>` or calling a method that returns `<T as pallet_scheduler::Config>::Call`
      = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
  note: associated function defined here
     --> /home/hernando/.cargo/git/checkouts/substrate-7e08433d4c370a21/c0ee2ad/frame/scheduler/src/lib.rs:425:10
      |
  425 |         pub fn schedule(
      |                ^^^^^^^^

  For more information about this error, try `rustc --explain E0308`.
  error: could not compile `contracts-node-runtime` due to previous error

Under the hood the Scheduler::schedule extrinsic is expecting a <T as pallet_scheduler::Config>::Call, which from the Config definition is the following:

type Call: Parameter
    + Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>
    + GetDispatchInfo
    + From<system::Call<Self>>;

So the error makes sense. According to the From implementation we can only turn system Calls into Scheduler Calls, which is why remark worked and remove_code didn't.

Successful Dispatches of non-system Calls

I've tried manually scheduling a Contracts call using the Polkadot JS UI and I'm able to schedule and execute it successfully. Below is the extrinsic it built:

polkadot-js-apps-contract-call

There's also the Referenda pallet which implements some scheduler related traits and that allows it to specify its own Call type.

#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + Sized {
    /// The Scheduler.
    type Scheduler: ScheduleAnon<
            Self::BlockNumber,
            CallOf<Self, I>,
            PalletsOriginOf<Self>,
            Hash = Self::Hash,
        > + ScheduleNamed<
            Self::BlockNumber,
            CallOf<Self, I>,
            PalletsOriginOf<Self>,
            Hash = Self::Hash,
        >;
}

Questions

  • How is Polkadot JS Apps able to successfully build a Contract Call and schedule it?
  • How am I able to replicate this in Rust without building a wrapper pallet a la pallet-referenda?

1 Answer 1

9

Alright, so I had a chat with Kian about this and he gave me some tips for how to approach this.

First, we need to distinguish between Runtime Calls and Pallet Calls.

The Runtime calls are those that are exposed by the runtime. These are generated by the construct_runtime! macro. This type of call indicates which of the pallets included by the Runtime expose dispatchable functions (a.k.a extrinsics).

Pallet calls are generated from the #[pallet::call] FRAME macro and indicate which dispatchables (extrinsics) are available in a given pallet.

Now, with that background we can break down the error a bit more.

First, let's take a look at a slightly simplified version of the pallet_scheduler::Call::schedule() function signature:

fn schedule(
    origin: OriginFor<T>,
    ...
    call: <T as pallet_scheduler::Config>::Call,

) -> DispatchResult; 

This is expecting a Runtime Call. We can tell this because it is going through the associated type Call of the Scheduler pallet's Config trait, which is wired up in the Runtime.

Now let's look at the original error again:

= note: expected struct `sp_std::prelude::Box<MaybeHashed<<T as pallet_scheduler::Config>::Call, <T as SysConfig>::Hash>>`
                 found struct `sp_std::prelude::Box<MaybeHashed<pallet_contracts::Call<_>, _>>`

We are handing the function a pallet_contracts::Call, which is a Pallet Call. We know this because we are going directly through the pallet_contracts module.

So the question now becomes, how can we provide a Runtime Call instead of a Pallet Call to the schedule()?

In order to build a Runtime Call for the Contracts pallet we can do the following:

// Note, `crate` refers to our `runtime/src/lib.rs`
let call = crate::Call::Contracts(pallet_contracts::Call::remove_code {
    code_hash: Default::default(),
});

However, this won't work just yet. Remember that we need this to be of the same type as the Scheduler pallet's associated type Call.

Because we're working with generics (as usual when we deal with Substrate) we need to add the following trait bound in the definition of our chain extension:

impl<T> ChainExtension<T> for MyExtension
where
    T: pallet_contracts::Config + pallet_scheduler::Config,
    <T as pallet_scheduler::Config>::Call: From<crate::Call>, // <- Added this
{ ... }

This specifies a requirement that we need be able to convert any Runtime Call into a Runtime call for the Scheduler pallet.

Luckily for us the construct_runtime! macro provides this implementation. So with that we can now convert our Call into the correct type.

let call: <T as pallet_scheduler::Config>::Call =
    crate::Call::Contracts(pallet_contracts::Call::remove_code {
        code_hash: Default::default(),
    })
    .into();

We can now pass this into the Scheduler::schedule() extrinsic and happily go about our day!

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.