本节是《Solidity by Example》的中文翻译与深入讲解,专为零基础或刚接触区块链开发的小白朋友打造。我们将通过“示例 + 解说 + 提示”的方式,带你逐步理解每一段 Solidity 代码的实际用途与背后的逻辑。
Solidity 是以太坊等智能合约平台使用的主要编程语言,就像写网页要用 HTML 和 JavaScript,写智能合约就需要会 Solidity。
如果你从没写过区块链代码也没关系,只要你了解一点点编程概念,比如“变量”“函数”“条件判断”,我们就能从最简单的例子开始,一步步建立你的 Solidity 编程思维。
Function Modifier
函数修饰器
修饰器是一段可以在函数调用之前和/或之后运行的代码。
修饰器可用于:
- 限制访问权限
- 验证输入
- 防止重入攻击
什么是 Solidity 函数修饰器?
函数修饰器(Modifier)是 Solidity 中一种代码复用机制,允许在函数执行前后插入逻辑,用于检查条件或执行额外操作。
- 比喻:修饰器像门禁系统,在你进入房间(调用函数)前检查身份或条件,离开时可能清理现场。
- 修饰器通过
_;
占位符指示函数主体的执行位置,_;
前后分别运行前置和后置逻辑。
修饰器的用途:
- 限制访问权限:如
onlyOwner
修饰器,确保只有合约拥有者能调用函数。 - 验证输入:如
validAddress
修饰器,检查输入地址是否有效。 - 防止重入攻击:如
noReentrancy
修饰器,锁定函数防止重复调用(重入)。
// SPDX-License-Identifier: MIT
// 使用 MIT 许可证,允许自由使用、修改和分发代码。
pragma solidity ^0.8.26;
// 指定 Solidity 编译器版本,必须为 0.8.26 或更高(但低于 0.9.0)。
contract FunctionModifier {
// 定义一个名为 FunctionModifier 的智能合约,展示函数修饰器的用法。
// We will use these variables to demonstrate how to use
// modifiers.
// 我们将使用这些变量来展示如何使用修饰器。
address public owner;
// 声明一个公共状态变量 owner,存储合约拥有者的地址(storage)。
// public 自动生成 getter 函数(owner())。
uint256 public x = 10;
// 声明一个公共状态变量 x,初始值为 10(storage),用于测试修饰器效果。
bool public locked;
// 声明一个公共状态变量 locked,初始值为 false(storage),用于防止重入攻击。
constructor() {
// Set the transaction sender as the owner of the contract.
// 将交易发送者设置为合约的拥有者。
owner = msg.sender;
// 在合约部署时,将调用者的地址(msg.sender)赋给 owner。
// msg.sender 是部署合约的账户地址。
}
// Modifier to check that the caller is the owner of
// the contract.
// 检查调用者是否为合约拥有者的修饰器。
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
// 使用 require 检查调用者(msg.sender)是否为 owner。
// 如果不是,抛出错误“Not owner”,交易回滚。
// Underscore is a special character only used inside
// a function modifier and it tells Solidity to
// execute the rest of the code.
// 下划线是修饰器中使用的特殊字符,指示 Solidity 执行后续代码。
_;
// _; 表示执行被修饰函数的主体代码。
// 本修饰器只有前置逻辑(检查权限),无后置逻辑。
}
// Modifiers can take inputs. This modifier checks that the
// address passed in is not the zero address.
// 修饰器可以接受输入参数。此修饰器检查传入的地址不是零地址。
modifier validAddress(address _addr) {
require(_addr != address(0), "Not valid address");
// 使用 require 检查输入地址 _addr 是否为零地址(address(0))。
// 如果是,抛出错误“Not valid address”,交易回滚。
_;
// _; 表示执行被修饰函数的主体代码。
// 本修饰器只有前置逻辑(检查地址有效性)。
}
function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner)
{
// 定义一个公共函数 changeOwner,接受新拥有者地址 _newOwner。
// public:可被外部和内部调用。
// 应用 onlyOwner 和 validAddress 修饰器:
// - onlyOwner:确保只有当前 owner 能调用。
// - validAddress:确保 _newOwner 非零地址。
owner = _newOwner;
// 将 owner 更新为 _newOwner,修改 storage,消耗 Gas。
}
// Modifiers can be called before and / or after a function.
// This modifier prevents a function from being called while
// it is still executing.
// 修饰器可以在函数调用前和/或后执行。
// 此修饰器防止函数在执行期间被再次调用。
modifier noReentrancy() {
require(!locked, "No reentrancy");
// 使用 require 检查 locked 是否为 false。
// 如果 locked 为 true(函数正在执行),抛出错误“No reentrancy”,防止重入。
locked = true;
// 设置 locked 为 true,表示函数进入执行状态,锁定合约。
_;
// _; 表示执行被修饰函数的主体代码。
locked = false;
// 函数执行后,设置 locked 为 false,释放锁。
// 本修饰器有前置逻辑(检查锁并加锁)和后置逻辑(解锁)。
}
function decrement(uint256 i) public noReentrancy {
// 定义一个公共函数 decrement,接受参数 i,减少状态变量 x。
// 应用 noReentrancy 修饰器,防止重入攻击。
x -= i;
// 将状态变量 x 减去 i,修改 storage,消耗 Gas。
if (i > 1) {
decrement(i - 1);
// 如果 i > 1,递归调用 decrement,传入 i-1。
// noReentrancy 修饰器确保递归调用不会导致重入。
}
}
}
FunctionModifier
合约展示修饰器的三种主要用途:
- 限制访问:
onlyOwner
修饰器确保只有合约拥有者能调用函数。 - 验证输入:
validAddress
修饰器检查输入地址非零。 - 防止重入:
noReentrancy
修饰器通过锁机制防止重入攻击。 - 合约包含状态变量(
owner
、x
、locked
)和两个函数(changeOwner
、decrement
),演示修饰器的实际应用。
函数修饰器的本质
- 函数修饰器是 Solidity 中一种代码复用工具,允许在函数执行前后插入逻辑,用于检查条件、限制访问或执行清理操作。
- 核心功能:
- 限制访问:如
onlyOwner
,确保只有特定用户(如合约拥有者)能调用函数。 - 验证输入:如
validAddress
,检查输入参数有效性(如非零地址)。 - 防止重入:如
noReentrancy
,通过锁机制防止函数被重复调用(重入攻击)。
- 限制访问:如
- 优势:
- 代码复用:将通用逻辑(如权限检查)封装到修饰器,避免重复编写。
- 清晰性:使函数逻辑更简洁,检查条件集中管理。
- 安全性:防止未授权访问或重入攻击。
代码功能
- 合约
FunctionModifier
:- 状态变量:
owner
:存储合约拥有者地址,初始化为部署者(msg.sender
)。x
:存储一个计数器,初始值为 10,用于测试函数效果。locked
:布尔值,初始为 false,用于防止重入攻击。
- 修饰器:
onlyOwner
:检查调用者是否为owner
,否则抛出错误“Not owner”。validAddress
:检查输入地址是否非零,否则抛出错误“Not valid address”。noReentrancy
:检查并设置锁(locked
),防止函数重入,执行后解锁。
- 函数:
changeOwner
:更新owner
地址,需通过onlyOwner
和validAddress
检查。decrement
:减少x
,支持递归调用,noReentrancy
防止重入攻击。
- 状态变量:
函数修饰器的注意事项
- 执行顺序:
- 多个修饰器按声明顺序执行(如
onlyOwner validAddress
先检查权限再验证地址)。 _;
前为前置逻辑,后为后置逻辑(如noReentrancy
的加锁和解锁)。
- 多个修饰器按声明顺序执行(如
- Gas 成本:
- 修饰器增加检查逻辑(如
require
),消耗少量 Gas。 - 修改
storage
(如locked
)消耗约 20,000+ Gas,可用瞬态存储优化(参考历史对话)。 - 错误抛出(如“Not owner”)消耗少量 Gas,但尽早抛出节省后续计算。
- 修饰器增加检查逻辑(如
- 安全性:
- 检查关键条件(如
msg.sender
、address(0)
),避免未授权访问。 - 重入保护(如
noReentrancy
)需确保锁状态正确重置。 - 修饰器逻辑需简单,避免引入复杂 bug。
- 检查关键条件(如
- 局限性:
- 修饰器不能直接访问函数参数或返回值,需通过输入参数(如
validAddress(address _addr)
)。 - 递归调用(如
decrement
)需确保修饰器(如noReentrancy
)支持。
- 修饰器不能直接访问函数参数或返回值,需通过输入参数(如