Substrate

Substrate Runtime Recipes

Troubleshooting

Substrate is a rapidly evolving project, which means that breaking changes may cause you problems when trying to follow the instructions below. Feel free to contact us with any problems you encounter.

This page will contain a series of minimal working samples which demonstrate different features and functionalities available to you when developing a custom Substrate runtime.

If you have not followed our tutorial on creating a custom substrate chain, we recommend you do that before working with the samples below.

Prerequisites

If you do not have substrate installed on your machine, run:

curl https://getsubstrate.io -sSf | bash

Create a Substrate Node Template

In this tutorial we will be using the Polkadot UI to interact with our node. To start, create an instance of the substrate-node-template using the following command:

substrate-node-new substrate-example <name>

To extend the default implementation of the substrate-node-template, you will need to modify substrate-example/runtime/src/lib.rs.

  • Add these two lines after the initial declarations:
mod runtime_example;
impl runtime_example::Trait for Runtime {}
  • Then modify the construct_runtime!() macro to include RuntimeExample at the end:
construct_runtime!(
    pub enum Runtime with Log(InternalLog: DigestItem<Hash, AuthorityId>) where
        Block = Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        System: system::{default, Log(ChangesTrieRoot)},
        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
        ...
        Balances: balances,
        UpgradeKey: upgrade_key,
        RuntimeExample: runtime_example::{Module, Call, Storage},
    }
);

NOTE

We may continue to update this file depending on the needs of our runtime example. However, you should only need to modify these sections, and the examples below should make clear what needs to change.

Finally, you need to create a new file called runtime_example.rs in the same folder as lib.rs.

Updating Your Runtime

You can paste any of the runtime samples below into that runtime_examples.rs file and compile the new runtime binaries with:

cd substrate-example
cargo build --release

You should delete the old chain before you start the new one:

substrate purge-chain --dev
./target/release/substrate-example --dev

Using the Polkadot UI to Interact

To simplify interactions with your custom Substrate runtime, we will take advantage of the Polkadot JS UI for Substrate.

By default, this UI is configured to interact with the public Substrate test-network BBQ Birch. To have it connect to your local node, simply go to:

Settings > remote node/endpoint to connect to > Local Node (127.0.0.1:9944)

A picture of the Polkadot UI Settings Tab

If the UI connected successfully, you should be able to go to the Explorer tab and see the block production process running.

A picture of the block production process running in Explorer tab

You can then interact with your custom functions in the Extrinsics tab under runtimeExample:

A picture of custom functions appearing in the Extrinsics tab

Viewing Storage Variables

If you want to check the value of a storage variable that you created, you can go to:

Chain State > runtimeExampleStorage > (variable name)

From there you should be able to query the state of the variable. It may return <unknown> if the value has not been set yet.

A picture of viewing a storage variable in the Polkadot UI

Viewing Events

Some runtime examples below generate Events when functions are run. You can temporarily view these events in the Explorer tab under recent events if any get generated.

A picture of an event getting generated in the Explorer tab

WASM Runtime Upgrade

Rather than restarting your chain for each update, you can also do an in-place runtime upgrade using the Polkadot UI. If you do this, you will not get runtime messages appearing in your terminal, but you should be able to interact with the chain via the UI just fine. To perform the upgrade, go to:

Extrinsics > Upgrade Key > upgrade(new)

There, you can select the file icon and upload the wasm file generated when you run ./build.sh

substrate-example/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

A picture of upgrading the Substrate runtime

Once the upgrade is finalized, you should be able to refresh the UI and see your updates.

Recipes

A good general reference for Substrate runtime development can be found in the srml/example source and the other SRML modules. The examples below are meant to be minimal working samples of specific features.

Simple Storage

Create a simple single value storage.

use srml_support::{StorageValue, dispatch::Result};

pub trait Trait: system::Trait {}

decl_module! {
  pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    fn set_value(_origin, value: u32) -> Result {
      <Value<T>>::put(value);
      Ok(())
    }
  }
}

decl_storage! {
  trait Store for Module<T: Trait> as RuntimeExampleStorage {
    Value: u32;
  }
}

Account Mapping

Create an account to value mapping in storage.

use srml_support::{StorageMap, dispatch::Result};
use system::ensure_signed;

pub trait Trait: system::Trait {}

