Substrate Runtime Recipes ========== **This page is now officially hosted on the official Substrate documentation at [Readme.io](https://substrate.readme.io/v1.0.0/docs/substrate-runtime-recipes)** **This page on HackMD may go out of date, and should not be used for direct reference** 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](https://substrate.readme.io/docs/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 You can now create an instance of the `substrate-node-template` using the following command: ``` substrate-node-new substrate-example <author-name> ``` You should be able to immediately start your chain by running: ``` cd substrate-example ./target/release/substrate-example --dev ``` 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 that 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](https://polkadot.js.org/apps/next/). 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](https://i.imgur.com/1FpB5aM.png) 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](https://i.imgur.com/TXmM0cB.png) You can then interact with your custom functions in the **Extrinsics** tab under **runtimeExample**: ![A picture of custom functions appearing in the Extrinsics tab](https://i.imgur.com/JFXSaHw.png) #### 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](https://i.imgur.com/JLoWxc3.png) #### 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](https://i.imgur.com/2jUtBUk.png) #### 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](https://i.imgur.com/rujS3p6.png) 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](https://github.com/paritytech/substrate/blob/master/srml/example/src/lib.rs) and the [other SRML modules](https://github.com/paritytech/substrate/blob/master/srml/). The examples below are meant to be minimal working samples of specific features. ### Simple Storage Create a simple single value storage. #### runtime_example.rs ``` 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. #### runtime_example.rs ``` 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. #### runtime_example.rs ``` 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. #### runtime_example.rs ``` use srml_support::{StorageValue, dispatch::Result}; use rstd::vec::Vec; 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 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. #### lib.<span></span>rs Add `type Event = Event;` to the trait implementation like so: ``` impl runtime_example::Trait for Runtime { type Event = Event; } ``` Remove `Storage` and add `Event` to `construct_runtime!()` as shown here: ``` ... RuntimeExample: runtime_example::{Module, Call, Event}, ... ``` #### runtime_example.rs ``` 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(event: Event) { <system::Module<T>>::deposit_event( <T as Trait>::Event::from(event).into() ); } 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. #### lib.<span></span>rs Add `type Event = Event;` to the trait implementation like so: ``` impl runtime_example::Trait for Runtime { type Event = Event; } ``` Add `Event<T>` to the `construct_runtime()` macro: ``` ... RuntimeExample: runtime_example::{Module, Call, Storage, Event<T>}, ... ``` #### runtime_example.rs ``` 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() = 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), } ); ```