In Solidity, Function Modifiers can be used to inject business logics into other functions in a declarative way.
You can think of it like one of the Decorator Design Pattern in Object-oriented programming, or Higher-order Pattern in Functional programming.
Here is a format of Function Modifier. It starts with modifier
keyword. Modifiers should include _
(underscore) as an insertion point of the original function.
modifier test() {
// add business logic you'd like to modify, e.g.:
// require(isOwner(msg.sender))/
_; // the original function will be called here
}
In this example, let's see how Function Modifier works in a simple contract source code.
Here is a VoteSystem
contract, which a new user can be added every time and vote. User
struct has a bool
flag, voted
, which does not allow user to vote multiple times.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
contract VoteSystem {
struct User {
string name;
uint256 age;
bool voted;
}
uint public voteCount = 0;
User private _user;
function newUser(string memory _name, uint _age) external {
_user = User(_name, _age, false);
}
}
In this example, you'd like to add an age restriction. It means that users under specific ages cannot vote unfortunately :(
Okay, here is our modifier. The cool thing about Function Modifiers is that it can accept arguments. It gives you flexibility to use the same modifiers with different conditions.
modifier ageRestrict(uint256 _age) {
require(
_user.age >= _age,
string.concat(_user.name, ": This user cannot vote")
);
_;
}
Let's add a vote()
function. When this function is called, the user in front of the vote system can vote. However, if the user is under 18
years old, the program will halt.
function vote() external ageRestrict(18) {
require(
_user.voted == false,
string.concat(_user.name, ": This user already voted")
);
_user.voted = true;
voteCount++;
}
Let's deploy this smart contract and see if that works as expected. In this example, I deployed this contract to my local Ganache network and testing with truffle console
.
Please follow this blog post to deploy your smart contracts to your local network
Let's add a first user, Alice, who is 20 years old. She can vote because she is over 18 years old.
truffle(development)> let vs = await VoteSystem.deployed()
truffle(development)> vs.newUser("alice", 20)
truffle(development)> vs.vote()
However, if she tries to vote more than twice, the vote system gives here an error message as follows:
truffle(development)> vs.vote()
Uncaught:
Error: Returned error: VM Exception while processing transaction: revert alice: This user already voted
(...8<...)
Now, next is Bob's turn, who is 15 years old. What will happen when he tries to vote?
truffle(development)> vs.newUser("bob", 15)
truffle(development)> vs.vote()
Uncaught:
Error: Returned error: VM Exception while processing transaction: revert bob: This user cannot vote
(...8<...)
Unfortunately he was rejected by the vote system due to his age :(
Function Modifiers are useful to change original functions behaviour, for example adding new validation.