I am learning solidity. I came across the concept of function modifiers. I am unable to understand it.
4 Answers
Modifiers are compile-time source code roll-ups. Functions can be "modified" by including the named modifier in the function declaration.
The _;
line is mandatory. It's a merge wildcard that indicates where "the rest" of the function should go. For example, as @oktapdia suggested,
function test() public onlyOwner { ...
merges the function source with the modifier
code at the _;
It is possible to place the _;
at the beginning, in the middle or at the end of the modifier
code. In practice, especially until you understand it really well, I would suggest the safest usage pattern is to use modifiers for consistent validation checks and only use the pattern with _;
at the end. In other words, use modifiers to check some things up front and then carry on.
You can use modifiers in conjunction with getter functions. Here is a contrived example to show how you might use them together to keep code organized while you make the state discoverable to clients and ensure potentially complex logic is consistently applied.
modifier onlyIfOkayAndAuthorized {
require(isOkay());
require(isAuthorized(msg.sender));
_;
}
// Let's say we have multiple concerns to mix and match
function isOkay() public view returns(bool isIndeed) {
// return a check result
return true;
}
function isAuthorized(address user) public view returns(bool isIndeed) {
// return a check result
return true;
}
return true
are just placeholders for more meaningful results.
The idea is clients, other contracts, and other internal functions can use the getter functions to fetch/check the individual concerns (if they want to). Functions that should be guarded by a certain combination of concerns can use the modifier so the logic of it all (and your intent) remains very clear to reviewers.
So, you can have several functions like above, and they may have some tricky steps. Hopefully, they can be understood as individual concerns. Then, when used in combination, you can reduce repetition and improve readability with a modifier.
function sensitive() public onlyIfOkayAndAuthorized returns(bool success) {...
In case it isn't clear, you could accomplish something quite similar if instead of a modifier you made a third function to work with the combination and then included a require()
in each function that needs it.
A function like:
function isOkayAndIsAuthorized(address user) public view returns(bool isIndeed) {
require(isOkay());
require(isAuthorized(user));
returnt true;
}
Then use it like:
function sensitive() public returns(bool success) {
require(isOkayAndIsAuthorized(msg.sender));
// carry on
}
The security/validation concern is part of the function, and although this will work, it is arguably less readable than a modifier and the intent may be less clear.
Hope it helps.
Modifier are like a prerequisites check of the function
Look at the following example
contract Test {
address testAddress;
constructor() {
testAddress = msg.sender;
}
// Can only be called by the owner
function test() public onlyOwner {
}
// Check if the function is called by the owner of the contract
modifier onlyOwner() {
require(msg.sender == testAddress);
_;
}
}
Modifier are good to avoid duplication in your smart contract code but are not mandatory, the following example is doing the same as the previous one
contract Test {
address testAddress;
constructor() {
testAddress = msg.sender;
}
// Can only be called by the owner
function test() public {
require(msg.sender == testAddress);
}
}
I would like to add how the placement of _ matters in the code. The following code is taken from solidity docs. Note how _ is placed between both return statements. it implies when the code is merged the function body would be placed/merged at where _ is. hence Before the function body lock is acquired. After the body is complete the last line is still run setting the lock to false.
contract Mutex {
bool locked;
modifier noReentrancy() {
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within `msg.sender.call` cannot call `f` again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() public noReentrancy returns (uint) {
(bool success,) = msg.sender.call("");
require(success);
return 7;
}
}
modifiers
are add-ons to functions that contain conditional logic.
For example, I have a function that turns off a light switch and a modifier that states the light switch must be “on”. I add the modifier to function so that it can only be called if the switch is “on” and will throw an error if the switch is “off”.