ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

WTF-Solidity 实战:深入解析 Solidity 构造函数(constructor)与修饰器(modifier)——以 Ownable 访问控制合约为例

2026/9/15 12:54:16 拓冰建站 浏览量
WTF-Solidity 实战:深入解析 Solidity 构造函数(constructor)与修饰器(modifier)——以 Ownable 访问控制合约为例 WTF-Solidity 实战深入解析 Solidity 构造函数constructor与修饰器modifier——以 Ownable 访问控制合约为例【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity本篇技术指南围绕 WTF-Solidity 教程第 11 讲展开系统讲解 Solidity 中constructor构造函数与modifier修饰器的核心语法、执行机制与最佳实践并以一个完整的Owner访问控制合约Ownable 模式的极简版贯穿始终。读完本文你将掌握构造函数如何初始化合约状态、_占位符如何决定修饰器逻辑与函数体的执行顺序、如何用onlyOwner限制敏感函数的调用权限并能对照 OpenZeppelin 官方实现写出生产级别的访问控制代码。一、为什么从 Ownable 学起在智能合约开发中constructor与modifier是出现频率极高的两个语言特性constructor负责在合约部署的那一刻完成状态初始化例如记录部署者地址modifier则负责为函数附加通用检查逻辑最典型的场景就是只有合约 owner 才能调用本函数。WTF-Solidity 教程正是以访问控制合约Ownable为最小载体把这两个特性串起来讲清楚。对应的可运行源码位于 Languages/en/11_Modifier_en/Owner.sol英文版与 11_Modifier/Owner.sol中文版后续所有分析均以此为准。二、constructor部署时只运行一次的初始化器2.1 基本语法与作用constructor是一个特殊函数在合约部署时自动执行且仅执行一次。每个合约最多只能声明一个构造函数。它的核心用途是初始化合约参数例如设置owner地址address owner; // 定义 owner 变量 // 构造函数 constructor() { owner msg.sender; // 将 owner 设置为部署者地址 }这里msg.sender在部署交易执行期间指向发起部署的地址因此这行代码的效果是谁部署合约谁就是 owner。2.2 两种初始化写法对比仓库源码实证仓库中同时保留了该合约的英文版与中文版恰好演示了构造函数的两种常见初始化方式版本构造函数初始化来源Languages/en/11_Modifier_en/Owner.solconstructor()隐式使用msg.sender部署者11_Modifier/Owner.solconstructor(address initialOwner)显式传入initialOwner参数英文版// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; contract Owner { address public owner; constructor() { owner msg.sender; // set owner to the address of deployer when contract is being deployed } ... }中文版则把部署者地址作为入参交给部署方决定// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; contract Owner { address public owner; // 构造函数 constructor(address initialOwner) { owner initialOwner; // 在部署合约的时候将 owner 设置为传入的 initialOwner 地址 } ... }两种写法在功能上都成立前者更省心天然指向部署者后者更灵活可以由工厂合约或其他代理地址代为部署并指定真正的 owner。实际项目中后者常用于合约由部署脚手架创建、但所有权需要交给多签钱包的场景。2.3 语法演进0.4.22 之前的老写法Solidity 各版本的构造函数语法并不一致这一点需要特别注意0.4.22之前没有constructor关键字构造函数必须写成与合约同名的函数0.4.22及之后改用constructor关键字。旧语法非常容易踩坑——开发者可能把合约命名为Parents构造函数却误写成parents大小写不一致导致它退化为一个普通函数合约初始化逻辑被完全跳过。0.4.22 之前的写法示例pragma solidity 0.4.21; contract Parents { // 与合约同名的函数才是构造函数 function Parents () public { } }自0.4.22起统一使用constructor关键字后这类命名混淆问题从根本上得到规避。现代项目包括本仓库所有合约均已使用pragma solidity ^0.8.34与新式构造函数语法。三、modifier函数的钢铁侠战甲3.1 设计动机modifier类似于面向对象编程中的装饰器decorator用于声明函数的专属属性、削减重复代码。教程里有一个形象的比喻modifier 是函数的钢铁侠战甲——被 modifier 修饰的函数会获得某些魔法属性。最流行的用法就是限制函数访问权限与其在每个敏感函数里重复写require(msg.sender owner)不如把这段校验封装成一个 modifier然后挂在所有需要保护的函数上。3.2 定义一个 onlyOwner 修饰器// 定义 modifier modifier onlyOwner { require(msg.sender owner); // 检查调用者是否为 owner 地址 _; // 如果是的话继续运行函数主体否则报错并 revert 交易 }语法要点使用modifier关键字声明命名习惯上用动词短语如onlyOwner函数体内先写前置检查逻辑这里是require权限校验_;是占位符placeholder代表被修饰函数的函数体插入的位置如果require条件不满足整个交易会 revert_;及之后的逻辑都不会执行。3.3_占位符与执行顺序_的位置决定了检查逻辑与函数体的先后关系这是理解 modifier 的关键前置检查型最常见require(...); _;——先校验、后执行函数体onlyOwner即属于此类后置处理型_; require(...);——先执行函数体、再校验例如操作完成后确认余额不变这类防重入校验包裹型require(...); _; require(...);——函数体前后各有一段逻辑OpenZeppelin 的nonReentrant即采用类似结构进入前加锁、退出后解锁。把 modifier 想成一个函数包装器即可编译时被修饰函数的函数体会被内联到_;所在的位置。3.4 使用 modifier 保护函数接下来定义一个changeOwner函数它可以把合约的owner换成新地址。但由于挂了onlyOwner修饰器只有原 owner 才能成功调用——这正是智能合约中最常见的访问控制实现function changeOwner(address _newOwner) external onlyOwner{ owner _newOwner; // 只有 owner 地址才能运行这个函数并改变 owner }四、完整合约极简 Ownable 访问控制将上述片段组合起来就是一份可直接复制进 Remix 运行的完整合约与 Languages/en/11_Modifier_en/Owner.sol 一致// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; contract Owner { address public owner; // define owner variable // constructor constructor() { owner msg.sender; // set owner to the address of deployer when contract is being deployed } // define modifier modifier onlyOwner { require(msg.sender owner); // check whether caller is address of owner _; // if true, continue to run the body of function; otherwise throw an error and revert transaction } // define a function with onlyOwner modifier function changeOwner(address _newOwner) external onlyOwner{ owner _newOwner; // only owner address can run this function and change owner } }其运行流程可以拆解为三步部署阶段constructor()自动执行owner被写为部署者地址owner 调用changeOwner(_newOwner)modifier 内require(msg.sender owner)通过 → 执行_;处的函数体 →owner更新为新地址非 owner 调用changeOwner(_newOwner)require失败 → 交易回滚revertowner保持不变。注意address public owner声明为public编译器会自动生成一个同名的owner()只读 getter 函数方便在 Remix 或链上直接查询当前 owner。五、对照 OpenZeppelin 官方 Ownable 实现教程指出OpenZeppelin是一个维护标准化 Solidity 代码库的组织其标准版Ownable值得对照学习。当前仓库的lib/openzeppelin-contracts子模块中即包含该实现lib/openzeppelin-contracts/contracts/access/Ownable.solv5.0.0。官方实现与本教程的极简版在思想上一脉相承但做了工程化增强维度极简版 OwnerOpenZeppelin Ownable v5.0.0权限检查require(msg.sender owner)_checkOwner()内部revert OwnableUnauthorizedAccount(_msgSender())错误处理无错误信息自定义错误OwnableUnauthorizedAccount/OwnableInvalidOwnerGas 更省状态变量address public owneraddress private _ownerowner()视图函数所有权转移直接改owner标准transferOwnership()OwnershipTransferred事件放弃所有权无renounceOwnership()将 owner 置为address(0)构造函数直接赋值校验initialOwner ! address(0)后调用_transferOwnership官方核心片段abstract contract Ownable is Context { address private _owner; error OwnableUnauthorizedAccount(address account); error OwnableInvalidOwner(address owner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor(address initialOwner) { if (initialOwner address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(initialOwner); } modifier onlyOwner() { _checkOwner(); _; } ... }从源码可以看到官方版把检查拆成了独立的_checkOwner()内部函数modifier 中仍是经典的_checkOwner(); _;结构同时引入自定义错误代替字符串错误信息v0.8.4 特性可大幅节省 revert 时的 Gas所有权变更时还会发出OwnershipTransferred事件供链下索引。极简版负责讲原理官方版负责讲工程化两者对照学习效果最佳。六、进阶带参数的 modifier 与修饰器继承仓库源码佐证onlyOwner是无参 modifier而 Solidity 的 modifier 同样支持参数。教程第 13 讲的 13_Inheritance/ModifierInheritance.sol 提供了一个带参数的例子可用于巩固_占位符的执行顺序contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { require(_a % 2 0 _a % 3 0); _; } } contract Identifier is Base1 { // 计算一个数分别被2除和被3除的值但传入的参数必须是2和3的倍数 function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { return getExactDividedBy2And3WithoutModifier(_dividend); } ... }这里 modifierexactDividedBy2And3(uint _a)接收函数参数_dividend先校验它同时是 2 和 3 的倍数通过后才执行函数体。此外该文件还演示了修饰器的继承与重写modifier可声明为virtual并在派生合约中override——例如重写为只有_;的放行版就能绕过原校验逻辑。这既展示了 modifier 的灵活性也提醒开发者重写修饰器会改变访问控制语义生产代码中需谨慎。七、Remix 实操演示三步验证访问控制下面以 Languages/en/11_Modifier_en/Owner.sol 为例在 Remix 中完成完整验证。第 1 步编译并部署合约。点击左侧owner按钮可查看当前 owner 为部署者地址第 2 步使用 owner 地址账户调用changeOwner传入新地址交易成功owner更新为新地址第 3 步切换到其他非 owner 账户再次调用changeOwner交易被onlyOwner修饰器拦截Remix 控制台报出VM error: revertowner保持不变这三张截图见 Languages/en/11_Modifier_en/img 目录完整呈现了成功路径与失败路径的差异成功时 Remix 底部交易记录为绿色对勾并提示CALL changeOwner function successfully失败时则提示revert直观展示require(msg.sender owner)的拦截效果。八、小结本节核心收获可概括为三点constructor部署时自动执行一次的初始化函数0.4.22之前用同名函数、之后统一用constructor关键字可通过msg.sender或显式参数初始化合约状态modifier以_占位符为核心的函数包装器通过前置/后置检查为函数注入通用逻辑onlyOwner是最经典的访问控制用法Ownable 模式用owner状态变量 constructor初始化 onlyOwner修饰器保护关键函数是智能合约访问控制的标准范式OpenZeppelin 官方实现在此基础上补齐了错误定义、事件与所有权转移的标准接口。掌握了 constructor 与 modifier你就拿到了 Solidity 中初始化与函数治理两把核心钥匙——后续教程中的 ERC20、ERC721、多签钱包50_MultisigWallet等合约都建立在这两个基础特性之上。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考