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 methodflip
will be marked withnon_reentrant
modifier, and we will add additional method, also marked withnon_reentrant
, which will ask another contract to callflip
of ourflipper
.flip_on_me
is a contract which has the only one methodflip_on_me
. This method will try to callflip
on the caller (it means that caller must be a contract with methodflip
).
#
MyFlipper#
Step 1: Include dependenciesInclude 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 importsTo 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 storageDeclare 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 modifiersAfter 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 contractTo 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!() } }}
#
FlipOnMeIt's a simple contract that doesn't use any logic from the OpenBrush, so you can use simple ink! here.
FlipOnMe
contract#
Step 1: Define 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 dependenciesTo 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 berlib
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.
#
TestingFor 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.