函数修改器可以一定程度上改变函数的行为。可以作为函数执行的先行条件,如果符合函数修改器定义的条件,才可以执行函数体内容。关于函数修改器,可以把理解成if的变相。
函数修改器的定义语法如下:
modifier 修改器名 {
条件体..
_;
}
function a() 修改器名 {
函数体..
}
当要执行a()时,会先去执行修改器,判断条件体,如果符合条件,才会继续执行a();如果不符合条件,a()将不执行。”_;”在这里表示的是a().
通过一个小栗子,直观的感受下:
pragma solidity ^0.4.0;
/**
* 权限控制
*/
contract Ownable {
address public owner = msg.sender;
// @notice 检查必须是合约的所有者
modifier onlyOwner {
if (msg.sender != owner) throw;
_;
}
// @notice 改变合约的拥有者身份
// @param _newOwner 新所有者的地址
function changeOwner(address _newOwner) onlyOwner {
if(_newOwner == 0x0) throw;
owner = _newOwner;
}
}
在上述的例子中,我们实现了只有合约所有者才能修改合约归属的权限的功能。
函数修改器可以接收上下文中存在的任意变量组成的表达式,直接在函数修改器中传入参数。
modifier 修改器名(uint 参数1, string 参数2) {
if(参数1>10 && 参数2 != "男")
//条件体..
_;
}
当函数的修改器条件判断不成功,如果函数没有返回值,对应的函数将不执行;如果函数有返回值,那将返回对应类型的默认值。
而函数修改器中的条件体,不论函数是否符合条件,都会继续执行完毕修改器中的后续逻辑。
参考如下实例:
pragma solidity ^0.4.0;
contract Test{
mapping(bool => uint) public mapp;
modifier A(mapping(bool => uint) mapp) {
if(mapp[true] == 0) {
mapp[true]= 1;
_;
mapp[true]= 3;//这句将会最后执行,可以在调试器中查看mapp的值为3。
}
}
function f1() A(mapp) returns(uint) {
mapp[true] = 2;
return mapp[true];//函数结束时,mapp值为2.
}
modifier B(mapping(bool => uint) mapp) {
if(mapp[true] == 1) { //条件体判断不成功,导致函数f2讲不会执行。
mapp[true]= 1;
_;
mapp[true]= 3;
}
mapp[true]=7;//秉着一站到底的原则,这句将会被最后执行,mapp值为7。
}
function f2() B(mapp) returns(uint) {
mapp[true] = 2;
return mapp[true];//有返回值的函数,将返回对应类型的默认值,即为0.
}
}
当一个函数拥有多个函数修改器时,执行顺序是按照先后顺序依次执行。如果有一个不满足,函数即不能执行。
pragma solidity ^0.4.0;
contract Test{
modifier A(uint a) {
if(a<10) throw;
_;
}
modifier B(uint b) {
if(b<10) throw;
_;
}
//必须同时满足A、B,才能执行f()
function f(uint a, uint b) A(a) B(b) returns(uint) {
return 777;
}
}
子类可以使用父类中的函数修改器,也可以重写父类的函数修改器。
pragma solidity ^0.4.0;
contract Father{
modifier A(uint a) {
if(a > 100) throw;
_;
}
}
contract Son is Father{
//重写父类中的函数修改器
modifier A(uint a) {
if(a > 50) throw;
_;
}
function f(uint a) A(a) returns(uint) {
return 777;
}
}
结合现实场景中的应用。在一些敏感操作中,我们需要设定特定的权限才允许执行相关操作;再者我们可以利用函数修改器进行数据的校验;还可以利用简单的函数修改器来进行重入锁的机制。
pragma solidity ^0.4.0;
contract Ownable {
address public owner = msg.sender;
/// @notice 检查必须是合约的所有者
modifier onlyOwner {
if (msg.sender != owner) throw;
_;
}
/// @notice 改变合约的拥有者身份
/// @param _newOwner 新所有者的地址
function changeOwner(address _newOwner)
onlyOwner
{
if(_newOwner == 0x0) throw;
owner = _newOwner;
}
}
contract DataVerifiable {
/// @notice throws if ether was sent accidentally
modifier refundEtherSentByAccident() {
if(msg.value > 0) throw;
_;
}
/// @notice throw if an address is invalid
/// @param _target the address to check
modifier throwIfAddressIsInvalid(address _target) {
if(_target == 0x0) throw;
_;
}
/// @notice throw if the id is invalid
/// @param _id the ID to validate
modifier throwIfIsEmptyString(string _id) {
if(bytes(_id).length == 0) throw;
_;
}
/// @notice throw if the uint is equal to zero
/// @param _id the ID to validate
modifier throwIfEqualToZero(uint _id) {
if(_id == 0) throw;
_;
}
/// @notice throw if the id is invalid
/// @param _id the ID to validate
modifier throwIfIsEmptyBytes32(bytes32 _id) {
if(_id == "") throw;
_;
}
}
pragma solidity ^0.4.0;
contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
// 这个函数使用了noReentrancy修改器,这保证了在f函数内部无法再次使用调用f函数
// 在执行return 7时也执行了函数修改器中的locked = false
function f() noReentrancy returns (uint) {
require(msg.sender.call());
return 7;
}
}