Modifiers
Modifiers ensure certain conditions are fulfilled prior to entering a function. By defining modifiers, you will reduce code redundancy (keep it DRY), and increase its readability as you will not have to add guards for each of your functions.
The Pair contract defines and uses a lock modifier that prevents reentrancy attacks. During initialization, it also ensures that the caller is the Factory, so it can be used as modifier.
1. Reentrancy Guard
To protect callable functions from reentrancy attacks, we will use the reentrancy guard modifier from Openbrush, which saves the lock status in storage (either ENTERED
or NOT_ENTERED
) to prevent reentrancy.
In the ./contracts/pair/Cargo.toml file, add the "reentrancy_guard"
feature to the Openbrush dependencies:
openbrush = { git = "https://github.com/727-Ventures/openbrush-contracts", version = "3.0.0", default-features = false, features = ["psp22", "reentrancy_guard"] }
In the ./contracts/pair/lib.rs file, add an import statement, and reentrancy_guard as a Storage field:
...
use openbrush::{
contracts::{
ownable::*,
psp22::*,
reentrancy_guard,
},
traits::Storage,
};
...
#[ink(storage)]
#[derive(Default, Storage)]
pub struct PairContract {
#[storage_field]
psp22: psp22::Data,
#[storage_field]
guard: reentrancy_guard::Data,
#[storage_field]
pair: data::Data,
}
...
In the ./logics/Cargo.toml file, add the "reentrancy_guard"
feature as an Openbrush dependency:
openbrush = { git = "https://github.com/727-Ventures/openbrush-contracts", version = "3.0.0", default-features = false, features = ["psp22", "reentrancy_guard"] }
Modifiers should be added in the impl block on top of the function, as an attribute macro.
In the ./logics/impls/pair/pair.rs file, add "reentrancy_guard"
, import statements, and modifier on top of mint, burn and swap as well as the Storage<reentrancy_guard::Data>
trait bound:
...
use openbrush::{
contracts::{
psp22::*,
reentrancy_guard::*,
traits::psp22::PSP22Ref,
},
modifiers,
traits::{
AccountId,
Balance,
Storage,
Timestamp,
ZERO_ADDRESS,
},
};
...
impl<T: Storage<data::Data> + Storage<psp22::Data> + Storage<reentrancy_guard::Data>> Pair for T {
...
#[modifiers(non_reentrant)]
fn mint(&mut self, to: AccountId) -> Result<Balance, PairError> {
...
#[modifiers(non_reentrant)]
fn burn(&mut self, to: AccountId) -> Result<(Balance, Balance), PairError> {
...
#[modifiers(non_reentrant)]
fn swap(
...
Finally, the non_reentrant
modifier returns ReentrancyGuardError
. So let's impl From
ReentrancyGuardError
for PairError
:
use openbrush::{
contracts::{
reentrancy_guard::*,
traits::{
psp22::PSP22Error,
},
},
traits::{
AccountId,
Balance,
Timestamp,
},
};
...
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum PairError {
PSP22Error(PSP22Error),
ReentrancyGuardError(ReentrancyGuardError),
...
impl From<ReentrancyGuardError> for PairError {
fn from(error: ReentrancyGuardError) -> Self {
PairError::ReentrancyGuardError(error)
}
}