decl_module! {
	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
		fn set_account_value(origin, value: u32) -> Result {
			let sender = ensure_signed(origin)?;
			<Value<T>>::insert(sender.clone(), value);
			Ok(())
		}
	}
}

decl_storage! {
	trait Store for Module<T: Trait> as RuntimeExampleStorage {
		Value: map T::AccountId => u32;
	}
}

Storage Mapping

Create a key/value mapping in storage.

use srml_support::{StorageMap, dispatch::Result};

pub trait Trait: system::Trait {}

decl_module! {
	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
		fn set_mapping(_origin, key: u32, value: u32) -> Result {
			<Value<T>>::insert(key, value);
			Ok(())
		}
	}
}

decl_storage! {
	trait Store for Module<T: Trait> as RuntimeExampleStorage {
		Value: map u32 => u32;
	}
}

String Storage (as Bytemap)

How to store a string in the runtime using JavaScript to convert the string to hex and back.

use srml_support::{StorageValue, dispatch::Result};

pub trait Trait: system::Trait {}

decl_module! {
  pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    fn set_value(_origin, value: Vec<u8>) -> Result {
      <Value<T>>::put(value);
      Ok(())
    }
  }
}

decl_storage! {
  trait Store for Module<T: Trait> as RuntimeExampleStorage {
    Value: Vec<u8>;
  }
}

JavaScript Helper for String Storage

We store the string as a bytearray, which is inputted into the Polkadot UI as a hex string. These helper functions can allow you to convert a string to hex and back right in your browser console.

function toHex(s) {
    var s = unescape(encodeURIComponent(s))
    var h = '0x'
    for (var i = 0; i < s.length; i++) {
        h += s.charCodeAt(i).toString(16)
    }
    return h
}

function fromHex(h) {
    var s = ''
    for (var i = 0; i < h.length; i+=2) {
        s += String.fromCharCode(parseInt(h.substr(i, 2), 16))
    }
    return decodeURIComponent(escape(s))
}

Adder with Simple Event

A simple adding machine which checks for overflow and emits an event with the result, without using storage.

You will need to modify lib.rs for ths example. Add type Event = Event; to the trait implementation, remove Storage, and add Event to construct_runtime!() like so:

impl runtime_example::Trait for Runtime {
	type Event = Event;
}

...
RuntimeExample: runtime_example::{Module, Call, Event},
...
use srml_support::dispatch::Result;

pub trait Trait: system::Trait {
    type Event: From<Event> + Into<<Self as system::Trait>::Event>;
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn deposit_event() = default;

        fn add(_origin, val1: u32, val2: u32) -> Result {
            let result = match val1.checked_add(val2) {
                Some(r) => r,
                None => return Err("Addition overflowed"),
            };
            Self::deposit_event(Event::Added(val1, val2, result));
            Ok(())
        }
    }
}

decl_event!(
    pub enum Event {
        Added(u32, u32, u32),
    }
);

Permissioned Function with Generic Event (OnlyOwner, Ownable)

A basic implementation of a permissioned function which can only be called by the "owner". An event is emitted when the function is successfully run.

You will need to modify lib.rs for this example. Add type Event = Event; to the trait implementation, and add Event<T> to the construct_runtime() macro:

impl runtime_example::Trait for Runtime {
	type Event = Event;
}

...
RuntimeExample: runtime_example::{Module, Call, Storage, Event<T>},
...
use srml_support::{StorageValue, dispatch::Result};
use system::ensure_signed;

pub trait Trait: system::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn deposit_event<T>() = default;

        fn init_ownership(origin) -> Result {
            ensure!(!<Owner<T>>::exists(), "Owner already exists");
            let sender = ensure_signed(origin)?;
            <Owner<T>>::put(&sender);
            Self::deposit_event(RawEvent::OwnershipTransferred(sender.clone(), sender));
            Ok(())
        }

        fn transfer_ownership(origin, newOwner: T::AccountId) -> Result {
            let sender = ensure_signed(origin)?;
            ensure!(sender == Self::owner(), "This function can only be called by the owner");
            <Owner<T>>::put(&newOwner);
            Self::deposit_event(RawEvent::OwnershipTransferred(sender, newOwner));
            Ok(())
        }
    }
}

decl_storage! {
	trait Store for Module<T: Trait> as RuntimeExampleStorage {
		Owner get(owner): T::AccountId;
    }
}

