Make System Configurable

We have one more step to take to make our Runtime as generic and configurable as possible.

To do it, we will need to take advantage of traits.

Custom Traits

We have already used traits provided to us in order to make our types generic.

Let's take a quick look at how you can define a custom trait:

#![allow(unused)]
fn main() {
pub trait Config {}
}

Traits can contain within it two things:

  1. functions which must be implemented by the type
  2. associated types

Custom Functions

The more obvious use of traits is to define custom functions

Let's say we want to expose a function which returns the name of something.

You could a trait GetName:

#![allow(unused)]
fn main() {
pub trait GetName {
	fn name() -> String;
}
}

Then you could implement this trait for any object.

#![allow(unused)]
fn main() {
struct Shawn;
impl GetName for Shawn {
	fn name() -> String {
		return "shawn".to_string();
	}
}
}

And then call that function on the object which implements it.

fn main() {
	println!("{}", Shawn::name());
}

We won't actually use this feature of traits in our simple blockchain, but there are plenty of use cases for this when developing more complex blockchain systems.

Associated Types

The other thing you can do with traits is define Associated Types.

This is covered in chapter 19 of the Rust Book under "Advance Traits".

Let's learn this concept by first looking at the problem we are trying to solve.

So far our simple blockchain code looks perfectly fine with generic types. However, let's imagine that our blockchain becomes more and more complex, requiring more and more generic types.

For example:

#![allow(unused)]
fn main() {
pub struct Pallet<AccountId, BlockNumber, BlockLength, BlockWeight, Hash, Nonce, Runtime, Version, ...> {
	// a bunch of stuff
}
}

Imagine every time you wanted to instantiate this struct, you would need to fill out each and every one of those types. Well systems do get this complex, and more, and the ability to abstract these types one level further can really simplify your code and make it much more readable.

For this we will use a trait with a bunch of associated types:

#![allow(unused)]
fn main() {
pub trait Config {
	type AccountId: Ord;
	type BlockNumber: Zero + One + AddAssign + Copy;
	type Nonce: Zero + One + Copy;
	// and more if needed
}
}

Then we can define our generic type using a single generic parameter!

#![allow(unused)]
fn main() {
pub struct Pallet<T: Config> {
	block_number: T::BlockNumber,
	nonce: BTreeMap<T::AccountId, T::Nonce>,
}
}

and implement functions using:

#![allow(unused)]
fn main() {
impl<T: Config> Pallet<T> {
	// functions using types from T here
}
}

Let's try to understand this syntax real quick.

  1. There is a generic type T. T has no meaningful name because it represents a bunch of stuff, and this is the convention most commonly used in Rust.
  2. T is required to implement the trait Config, which we previously defined.
  3. Because T implements Config, and Config has the associated types AccountId, BlockNumber, and Nonce, we can access those types like so:
    • T::AccountId
    • T::BlockNumber
    • T::Nonce

There is no meaningful difference between what we had before with 3 generic parameters, and a single generic parameter represented by a Config trait, but it certainly makes everything more scalable, easy to read, and easy to configure.

In this context, we call the trait Config because it is used to configure all the types for our Pallet.

Implementing the Config Trait

Let's round this out with showing how you can actually implement and use the Config trait.

Just like before, we need some object which will implement this trait. In our case, we can use the Runtime struct itself.

#![allow(unused)]
fn main() {
impl system::Config for Runtime {
	type AccountId = String;
	type BlockNumber = u32;
	type Nonce = u32;
}
}

Then, when defining the system::Pallet within the Runtime, we can use the following syntax:

#![allow(unused)]
fn main() {
pub struct Runtime {
	system: system::Pallet<Self>,
}
}

Here we are basically saying that Pallet will use Runtime as its generic type, but this is defined within the Runtime, so we refer to it as Self.

Make Your System Configurable

Phew. That was a lot.

Let's practice all you have learned to create Config trait for your System Pallet, and then configure the pallet for the Runtime in main.rs.

  1. Define the Config trait which will have your 3 associated types AccountId, BlockNumber, and Nonce.
  2. Make sure these types have their trait constraints defined in Config.
  3. Update your struct Pallet to use T: Config and reference your types using the T:: syntax.
  4. Update all of your functions to use the T:: syntax.
  5. Update your test, creating a struct TestConfig, and implementing Config for it, and using it to instantiate your Pallet struct.
  6. Go to your main.rs file, and implement system::Config for the Runtime struct.
  7. Update your Runtime definition to instantiate system::Pallet with Self.

