
适用范围与安全基础
讨论区块链编写智能合约有哪些常见问题,需要先限定执行环境。以下主要适用于以太坊与 Solidity 合约,不代表所有区块链的实现方式。以太坊开发者安全文档强调权限控制、执行校验、测试和独立审查;OpenZeppelin Contracts 安全文档则介绍重入防护、拉取式支付与暂停模块。
合约部署后的逻辑通常不能直接修改,因此安全问题不能只依赖上线后修补。开发阶段需要明确谁能改变状态、哪些条件必须成立,以及操作失败时如何处理。

权限问题:公开入口不等于任意授权
public 或 external 决定函数是否可以从外部调用,并不自动限制调用者的身份。涉及增发、升级或暂停的入口,需要独立的授权检查;private 也不能替代整个系统的权限设计。

单一管理员便于管理,却集中了承担风险的权限。角色划分可以缩小单个账户的操作范围,多签可以要求多方共同批准,但这些机制并不意味着风险消失。审查时还需确认谁能授予角色、撤销角色或更换管理员。
重入问题:外部调用会改变执行顺序
合约与外部账户或合约交互时,对方可能在原操作结束前再次调用相关入口。如果内部记账尚未完成,后续调用可能基于过时状态执行。因此,不能把外部调用简单理解为一次没有后续影响的转账。
常见防护思路是先检查条件、再更新内部状态、最后执行外部交互,并在适当入口使用重入锁。拉取式支付将记账与领取分开,但不能据此断言整个合约没有重入风险,仍需检查提款路径和共享状态。
校验问题:输入条件与内部不变量混淆
require 适合检查输入、权限和业务前提;revert 可以在条件分支中明确终止执行;assert 主要用于检查本应始终成立的内部不变量。三者用途不同,不能仅以是否能让交易失败来选择。
例如,用户请求超过可用额度属于可预期的输入问题,而内部账目违反既定约束属于不变量问题。明确区分两者,有助于定位错误,也能让测试分别覆盖正常拒绝路径与异常状态。
模块问题:继承不等于完成防护
OpenZeppelin 的暂停模块需要应用相应修饰器才会限制函数执行。同样,重入防护也需要接入目标入口;使用同一重入锁的受保护函数之间不能直接嵌套调用。模块是否有效,取决于实际调用路径。
所引 OpenZeppelin 页面属于旧版 4.x 文档,导入路径和接口不能直接视为其他版本的用法。暂停还需要明确触发权限、覆盖范围与恢复条件,它是应急控制手段,并不会自动修复缺陷。
验证问题:测试通过不等于没有漏洞
单元测试只能验证已设计的案例,还应关注越权调用、重复操作、边界输入和外部交互失败。静态分析、模糊测试与独立审查可以从不同角度补充检查,但任何单一手段都不能作为绝对安全证明。
形式化验证的结论也受规范、模型和假设约束,不能扩大为合约不存在任何问题。更可靠的开发流程,是先写清安全属性,再检查实现、测试与部署配置是否共同满足这些属性。