惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
B
Blog
Jina AI
Jina AI
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
美团技术团队
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
The Cloudflare Blog
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
GbyAI
GbyAI
腾讯CDC
MongoDB | Blog
MongoDB | Blog

秋码分享

我是如何解决将 c++ 编译成可以在 node.js 中使用的 *.node,中间出现的一大堆问题的(指纹浏览器基石篇) eSIM Plus 爱沙尼亚手机号彻底翻车?“永久有效”悄然变成了一年! 接码平台 SMS-Activate 余额可以转移到新平台使用,截止日期:2026年1月29日 是时候将 hugo-theme-kiwi 主题提交到 themes.gohugo.io 站点上了 Flux2 刚开源就凉了?Z-Image 本地部署狠狠打了个样 声音的未来:Chatterbox —— 用「夸张度旋钮」提升表现力的开源 TTS 向导 还以为那只是换个背景?Qwen-Image-Edit 在 ComfyUI 中能做到更离谱的事 Windows 结合最新版 ComfyUI 部署阿里最新开源的 Qwen-Image 图像大模型 从零样本到跨场景:Seed-VC语音转换技术的革命性突破 大语音模型轻量化革命:MegaTTS3 如何重新定义文本生成语音的技术边界(windows篇) 竞赛级编程大模型OlympicCoder-7B之本地部署(Windows篇) 阿里开源了端到端全模态大模型Qwen-2.5-Omini-7B之本地部署(windows篇) 语音识别之whisper本地部署(实时语音之开篇) 甭管是个人还是企业都能部署的Mistral-Small3.1,远超同级别的模型 文生音乐开源项目DiffRhythm,8G显存本地部署之Windows篇 阿里QwQ-32B本地部署指南:用Ollama轻松运行320亿参数大模型 基于Qwen2.5大模型的Spark-TTS,零样本语音克隆,CPU可运行之本地部署(Windows篇) 智谱开源了文生图CogView4-6B模型,支持中文提示词之本地部署(Windows篇) 基于歌词生成整首歌的开源AI音乐模型,支持中、英、日、韩等多种语言,本地化部署YuE(windows篇) 阿里云开源的文生视频万相 Wan2.1之本地部署Wan2.1-T2V-1.3B模型 互动式开源AI图像编辑神器,Windows11本地部署 MagicQuill 本地部署Qwen2.5-VL-7B-Instruct多模态视觉大模型(Windows篇) 保持角色一致性的绘本生成AI开源项目之Story-Adapter本地部署Windows篇 本地部署 Stable Diffusion 3.5(最新 ComfyUI记录篇) 谁说Win7安装不了Node.js最新版的呢?都2025年,还不更新系统到Win11 vs code远程调试Linux服务器上的php代码 浏览器定制 | Windows11 编译 Chromium 133.0.6885.0(截稿前Chromium最新版之编译篇[一]) 不说是彻底搞懂,至少让你不再惧怕c/c++指针,以及各种奇葩指针变种 解决windows下php8.x及以上版本,在Apache2.4中无法加载CURL扩展的问题 在 Windows8.1 下编译 Chromium (103.0.5060.68 之三)
玩以太坊链上项目的必备技能(修改器 [modifier]-Solidity之...
一个游离于山间之上的江湖漫行者。A jungle wanderer who wanders above the mountain · 2022-12-23 · via 秋码分享

修改器(modifier)在 Solidity 中是一种特殊类型的函数,用于修改其它函数的行为。例如,开发人员可以使用修改器来检查在允许函数执行之前是否满足某个条件。

修改器(modifier)

在讲修改器(modifier)之前,我们使用前面几篇文章所学到的知识来实现一个简单的代币类合约。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract InheritanceModifierExample {
    //账户 => 金额 映射
    mapping(address => uint) public tokenBalance;
    //拥有者
    address owner;

    uint tokenPrice = 1 ether;

    constructor() {
        owner = msg.sender;
        tokenBalance[owner] = 100;
    }

    function createNewToken() public {
        //使用 require 检查是不是合约拥有着
        require(msg.sender == owner, "You are not allowed");
        tokenBalance[owner]++;
    }

    function burnToken() public {
        require(msg.sender == owner, "You are not allowed");
        tokenBalance[owner]--;
    }

    function purchaseToken() public payable {
        //检查账户中的代币是否足够
        require((tokenBalance[owner] * tokenPrice) / msg.value > 0, "not enough tokens");
        tokenBalance[owner] -= msg.value / tokenPrice;
        tokenBalance[msg.sender] += msg.value / tokenPrice;
    }

    function sendToken(address _to, uint _amount) public {
        require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
        tokenBalance[msg.sender] -= _amount;
        tokenBalance[_to] += _amount;
    }

}

img

我们拷贝部署合约的账户地址0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

切换成其他账户,也拷贝出地址 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2

img

img

查看账户余额是否正确,复制 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,将其填入 tokenBalance的输入栏,看看余额是否为1。

img

我们再来看看该账户burn with Account(账户烧钱)会发生什么样的状况呢?

img

从上述这个简单的代币类合约,聪慧如您,想必是看出它的不足了。

没错,就是上面合约中的函数都有类似的 require 语句,作为开发者的您会将这些 require 语句抽取到一个函数中,但在 Solidity 却引入了一种特别的函数——修改器(modifier)。

Solidity 修改器(modifier)用来做什么的?

修改器(modifier)在 Solidity 中是一种特殊类型的函数,用于修改其它函数的行为。例如,开发人员可以使用修改器来检查在允许函数执行之前是否满足某个条件。

修改器(modifier)与函数类似,因为它们可以接受参数并有一个返回类型。修改器(modifier)也可以被链在一起,这意味着你可以在一个函数上有多个修改器(modifier)

然而,修改器(modifier)只能修改合约逻辑,不能修改合约的存储,包括结构。修改器(modifier)减少了开发者必须编写模板代码的数量,并且可以使您的 Solidity 代码更加可读。

多个修改器(modifier)由空格分隔。修改器(modifier)的顺序很重要。列表中的第一个修改器(modifier)将被首先执行,第二个修改器(modifier)将被第二次应用,以此类推。

例如,如果您有一个修改器(modifier)检查用户是否经过认证,另一个修改器(modifier)检查用户是否被授权查看某个资源,那么这些修改器(modifier)的应用顺序将决定用户是否能够查看该资源。

Solidity 修改器(modifier)的不同类型

Solidity修改器(modifier)有四大类:闸门检查、先决条件、过滤器和防止重入攻击。

  1. 闸门检查

它在允许一个函数执行之前检查某个条件是否为真。

例如,您可能有一个允许用户从他们的账户中取钱的函数,但在函数执行之前,开发者可能想检查用户的账户中是否有足够的钱来进行取款。这种检查被认为是一个门检查修改器

另一个闸门检查的例子是,在允许用户查看某个资源之前,检查用户是否经过认证的函数。

  1. 先决条件

它为一个函数的执行设置了环境,而不是检查某个条件是否为真。

例如,一个 Solidity 开发者可能会使用一个需要一定数量的以太币来执行函数。在这种情况下,先决条件将是设置以太币平衡的函数。

  1. 滤波器

它检查某个条件是否为真,如果是,则允许函数执行。如果条件不为真,那么该函数将不会执行。

闸门检查不同,即使条件为真,也不会自动允许函数执行,而过滤器将允许函数在条件为真时执行。

  1. 重入式攻击的预防

递归攻击是一种攻击类型,恶意行为者试图通过递归调用多次执行一个函数,以利用它。

例如,想象一下,您有一个允许用户从他们的账户中提款的函数。一个重入式攻击者可能会试图多次调用该函数,以提取比他们账户中实际拥有的更多的钱。

为了防止重入式攻击,您可以使用一个修改器来检查该函数是否被递归调用。如果是,那么该函数将不会执行。

require 和 修改器(modifier)之间的关系

require 经常与修改器互换使用,因为它们都允许您在一个函数执行之前检查某个条件是否为真。如果指定的条件不为真,那么编译器就会抛出一个错误。

例如,下面的语句使用 require 关键字,以便只有所有者才能与一个函数进行交互。

modifier onlyOwner() {
    require(msg.sender == owner);
    _;
}

require 和 修改器(modifier)之间的区别。

1、修改器(modiffier) 可用于为一个函数的执行设置环境(如先决条件的情况下) 2、require 只能用于检查某个条件是否为真 3、修改器(modiffier)` 可以被重写 4、要求不能被覆盖

什么是修改器(modiffier) 重写?

virtual关键字可以用来表示可以在派生合约override(重写)

例如,一个带有修改器(modifier)的合约,而派生合约继承自它。

假使,基合约中的修改器(modifier)被标记了virtual,那么它便可以在派生合约中被override(重写)

继承可以让您扩展合约的属性,在修改器(modifier)的上下文中,继承允许您添加新的(modifier),或覆盖现有的(modifier)

下面的简单实现演示了继承和修改器如何一起工作。

contract A {
    modifier X virtual {
    }
}    

contract B is A {
    modifier X override {
    }
}

在这个例子中,合约 B 继承了合约 A,两个合约都有一个叫做 X的修改器。然而,在合约 B 中,该修饰语被标记为override,这表明它覆盖了合约 A 中的修改器。

如何使用修改器

当使用一个修改器时,您首先需要在合约中定义修改器函数。修改器使用一个特殊的符号_,只有当修改器的条件得到满足时,才会插入函数体。

下面的合同演示了如何使用一个修改器。

contract Owner {
   address public owner = msg.sender;
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

在上面的例子中,该合约有两个修改器:onlyOwner costs

第一个修改器检查msg.sender是否是合约的所有者,第二个修改器检查msg.value是否大于或等于某个价格。

这两个修饰符都可以用在合同中的任何函数上。

_ 放在哪里?

_ 被称为合并通配符。它将函数代码与 _ 所在的修改器代码合并。

换句话说,函数的主体(修改器所连接的)将被插入修改器的定义中出现 _ 的地方。

一个修改器必须在其主体内有 _ 才能执行。它是强制性的(如果不是这样的话,Solidity 会抛出一个错误吗)。

您写将 _ 符号的地方将决定该函数是在修改器代码之前、之间还是之后执行。

modifier SomethingBefore {
    require(/* check something first */);
    _; // 继续执行功能
}

modifier SomethingAfter {
    _; // 先运行函数
    require(/* then check something */)
}

如上面的例子所示,您可以把 _ 放在修饰语体的开头、中间或结尾。

在实践中,(特别是在您真正了解修改器的工作原理之前),最安全的使用模式是将 _ 放在最后。在这种情况下,修改器的作用是一致的验证检查,所以要先检查一个条件,然后再继续。下面的代码片段显示了这个例子。

function isOkay() public view returns(bool) {
    // 做一些验证检查
    return true;
}
function isAuthorised(address _user) public view returns(bool) {
    // 逻辑检查以及账户授权
    return true; 
}
modifier OnlyIfOkAndAuthorised {
    require(isOkay());
    require(isAuthorised(msg.sender));
    _;
}

向修改器传递参数

修改器也可以接受参数。像函数一样。

modifier Fee (uint _fee) {
    if (msg.value >= _fee) {
        _;
    }
}

使用上面的例子,您可以确保调用您的一个合约函数的用户(或合约)已经发送了一些以太币来支付预先要求的费用。

让我们用一个简单的例子来说明,这个合约就像一个金库。

您想确保每个想取出储存在合约金库中的钱的用户都要向合约支付最低2.5%的费用。

一个带有参数的修改器可以模拟这种行为。请看下面的代码。

// SPDX-License-Idendifier: GPL-3.0
pragma solidity ^0.8.0;
contract Vault {

    modifier fee(uint _fee) {
        if (msg.value != _fee) {
            revert("You must pay a fee to withdraw your ethers");
        } else {
            _;
        }
    }

    function deposit(address _user, uint _amount) external {
        // ...
    }

    function withdraw(uint _amount) external payable fee(0.025 ether) {
        // ...
    }
 }

修改器参数允许使用任意表达式,在这种情况下,所有从函数中可见的符号都在修改器中可见。

在一个函数上应用多个修改器。

多个修改器可以应用到一个函数,您可以这样做。

contract OwnerContract {    
    address public owner = msg.sender;
    uint public creationTime = now;    

    modifier onlyBy(address _account) {
        require(
            msg.sender == _account,
            "Sender not authorized.
        );
        _;
    }    

    modifier onlyAfter(uint _time) {
        require(
            now >= _time,
            "Function called too early."
        );
        _;
    }    

    function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
        delete owner;
    }
 }

修改器将按照它们被定义的顺序执行,所以从左到右。所以在上面的例子中,该函数将在运行前检查以下条件。

onlyBy(...) : 调用合同的地址是否为所有者?
onlyAfter(...) : 是否有超过6周的时间是该人的所有者?

带枚举的修改器

如果您的合约持有一个枚举类型的状态变量,您可以通过传递一个可用的选项作为修改器的参数来检查它持有的值。

enum State { Created, Locked, Inactive }
State state;modifier isState(State _expectedState) {
    require(state == _expectedState);
    _;
}