Again, this is a big step for new Rust developers, and a common place that people can get very confused.

You will have the opportunity to do this whole process again for the Balances Pallet, so don't be afraid to peek at the solution this time around if you cannot get your code working.

Really take time to understand this step, what is happening, and what all of this syntax means to Rust.

Remember that Rust is a language which is completely type safe, so end of the day, all of these generic types and configurations need to make sense to the Rust compiler.

mod balances;
mod system;

// 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;
}

/*
	TODO:
	Implement the `system::Config` trait you created on your `Runtime`.
	Use `Self` to satisfy the generic parameter required for `system::Pallet`.
*/

// This is our main Runtime.
// It accumulates all of the different pallets we want to use.
#[derive(Debug)]
pub struct Runtime {
	system: system::Pallet<types::AccountId, types::BlockNumber, types::Nonce>,
	balances: balances::Pallet<types::AccountId, 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() }
	}
}

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);
}
#![allow(unused)]
fn main() {
use core::ops::AddAssign;
use num::traits::{One, Zero};
use std::collections::BTreeMap;

/*
	TODO: Combine all generic types and their trait bounds into a single `pub trait Config`.
	When you are done, your `Pallet` can simply be defined with `Pallet<T: Config>`.
*/

/// This is the System Pallet.
/// It handles low level state needed for your blockchain.
#[derive(Debug)]
pub struct Pallet<AccountId, BlockNumber, Nonce> {
	/// The current block number.
	block_number: BlockNumber,
	/// A map from an account to their nonce.
	nonce: BTreeMap<AccountId, Nonce>,
}

/*
	TODO: Update all of these functions to use your new configuration trait.
*/

impl<AccountId, BlockNumber, Nonce> Pallet<AccountId, BlockNumber, Nonce>
where
	AccountId: Ord + Clone,
	BlockNumber: Zero + One + AddAssign + Copy,
	Nonce: Zero + One + Copy,
{
	/// Create a new instance of the System Pallet.
	pub fn new() -> Self {
		Self { block_number: BlockNumber::zero(), nonce: BTreeMap::new() }
	}

	/// Get the current block number.
	pub fn block_number(&self) -> BlockNumber {
		self.block_number
	}

	// This function can be used to increment the block number.
	// Increases the block number by one.
	pub fn inc_block_number(&mut self) {
		self.block_number += BlockNumber::one();
	}

	// Increment the nonce of an account. This helps us keep track of how many transactions each
	// account has made.
	pub fn inc_nonce(&mut self, who: &AccountId) {
		let nonce: Nonce = *self.nonce.get(who).unwrap_or(&Nonce::zero());
		let new_nonce = nonce + Nonce::one();
		self.nonce.insert(who.clone(), new_nonce);
	}
}

#[cfg(test)]
mod test {
	/*
		TODO: Create a `struct TestConfig`, and implement `super::Config` on it with concrete types.
		Use this struct to instantiate your `Pallet`.
	*/

	#[test]
	fn init_system() {
		let mut system = super::Pallet::<String, u32, u32>::new();
		system.inc_block_number();
		system.inc_nonce(&"alice".to_string());

		assert_eq!(system.block_number(), 1);
		assert_eq!(system.nonce.get("alice"), Some(&1));
		assert_eq!(system.nonce.get("bob"), None);
	}
}
}
mod balances;
mod system;

// 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;
}

