Solidity 中的 modifier 是什么?
作者 Frederik B
Solidity 是一种面向对象的高级语言,用于实现智能合约。在学习 Solidity 的过程中,你会遇到修饰器(modifier),这是一种用于修改其他函数行为的特殊函数。
本文将解释 Solidity 修饰器的作用,介绍修饰器的类型,并展示如何使用它们。读完本文后,你将能够识别 Solidity 修饰器并理解如何使用它们。
Solidity 修饰器的作用是什么?
修饰器是一种特殊的Solidity 函数类型,用于修改其他函数的行为。 例如,开发者可以使用修饰器在允许函数执行之前检查某个条件是否满足。
修饰器与函数类似,可以接受参数并具有返回类型。修饰器也可以链式组合,也就是说一个函数上可以有多个修饰器。
不过,修饰器只能修改合约逻辑,不能修改合约的存储,这也包括结构体(struct)。修饰器减少了开发者需要编写的样板代码,可以让你的 Solidity 代码更具可读性。
一个 Solidity 函数上可以有多个修饰器吗?
可以,开发者可以在一个 Solidity 函数上使用多个修饰器。 多个修饰器之间用逗号分隔。关于使用多个修饰器,有一点需要注意:修饰器的顺序很重要。列表中第一个修饰器会最先执行,第二个修饰器第二个应用,依此类推。
例如,如果你有一个修饰器用于检查用户是否已通过身份验证,另一个修饰器用于检查用户是否有权限查看某个资源,那么这些修饰器的应用顺序将决定用户是否能够查看该资源。
Solidity 修饰器有哪些不同类型?
Solidity 修饰器大致可以分为四类:门禁检查(gate check)、前提条件(prerequisite)、过滤器(filter)以及重入攻击防护(reentrancy attack prevention)。
1. 门禁检查(Gate checks)
“门禁检查”是一种在允许函数执行之前,检查某个条件是否为真的修饰器。
例如,你可能有一个函数允许用户从账户中提取资金,但在函数执行之前,开发者可能希望检查用户账户中是否有足够的资金进行提现。这种检查就被视为门禁检查修饰器。
门禁检查的另一个例子是:在允许用户查看某个资源之前,检查用户是否已通过身份验证的函数。
2. 前提条件(Prerequisites)
“前提条件”是一种为函数执行搭建环境的修饰器,而不是检查某个条件是否为真。
例如,Solidity 开发者可能会使用一个函数,要求在调用时必须随之发送一定数量的以太币才能执行。在这种情况下,前提条件就是设置以太币余额的函数。
3. 过滤器(Filters)
“过滤器”是一种检查某个条件是否为真的修饰器,如果为真,则允许函数执行。如果条件不为真,则函数不会执行。
与门禁检查不同的是,门禁检查即使条件为真,也不会自动允许函数执行,而过滤器在条件为真时会允许函数执行。
4. 重入攻击防护(Reentrancy attack prevention)
重入攻击是一种攻击方式,恶意行为者试图通过递归调用多次执行某个函数,以此进行攻击利用。
例如,假设你有一个函数允许用户从账户中提取资金。发起重入攻击的攻击者可能会尝试多次调用该函数,以提取超过其账户实际余额的资金。
为了防止重入攻击,你可以使用一个修饰器来检查该函数是否正在被递归调用。如果是,则函数不会执行。
require 与 Solidity 修饰器之间是什么关系?
require 常常与修饰器互换使用,因为二者都可以让你在允许函数执行之前检查某个条件是否为真。如果指定的条件不为真,编译器就会抛出错误。
例如,以下语句使用了 require 关键字,使得只有所有者才能与该函数进行交互:
require 和 Solidity 修饰器之间存在一些重要区别:
- 修饰器可以用于为函数执行搭建环境(如前提条件的情况)
- require 只能用于检查某个条件是否为真
- 修饰器可以被重写
- require 不能被重写
Solidity 处理修饰器的方式与 Vyper 相比有何不同?
Vyper 是一种用于 Ethereum 智能合约开发的 Python 风格语言,它做出了某些权衡以提高安全性,包括不使用修饰器。相反,开发者应该在函数内使用内联检查和 assert,如果要修改智能合约,也应该再次将改动作为函数的一部分显式写出。
Vyper 移除修饰器功能的选择提高了智能合约的可审计性,因为读者不必在脑海中把修饰器套用到函数上,才能看出函数会做什么。
什么是修饰器重写?
关键字 “virtual” 可用于表明某个修饰器可以在派生合约中被重写。例如,假设你有一个合约 “Base Contract”,其中有一个修饰器 “myModifier”。你还有一个合约 “Derived Contract”,它继承自 “Base Contract”。
如果 “Base Contract” 被标记为 “virtual”,那么它就可以在派生合约中被重写。这种做法常用于库的场景中,即某个合约允许被自定义。
修饰器如何与继承一起工作?
继承让你可以扩展合约的属性,在修饰器的语境下,继承使你可以添加新的修饰器,或重写已有的修饰器。 这可以通过上一节提到的关键字 virtual 来实现。
下面这个简单的实现展示了继承和修饰器是如何协同工作的:
在这个例子中,合约 B 继承自合约 A。两个合约都有一个名为 “X” 的修饰器。不过,在合约 B 中,该修饰器被标记为 “override”,表示它重写了合约 A 中的修饰器。
如何在 Solidity 中使用修饰器
要使用修饰器,你首先需要在合约中定义修饰器函数。修饰器使用一个特殊符号 “_;”,只有在满足修饰器条件时,才会插入函数体。
下面的合约展示了如何使用修饰器:
在上面的例子中,该合约有两个修饰器:“onlyOwner” 和 “costs”。
第一个修饰器检查 msg.sender 是否为合约的所有者,第二个修饰器检查 msg.value 是否大于或等于某个价格。
这两个修饰器都可以用在合约中的任何函数上。
继续学习 Solidity 修饰器
修饰器是用于修改其他函数行为的函数。通过使用修饰器,你可以减少需要编写的样板代码,让你的 Solidity 代码更具可读性。
要继续学习 Solidity 修饰器并了解如何成为 Solidity 开发者,请报名参加 Alchemy University 为期 7 周的 Ethereum 开发者训练营。这门免费的Solidity 速成课程最初由 ChainShot 团队创建,是学习 Solidity 的最佳途径。如果开发者对开发完全是新手,Alchemy University 的为期 3 周的 JavaScript 速成课程是开始 Ethereum 训练营之前的绝佳前置课程。
相关概览

构建区块链应用
Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。


