Skip to main content

Reentrancy Guard

This example shows how you can use the non_reentrant modifier to prevent reentrancy into certain functions. In this example we will create two contracts:

  • my_flipper_guard - this contract is the simple version of flipper, but method flip will be marked with non_reentrant modifier, and we will add additional method, also marked with non_reentrant, which will ask another contract to call flip of our flipper.
  • flip_on_me is a contract which has the only one method flip_on_me. This method will try to call flip on the caller (it means that caller must be a contract with method flip).

MyFlipper#

Step 1: Include dependencies#

Include brush as dependency in the cargo file or you can use default Cargo.toml template. After you need to enable default implementation of Reentrancy Guard via brush features.

brush = { tag = "v1.6.1", git = "https://github.com/Supercolony-net/openbrush-contracts", default-features = false, features = ["reentrancy_guard"] }

Step 2: Add imports#

To declare the contract, you need to use brush::contract macro instead of ink::contract. Import everything from brush::contracts::reentrancy_guard.

#![cfg_attr(not(feature = "std"), no_std)]
#[brush::contract]pub mod my_flipper_guard {  use brush::{    contracts::reentrancy_guard::*,    modifiers,  };
  use crate::flip_on_me::CallerOfFlip;  use ink_env::call::FromAccountId;  use ink_storage::traits::SpreadAllocate;

Step 3: Define storage#

Declare storage struct and declare the field for ReentrancyGuardStorage trait. Then you need to derive ReentrancyGuardStorage trait and mark the field with #[ReentrancyGuardStorageField] attribute. Deriving this trait allows you to use non_reentrant modifier.

#[ink(storage)]#[derive(Default, SpreadAllocate, ReentrancyGuardStorage)]pub struct MyFlipper {    #[ReentrancyGuardStorageField]    guard: ReentrancyGuardData,    value: bool,}

Step 4: Add modifiers#

After that you can add non_reentrant modifier to flip and call_flip_on_me methods.

impl MyFlipper {    #[ink(constructor)]    pub fn new() -> Self {        ink_lang::codegen::initialize_contract(|_instance: &mut Self| {})    }
    #[ink(message)]    pub fn get_value(&self) -> bool {        self.value    }
    #[ink(message)]    #[brush::modifiers(non_reentrant)]    pub fn flip(&mut self) {        self.value = !self.value;    }
    #[ink(message)]    #[modifiers(non_reentrant)]    pub fn call_flip_on_me(&mut self, callee: AccountId) {        // This method will do a cross-contract call to callee account. It calls method `flip_on_me`.        // Callee contract during execution of `flip_on_me` will call `flip` of this contract.        // `call_flip_on_me` and `flip` are marked with `non_reentrant` modifier. It means,        // that call of `flip` after `call_flip_on_me` must fail.        let mut flipper: CallerOfFlip = FromAccountId::from_account_id(callee);        flipper.flip_on_me();    }}

Step 5: Add stub contract#

To simplify cross contract call to FlipOnMe contract let's create a wrapper around the contract's account id. For that, we will define another contract in this crate with #[ink_lang::contract(compile_as_dependency = true)] and empty methods but with the same signature as in the original contract.

/// This is a stub implementation of contract with method `flip_on_me`./// We need this implementation to create a wrapper around account id of contract./// With this wrapper, we can easily call methods of some contract./// Example:/// ```/// let mut flipper: CallerOfFlip = FromAccountId::from_account_id(callee);/// flipper.flip_on_me();/// ```#[ink_lang::contract(compile_as_dependency = true)]pub mod flip_on_me {    #[ink(storage)]    pub struct CallerOfFlip {}
    impl CallerOfFlip {        #[ink(constructor)]        pub fn new() -> Self {            unimplemented!()        }    }
    impl CallerOfFlip {        #[ink(message)]        pub fn flip_on_me(&mut self) {            unimplemented!()        }    }}

FlipOnMe#

It's a simple contract that doesn't use any logic from the OpenBrush, so you can use simple ink! here.

Step 1: Define FlipOnMe contract#

It has the only method flip_on_me, which will call flip on caller.

#[ink_lang::contract]pub mod flip_on_me {    use ink_env::call::FromAccountId;    use my_flipper_guard::my_flipper_guard::MyFlipper;
    #[ink(storage)]    #[derive(Default)]    pub struct FlipOnMe {}
    impl FlipOnMe {        #[ink(constructor)]        pub fn new() -> Self {            ink_lang::codegen::initialize_contract(|_instance: &mut Self| {})        }
        #[ink(message)]        pub fn flip_on_me(&mut self) {            let caller = self.env().caller();            // This method does a cross-contract call to caller contract and calls the `flip` method.            let mut flipper: MyFlipper = FromAccountId::from_account_id(caller);            flipper.flip();        }    }}

Step 2: Include dependencies#

To do a cross-contract call to MyFlipper you need to import the MyFlipper contract with ink-as-dependency feature.

Note: The crate type of the MyFlipper should be rlib for that.

[dependencies]ink_primitives = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false }ink_metadata = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false, features = ["derive"], optional = true }ink_env = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false }ink_storage = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false }ink_lang = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false }ink_prelude = { tag = "v3.0.1", git = "https://github.com/paritytech/ink", default-features = false }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }
# This dependenciesmy_flipper_guard = { path = "../flipper", default - features = false, features = ["ink-as-dependency"] }
[features]default = ["std"]std = [    "ink_primitives/std",    "ink_metadata",    "ink_metadata/std",    "ink_env/std",    "ink_storage/std",    "ink_lang/std",    "scale/std",    "scale-info",    "scale-info/std",        # This dependencies    "my_flipper_guard/std",]

You can check an example of the usage of ReentrancyGuard.

Testing#

For testing, you can run the integration test, or you can deploy both contracts and call call_flip_on_me on MyFlipper account providing account id of FlipOnMe contract as an argument.