// 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<types::AccountId, types::Balance>,
}

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

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() }
	}
}

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);
}
#![allow(unused)]
fn main() {
use core::ops::AddAssign;
use num::traits::{One, Zero};
use std::collections::BTreeMap;

/// The configuration trait for the System Pallet.
/// This controls the common types used throughout our state machine.
pub trait Config {
	/// A type which can identify an account in our state machine.
	/// On a real blockchain, you would want this to be a cryptographic public key.
	type AccountId: Ord + Clone;
	/// A type which can be used to represent the current block number.
	/// Usually a basic unsigned integer.
	type BlockNumber: Zero + One + AddAssign + Copy;
	/// A type which can be used to keep track of the number of transactions from each account.
	/// Usually a basic unsigned integer.
	type Nonce: Zero + One + Copy;
}

/// This is the System Pallet.
/// It handles low level state needed for your blockchain.
#[derive(Debug)]
pub struct Pallet<T: Config> {
	/// The current block number.
	block_number: T::BlockNumber,
	/// A map from an account to their nonce.
	nonce: BTreeMap<T::AccountId, T::Nonce>,
}

/// The System Pallet is a low level system which is not really meant to be exposed to the outside
/// world. Instead, these functions are used by your low level blockchain systems.
impl<T: Config> Pallet<T> {
	/// Create a new instance of the System Pallet.
	pub fn new() -> Self {
		Self { block_number: T::BlockNumber::zero(), nonce: BTreeMap::new() }
	}

	/// Get the current block number.
	pub fn block_number(&self) -> T::BlockNumber {
		self.block_number
	}

	// This function can be used to increment the block number.
	// Increases the block number by one.
	pub fn inc_block_number(&mut self) {
		self.block_number += T::BlockNumber::one();
	}

	// Increment the nonce of an account. This helps us keep track of how many transactions each
	// account has made.
	pub fn inc_nonce(&mut self, who: &T::AccountId) {
		let nonce: T::Nonce = *self.nonce.get(who).unwrap_or(&T::Nonce::zero());
		let new_nonce = nonce + T::Nonce::one();
		self.nonce.insert(who.clone(), new_nonce);
	}
}

#[cfg(test)]
mod test {
	struct TestConfig;
	impl super::Config for TestConfig {
		type AccountId = String;
		type BlockNumber = u32;
		type Nonce = u32;
	}

	#[test]
	fn init_system() {
		let mut system = super::Pallet::<TestConfig>::new();
		system.inc_block_number();
		system.inc_nonce(&"alice".to_string());

		assert_eq!(system.block_number(), 1);
		assert_eq!(system.nonce.get("alice"), Some(&1));
		assert_eq!(system.nonce.get("bob"), None);
	}
}
}
diff --git a/src/main.rs b/src/main.rs
index 8d30b1be..d681065c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,12 @@ mod types {
 	pub type Nonce = u32;
 }
 
+/*
+	TODO:
+	Implement the `system::Config` trait you created on your `Runtime`.
+	Use `Self` to satisfy the generic parameter required for `system::Pallet`.
+*/
+
 // This is our main Runtime.
 // It accumulates all of the different pallets we want to use.
 #[derive(Debug)]
diff --git a/src/system.rs b/src/system.rs
index c7563896..1f061b1d 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -2,6 +2,11 @@ use core::ops::AddAssign;
 use num::traits::{One, Zero};
 use std::collections::BTreeMap;
 
+/*
+	TODO: Combine all generic types and their trait bounds into a single `pub trait Config`.
+	When you are done, your `Pallet` can simply be defined with `Pallet<T: Config>`.
+*/
+
 /// This is the System Pallet.
 /// It handles low level state needed for your blockchain.
 #[derive(Debug)]
@@ -12,6 +17,10 @@ pub struct Pallet<AccountId, BlockNumber, Nonce> {
 	nonce: BTreeMap<AccountId, Nonce>,
 }
 
+/*
+	TODO: Update all of these functions to use your new configuration trait.
+*/
+
 impl<AccountId, BlockNumber, Nonce> Pallet<AccountId, BlockNumber, Nonce>
 where
 	AccountId: Ord + Clone,
