Dispatching Calls

We have built our execute_block logic depending on the dispatch logic we have not implemented yet.

Let's do that.

Adding Our Calls

Dispatch logic is all about routing a user's extrinsic to the proper Pallet function. So far, the only user callable function we have created is the transfer function in the Balances Pallet.

So let's add that call to our RuntimeCall enum.

Our transfer function expects 3 inputs:

  • caller: The account calling the transfer function, and whose balance will be reduced.
  • to: The account where the funds will be sent.
  • amount: The amount of funds to transfer.

However, remember that our dispatch logic already has information about the caller which is coming from the Extrinsic in the Block. So we do not need this data again in the RuntimeCall.

In fact, every Call in our runtime should omit the caller, and know that it is being provided by our dispatch logic.

So when adding a new variant to RuntimeCall, it should look something like:

#![allow(unused)]
fn main() {
pub enum RuntimeCall {
	BalancesTransfer { to: types::AccountId, amount: types::Balance },
}
}

A user submitting an extrinsic to our state machine can use this enum variant to specify which function they want to call (transfer), and the parameters needed for that call.

Dispatch Logic

The core logic in the dispatch function is a simple match statement.

Basically, given some RuntimeCall, we need to match on the variant being provided to us, and then pass the appropriate parameters to the correct Pallet function. As mentioned before, dispatch already has access to the caller information, so the final logic is as simple as:

#![allow(unused)]
fn main() {
match runtime_call {
	RuntimeCall::BalancesTransfer { to, amount } => {
		self.balances.transfer(caller, to, amount)?;
	}
}
}

Dispatch logic really is that simple!

Note that we propagate up any errors returned by our function call with the ? operator. This is important if you want to see the error messages that we set up in the execute_block logic.

Write Your Dispatch Logic

Follow the TODOs provided in the template to build your RuntimeCall and complete your dispatch logic.

Your code should still compile with some "never constructed/used" warnings. Just one more step and we will get rid of all those warnings!

mod balances;
mod support;
mod system;

use crate::support::Dispatch;

// These are the concrete types we will use in our simple state machine.
// Modules are configured for these types directly, and they satisfy all of our
// trait requirements.
mod types {
	pub type AccountId = String;
	pub type Balance = u128;
	pub type BlockNumber = u32;
	pub type Nonce = u32;
	pub type Extrinsic = crate::support::Extrinsic<AccountId, crate::RuntimeCall>;
	pub type Header = crate::support::Header<BlockNumber>;
	pub type Block = crate::support::Block<Header, Extrinsic>;
}

// These are all the calls which are exposed to the world.
// Note that it is just an accumulation of the calls exposed by each module.
pub enum RuntimeCall {
	/* TODO: Create an enum variant `BalancesTransfer` which contains named fields:
		- `to`: an `AccountId`
		- `amount`: a `Balance`
	*/
}

// This is our main Runtime.
// It accumulates all of the different pallets we want to use.
#[derive(Debug)]
pub struct Runtime {
	system: system::Pallet<Self>,
	balances: balances::Pallet<Self>,
}

impl system::Config for Runtime {
	type AccountId = types::AccountId;
	type BlockNumber = types::BlockNumber;
	type Nonce = types::Nonce;
}

impl balances::Config for Runtime {
	type Balance = types::Balance;
}

impl Runtime {
	// Create a new instance of the main Runtime, by creating a new instance of each pallet.
	fn new() -> Self {
		Self { system: system::Pallet::new(), balances: balances::Pallet::new() }
	}

	// Execute a block of extrinsics. Increments the block number.
	fn execute_block(&mut self, block: types::Block) -> support::DispatchResult {
		self.system.inc_block_number();
		if block.header.block_number != self.system.block_number() {
			return Err("block number does not match what is expected")
		}
		// An extrinsic error is not enough to trigger the block to be invalid. We capture the
		// result, and emit an error message if one is emitted.
		for (i, support::Extrinsic { caller, call }) in block.extrinsics.into_iter().enumerate() {
			self.system.inc_nonce(&caller);
			let _res = self.dispatch(caller, call).map_err(|e| {
				eprintln!(
					"Extrinsic Error\n\tBlock Number: {}\n\tExtrinsic Number: {}\n\tError: {}",
					block.header.block_number, i, e
				)
			});
		}
		Ok(())
	}
}