decl_event!(
	pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
		OwnershipTransferred(AccountId, AccountId),
	}
);

Hashing data

Substrate provides in-built support for hashing data with BlakeTwo256 algorithm. This is available as part of the system trait. The Hashing type under the system trait exposes a function called hash. This function takes a reference of a byte array (Vec<u8>) and produces a BlakeTwo256 hash digest of it.

In the following code snippet, our custom module has a function get_hash which takes a Vec<u8> parameter data and calls the hash function on it.

use runtime_primitives::traits::Hash;
use srml_support::{dispatch::Result};
use {system::{self}};

pub trait Trait: system::Trait {}

decl_module! {
  pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    pub fn get_hash(_origin, data: Vec<u8>) -> Result {
      let _digest = <<T as system::Trait>::Hashing as Hash>::hash(&data);
      Ok(())
    }
  }
}

Storing Structs with Generics

A basic runtime which stores custom, nested structs using a combination of Rust primitive types and Substrate specific types via generics. We also show you how to import and use this custom type in both the Polkadot-UI and Substrate-UI.

use srml_support::{StorageMap, dispatch::Result};

pub trait Trait: balances::Trait {}

#[derive(Encode, Decode, Default)]
pub struct Thing <Hash, Balance> {
    my_num: u32,
    my_hash: Hash,
    my_balance: Balance,
}

#[derive(Encode, Decode, Default)]
pub struct SuperThing <Hash, Balance> {
    my_super_num: u32,
    my_thing: Thing<Hash, Balance>,
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn set_mapping(_origin, key: u32, num: u32, hash: T::Hash, balance: T::Balance) -> Result {
            let thing = Thing { 
                            my_num: num, 
                            my_hash: hash, 
                            my_balance: balance
                        };
            <Value<T>>::insert(key, thing);
            Ok(())
        }

        fn set_super_mapping(_origin, key: u32, super_num: u32, thing_key: u32) -> Result {
            let thing = Self::value(thing_key);
            let super_thing = SuperThing { 
                            my_super_num: super_num, 
                            my_thing: thing
                        };
            <SuperValue<T>>::insert(key, super_thing);
            Ok(())
        }
    }
}

decl_storage! {
    trait Store for Module<T: Trait> as RuntimeExampleStorage {
        Value get(value): map u32 => Thing<T::Hash, T::Balance>;
        SuperValue get(super_value): map u32 => SuperThing<T::Hash, T::Balance>;
    }
}

To access the value of this struct via the UI, you will need to import the structure of your new type so that the UI understand how to decode it.

Polkadot UI

For the Polkadot-UI, you will need to create a JSON file which describes your struct:

NOTE

The order of the struct definitions matter here since the UI registers one by one in order.

{
    "Thing": {
        "my_num": "u32",
        "my_hash": "Hash",
        "my_balance": "Balance"
    },
    "SuperThing": {
        "my_super_num": "u32",
        "my_thing": "Thing"
    }
}

Then go into the Polkadot UI, and navigate to:

Settings > developer > additional type definitions (JSON)

Import your JSON file there and press "Save and Reload". If all went well, you should be able to interact with this value just like any other primitive type.

Substrate UI

For the Substrate UI, you need to take advantage of a registration function provided by the oo7-substrate module: addCodecTransform().

In the console, simply type:

addCodecTransform('Thing<Hash,Balance>', { my_num: 'u32', my_hash: 'Hash', my_balance: 'Balance' });
addCodecTransform('SuperThing<Hash,Balance>', { my_super_num: 'u32', my_thing: 'Thing' });

You can add these lines in your app.jsx constructor() function to automatically import these types when the page loads.

Print a message

Sometimes we need to print debug messages while building applications. This recipe shows how to print messages for debugging your Substrate runtime code.

You need to include runtime_io crate from the Substrate core in order to use IO functions from within the runtime modules.

use srml_support::{dispatch::Result};
use runtime_io;

pub trait Trait: system::Trait {}

decl_module! {
  pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    pub fn print_something(_origin) -> Result {
      // the following line prints a message on the node's terminal/console
      runtime_io::print("Hello World from Substrate Runtime!");
      Ok(())
    }
  }
}

When this function is executed, the message will appear on the terminal where the Substrate node is running. The following screenshot shows the printed message.