- 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!