impl crate::support::Dispatch for Runtime {
	type Caller = <Runtime as system::Config>::AccountId;
	type Call = RuntimeCall;
	// Dispatch a call on behalf of a caller. Increments the caller's nonce.
	//
	// Dispatch allows us to identify which underlying module call we want to execute.
	// Note that we extract the `caller` from the extrinsic, and use that information
	// to determine who we are executing the call on behalf of.
	fn dispatch(
		&mut self,
		caller: Self::Caller,
		runtime_call: Self::Call,
	) -> support::DispatchResult {
		/*
			TODO:
			Use a match statement to route the `runtime_call` to call the appropriate function in
			our pallet. In this case, there is only `self.balances.transfer`.

			Your `runtime_call` won't contain the caller information which is needed to make the
			`transfer` call, but you have that information from the arguments to the `dispatch`
			function.

			You should propagate any errors from the call back up this function.
		*/
		Ok(())
	}
}

fn main() {
	let mut runtime = Runtime::new();
	let alice = "alice".to_string();
	let bob = "bob".to_string();
	let charlie = "charlie".to_string();

	runtime.balances.set_balance(&alice, 100);

	// start emulating a block
	runtime.system.inc_block_number();
	assert_eq!(runtime.system.block_number(), 1);

	// first transaction
	runtime.system.inc_nonce(&alice);
	let _res = runtime
		.balances
		.transfer(alice.clone(), bob, 30)
		.map_err(|e| eprintln!("{}", e));

	// second transaction
	runtime.system.inc_nonce(&alice);
	let _res = runtime.balances.transfer(alice, charlie, 20).map_err(|e| eprintln!("{}", e));

	println!("{:#?}", runtime);
}
mod balances;
mod support;
mod system;

use crate::support::Dispatch;

// These are the concrete types we will use in our simple state machine.
// Modules are configured for these types directly, and they satisfy all of our
// trait requirements.
mod types {
	pub type AccountId = String;
	pub type Balance = u128;
	pub type BlockNumber = u32;
	pub type Nonce = u32;
	pub type Extrinsic = crate::support::Extrinsic<AccountId, crate::RuntimeCall>;
	pub type Header = crate::support::Header<BlockNumber>;
	pub type Block = crate::support::Block<Header, Extrinsic>;
}

// These are all the calls which are exposed to the world.
// Note that it is just an accumulation of the calls exposed by each module.
pub enum RuntimeCall {
	BalancesTransfer { to: types::AccountId, amount: types::Balance },
}

// This is our main Runtime.
// It accumulates all of the different pallets we want to use.
#[derive(Debug)]
pub struct Runtime {
	system: system::Pallet<Self>,
	balances: balances::Pallet<Self>,
}

impl system::Config for Runtime {
	type AccountId = types::AccountId;
	type BlockNumber = types::BlockNumber;
	type Nonce = types::Nonce;
}

impl balances::Config for Runtime {
	type Balance = types::Balance;
}

impl Runtime {
	// Create a new instance of the main Runtime, by creating a new instance of each pallet.
	fn new() -> Self {
		Self { system: system::Pallet::new(), balances: balances::Pallet::new() }
	}

	// Execute a block of extrinsics. Increments the block number.
	fn execute_block(&mut self, block: types::Block) -> support::DispatchResult {
		self.system.inc_block_number();
		if block.header.block_number != self.system.block_number() {
			return Err("block number does not match what is expected");
		}
		// An extrinsic error is not enough to trigger the block to be invalid. We capture the
		// result, and emit an error message if one is emitted.
		for (i, support::Extrinsic { caller, call }) in block.extrinsics.into_iter().enumerate() {
			self.system.inc_nonce(&caller);
			let _res = self.dispatch(caller, call).map_err(|e| {
				eprintln!(
					"Extrinsic Error\n\tBlock Number: {}\n\tExtrinsic Number: {}\n\tError: {}",
					block.header.block_number, i, e
				)
			});
		}
		Ok(())
	}
}

