3
let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;

https://github.com/paritytech/polkadot/blob/master/xcm/xcm-executor/src/lib.rs#L342

I got this code above and tried to find the specific decoding code but failed.

So what is the specific decoding process? How does the encoded transaction in Transact turn into decoded transaction call?

2 Answers 2

3

Every XCM contains a type parameter Call that allows you to specify exactly what the type of the Call is. Thus, if you want to turn into a specific Call type, you have to ensure that your XCM type properly contains the Call type that you want to decode into. The way it then gets decoded is then determined by your Call type's Decode implementation.

You can utilize the Xcm::<Call>::from method to change from a Call type to another, like so:

use runtime::Call;

let foo = Xcm::<()>::ClearOrigin;
let bar = Xcm::<Call>::from(foo);
2
1
  • first byte: pallet id,
  • second byte: call position in the order defined on the pallet,
  • third byte and onwards: arguments encoded in SCALE, and put in the order defined on the fn parameters definition (except origin argument).

For example, if you want to execute the do_something call of pallet_template, first you see the id of the pallet in the destination runtime:

construct_runtime! {
    pub enum Runtime where
        Block = Block,
        NodeBlock = primitives::v2::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // Basic stuff; balances is uncallable initially.
        System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 0,
        ...
        TemplatePallet: pallet_template::{Pallet, Call, Storage, Event<T>}  = 40,
    }
}

So your first byte will need to be equal to 40 in decimal.

Then you look at the order in which the functions are defined on the pallet:

#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(...)]
    pub fn do_something(...) -> DispatchResultWithPostInfo {...}

    #[pallet::weight(...)]
    pub fn cause_error(...) -> DispatchResultWithPostInfo {...}
}

The position of the do_something function is number 1, so your second byte should equal 1 in decimal.

You finally look at the parameters of the function:

pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResultWithPostInfo {...}

Without counting the origin, you only have one parameter. So the number of bytes will be decided by how SCALE encoding works on u32, but that is something you can easily find, and should not worry to much about.

Note:

I found this information by playing around the code myself, and using the cargo expand function to see how the pallet and runtime look after the macros. I highly recommend using it to understand the code a bit better.

I used this knowledge to create a mock Call enum that derives Encode and Decode. This way one runtime can create a valid encoded pallet call of another runtime without needing all of the underlying logic.

For example, if you have on one runtime a very complex call on pallet id 42, and function position 4, with a first argument u32 and a second argument Vec, you can create a mock call that will get encoded the same as the original pallet as following:

#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
#[allow(non_camel_case_types)]
enum MockCall {
    #[codec(index = 0u8)]
    first_fn {
        first_item: u32,
        second_item: Vec<u32>, 
    }
}

The names of the enum, function, and parameters do not matter, only the order and parameter types. You also need to derive the scale encoding!

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.