How to use modifiers for ink! Smart-Contracts using OpenBrush?

Pierre Ossun
WASM conference
Published in
3 min readDec 1, 2021

Why use modifiers?

Smart contracts expose callable functions to interact with them. These functions are callable only under certain conditions that should be met before entering the call. It basically checks a condition prior to entering a function.

By defining modifiers you will reduce code redundancy and increase its readability as you will not have to add guards for each of your functions.

In ink! eDSL there is no such feature, so the only way to deal with the condition prior execution of a message function is to add guards. OpenBrush library (that is inspired by Openzepellin) developed a set of modifiers, Rust attribute macros, to use standardized modifiers.

OpenBrush library for ink! Smart contracts

OpenBrush is a library for smart contract development on ink!. It has all standardized (Polkadot Standard Proposal) contracts with their extensions as well as a collection of modifiers.

List of modifiers

  • Ownable Restrict access to action for non-owners
  • Roles Define set of roles and restrict access to an action by roles
  • Reentrancy guard Prevent reentrant calls to a function
  • Pausable Pause/Unpause the contract to disable/enable some operations
  • Timelock Controller Execute transactions with some delay
  • Payment Splitter Split amount of native tokens between participants

How it works?

Ownable

Please refer to the latest OpenBrush documentation for an up-to-date tutorial

This example shows how you can use the implementation of ownable to provide only owner rights for contract functions.

Step 1: Include dependencies

Include dependencies to ownable and brush in the cargo file.

Step 2: Add imports

Replace ink::contract macro by brush::contract. Import everything from ownable::traits.

Step 3: Define storage

Declare storage struct and declare the field related to OwnableStorage trait. Then you need to derive the OwnableStorage trait and mark the corresponding field with the #[OwnableStorageField] attribute. Deriving this trait allows you to reuse the default implementation of Ownable.

Step 4: Inherit logic

Inherit implementation of the Ownable trait. You can customize (override) methods in this impl block.

Step 5: Define constructor

Define the constructor and initialize the owner with the contract initiator. Your basic version of Ownable contract is ready!

Step 6: Customize your contract

Customize it by adding ownable logic. We will add a owner_function to MyOwnable implemenation and add the only_owner modifier, which will verify that the caller of the function is the owner.

Build & use your custom modifier

First please refer to step 1 and 2 above, and then add to your custom modifier.

Rules to define your modifier:

  • The first argument should not be self, and must be a reference to a type instance: &T. In most cases, it’s the instance of the contract.
  • Second argument is function’s body(this function contains the main code of method attached to the modifier). The type must be Fn(&T), FnMut(&T) or FnOnce(&T).
  • Every next argument should not be a reference to an object. Because modifier only allows to pass arguments by value(Modifier will pass the clone of argument).
  • The return type of body function(second argument) must be the same as the return type of modifier.

--

--