impl crate::support::Dispatch for Runtime {
	type Caller = <Runtime as system::Config>::AccountId;
	type Call = RuntimeCall;
	// Dispatch a call on behalf of a caller. Increments the caller's nonce.
	//
	// Dispatch allows us to identify which underlying module call we want to execute.
	// Note that we extract the `caller` from the extrinsic, and use that information
	// to determine who we are executing the call on behalf of.
	fn dispatch(
		&mut self,
		caller: Self::Caller,
		runtime_call: Self::Call,
	) -> support::DispatchResult {
		// This match statement will allow us to correctly route `RuntimeCall`s
		// to the appropriate pallet level function.
		match runtime_call {
			RuntimeCall::BalancesTransfer { to, amount } => {
				self.balances.transfer(caller, to, amount)?;
			},
		}
		Ok(())
	}
}

fn main() {
	let mut runtime = Runtime::new();
	let alice = "alice".to_string();
	let bob = "bob".to_string();
	let charlie = "charlie".to_string();

	runtime.balances.set_balance(&alice, 100);

	// start emulating a block
	runtime.system.inc_block_number();
	assert_eq!(runtime.system.block_number(), 1);

	// first transaction
	runtime.system.inc_nonce(&alice);
	let _res = runtime
		.balances
		.transfer(alice.clone(), bob, 30)
		.map_err(|e| eprintln!("{}", e));

	// second transaction
	runtime.system.inc_nonce(&alice);
	let _res = runtime.balances.transfer(alice, charlie, 20).map_err(|e| eprintln!("{}", e));

	println!("{:#?}", runtime);
}
diff --git a/src/main.rs b/src/main.rs
index 537d6072..ea8261d0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,7 +20,10 @@ mod types {
 // These are all the calls which are exposed to the world.
 // Note that it is just an accumulation of the calls exposed by each module.
 pub enum RuntimeCall {
-	// TODO: Not implemented yet.
+	/* TODO: Create an enum variant `BalancesTransfer` which contains named fields:
+		- `to`: an `AccountId`
+		- `amount`: a `Balance`
+	*/
 }
 
 // This is our main Runtime.
@@ -81,7 +84,18 @@ impl crate::support::Dispatch for Runtime {
 		caller: Self::Caller,
 		runtime_call: Self::Call,
 	) -> support::DispatchResult {
-		unimplemented!();
+		/*
+			TODO:
+			Use a match statement to route the `runtime_call` to call the appropriate function in
+			our pallet. In this case, there is only `self.balances.transfer`.
+
+			Your `runtime_call` won't contain the caller information which is needed to make the
+			`transfer` call, but you have that information from the arguments to the `dispatch`
+			function.
+
+			You should propagate any errors from the call back up this function.
+		*/
+		Ok(())
 	}
 }
 
diff --git a/src/main.rs b/src/main.rs
index ea8261d0..d3cbb031 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,10 +20,7 @@ mod types {
 // These are all the calls which are exposed to the world.
 // Note that it is just an accumulation of the calls exposed by each module.
 pub enum RuntimeCall {
-	/* TODO: Create an enum variant `BalancesTransfer` which contains named fields:
-		- `to`: an `AccountId`
-		- `amount`: a `Balance`
-	*/
+	BalancesTransfer { to: types::AccountId, amount: types::Balance },
 }
 
 // This is our main Runtime.
@@ -54,7 +51,7 @@ impl Runtime {
 	fn execute_block(&mut self, block: types::Block) -> support::DispatchResult {
 		self.system.inc_block_number();
 		if block.header.block_number != self.system.block_number() {
-			return Err("block number does not match what is expected")
+			return Err("block number does not match what is expected");
 		}
 		// An extrinsic error is not enough to trigger the block to be invalid. We capture the
 		// result, and emit an error message if one is emitted.
@@ -84,17 +81,13 @@ impl crate::support::Dispatch for Runtime {
 		caller: Self::Caller,
 		runtime_call: Self::Call,
 	) -> support::DispatchResult {
-		/*
-			TODO:
-			Use a match statement to route the `runtime_call` to call the appropriate function in
-			our pallet. In this case, there is only `self.balances.transfer`.
-
-			Your `runtime_call` won't contain the caller information which is needed to make the
-			`transfer` call, but you have that information from the arguments to the `dispatch`
-			function.
-
-			You should propagate any errors from the call back up this function.
-		*/
+		// This match statement will allow us to correctly route `RuntimeCall`s
+		// to the appropriate pallet level function.
+		match runtime_call {
+			RuntimeCall::BalancesTransfer { to, amount } => {
+				self.balances.transfer(caller, to, amount)?;
+			},
+		}
 		Ok(())
 	}
 }