@@ -45,6 +54,11 @@ where
 
 #[cfg(test)]
 mod test {
+	/*
+		TODO: Create a `struct TestConfig`, and implement `super::Config` on it with concrete types.
+		Use this struct to instantiate your `Pallet`.
+	*/
+
 	#[test]
 	fn init_system() {
 		let mut system = super::Pallet::<String, u32, u32>::new();
diff --git a/src/main.rs b/src/main.rs
index d681065c..40dc8ac5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,20 +11,20 @@ mod types {
 	pub type Nonce = u32;
 }
 
-/*
-	TODO:
-	Implement the `system::Config` trait you created on your `Runtime`.
-	Use `Self` to satisfy the generic parameter required for `system::Pallet`.
-*/
-
 // This is our main Runtime.
 // It accumulates all of the different pallets we want to use.
 #[derive(Debug)]
 pub struct Runtime {
-	system: system::Pallet<types::AccountId, types::BlockNumber, types::Nonce>,
+	system: system::Pallet<Self>,
 	balances: balances::Pallet<types::AccountId, types::Balance>,
 }
 
+impl system::Config for Runtime {
+	type AccountId = types::AccountId;
+	type BlockNumber = types::BlockNumber;
+	type Nonce = types::Nonce;
+}
+
 impl Runtime {
 	// Create a new instance of the main Runtime, by creating a new instance of each pallet.
 	fn new() -> Self {
diff --git a/src/system.rs b/src/system.rs
index 1f061b1d..8bef9918 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -2,66 +2,70 @@ use core::ops::AddAssign;
 use num::traits::{One, Zero};
 use std::collections::BTreeMap;
 
-/*
-	TODO: Combine all generic types and their trait bounds into a single `pub trait Config`.
-	When you are done, your `Pallet` can simply be defined with `Pallet<T: Config>`.
-*/
+/// The configuration trait for the System Pallet.
+/// This controls the common types used throughout our state machine.
+pub trait Config {
+	/// A type which can identify an account in our state machine.
+	/// On a real blockchain, you would want this to be a cryptographic public key.
+	type AccountId: Ord + Clone;
+	/// A type which can be used to represent the current block number.
+	/// Usually a basic unsigned integer.
+	type BlockNumber: Zero + One + AddAssign + Copy;
+	/// A type which can be used to keep track of the number of transactions from each account.
+	/// Usually a basic unsigned integer.
+	type Nonce: Zero + One + Copy;
+}
 
 /// This is the System Pallet.
 /// It handles low level state needed for your blockchain.
 #[derive(Debug)]
-pub struct Pallet<AccountId, BlockNumber, Nonce> {
+pub struct Pallet<T: Config> {
 	/// The current block number.
-	block_number: BlockNumber,
+	block_number: T::BlockNumber,
 	/// A map from an account to their nonce.
-	nonce: BTreeMap<AccountId, Nonce>,
+	nonce: BTreeMap<T::AccountId, T::Nonce>,
 }
 
-/*
-	TODO: Update all of these functions to use your new configuration trait.
-*/
-
-impl<AccountId, BlockNumber, Nonce> Pallet<AccountId, BlockNumber, Nonce>
-where
-	AccountId: Ord + Clone,
-	BlockNumber: Zero + One + AddAssign + Copy,
-	Nonce: Zero + One + Copy,
-{
+/// The System Pallet is a low level system which is not really meant to be exposed to the outside
+/// world. Instead, these functions are used by your low level blockchain systems.
+impl<T: Config> Pallet<T> {
 	/// Create a new instance of the System Pallet.
 	pub fn new() -> Self {
-		Self { block_number: BlockNumber::zero(), nonce: BTreeMap::new() }
+		Self { block_number: T::BlockNumber::zero(), nonce: BTreeMap::new() }
 	}
 
 	/// Get the current block number.
-	pub fn block_number(&self) -> BlockNumber {
+	pub fn block_number(&self) -> T::BlockNumber {
 		self.block_number
 	}
 
 	// This function can be used to increment the block number.
 	// Increases the block number by one.
 	pub fn inc_block_number(&mut self) {
-		self.block_number += BlockNumber::one();
+		self.block_number += T::BlockNumber::one();
 	}
 
 	// Increment the nonce of an account. This helps us keep track of how many transactions each
 	// account has made.
-	pub fn inc_nonce(&mut self, who: &AccountId) {
-		let nonce: Nonce = *self.nonce.get(who).unwrap_or(&Nonce::zero());
-		let new_nonce = nonce + Nonce::one();
+	pub fn inc_nonce(&mut self, who: &T::AccountId) {
+		let nonce: T::Nonce = *self.nonce.get(who).unwrap_or(&T::Nonce::zero());
+		let new_nonce = nonce + T::Nonce::one();
 		self.nonce.insert(who.clone(), new_nonce);
 	}
 }
 
 #[cfg(test)]
 mod test {
-	/*
-		TODO: Create a `struct TestConfig`, and implement `super::Config` on it with concrete types.
-		Use this struct to instantiate your `Pallet`.
-	*/
+	struct TestConfig;
+	impl super::Config for TestConfig {
+		type AccountId = String;
+		type BlockNumber = u32;
+		type Nonce = u32;
+	}
 
 	#[test]
 	fn init_system() {
-		let mut system = super::Pallet::<String, u32, u32>::new();
+		let mut system = super::Pallet::<TestConfig>::new();
 		system.inc_block_number();
 		system.inc_nonce(&"alice".to_string());