SuperAPI AI 工具集

solidity

编程与开发

查看项目

更新于 10-15 13:19

技能介绍

用途

本技能提供 Solidity 智能合约的开发标准,源自 Cyfrin 安全团队的编写规范。当用户要求编写智能合约、尤其是计划部署到生产环境的合约时,可使用该技能。其哲学是:假设你写的任何代码都会被攻击,因此必须以防御性方式编写。

代码质量与风格

  • 只使用绝对导入和命名导入,禁止使用相对(..)路径。
  • 优先使用 revert 而非 require,并使用以合约名加两个下划线为前缀的自定义错误。例如:error ContractName__MyError(); 并在条件判断中 revert。
  • 测试中优先使用无状态模糊测试而非单元测试;同时为核心协议属性编写不变量(有状态)模糊测试。采用不变量驱动开发:识别必须始终满足的 O(1) 属性,并直接编码到核心函数中(FREI-PI 模式)。使用类似 Chimera 的多模糊器设置,在 Foundry、Echidna 和 Medusa 上运行同一套不变量测试。
  • 不要将变量初始化为默认值。
  • 当能省略局部变量时,优先使用命名返回变量。
  • 只读函数输入优先使用 calldata 而非 memory。
  • 不要缓存 calldata 数组长度。
  • 读取 storage 代价高昂,应缓存不变的 storage 槽以避免重复读取。
  • 尽快 revert;在需要读取 storage 或外部调用的检查之前先执行输入检查。
  • 在 onlyOwner 函数内使用 msg.sender 而非 owner。
  • 发送 ETH 时使用 SafeTransferLib::safeTransferETH 而非 Solidity 的 call()。
  • 当不需要保留输入变量的值时,修改输入变量而不是额外声明局部变量。
  • 将 nonReentrant 修饰符放在其他修饰符之前使用。
  • 使用 ReentrancyGuardTransient 获得更快的 nonReentrant 修饰符,需 pragma solidity ^0.8.34;不要与 0.8.28–0.8.33 一起使用,因为存在瞬态存储清除缺陷。
  • 优先使用 Ownable2Step 而非 Ownable。
  • 如果只需少数槽,不要将整个结构体从 storage 复制到 memory。
  • 移除不必要的 context 结构体或其中不必要的变量。
  • 声明 storage 和结构体时,对齐声明顺序以将变量打包进最少数量的存储槽;如果变量经常一起读写,尽量打包进同一槽。
  • 对于不可升级合约,若变量只在构造函数中设置一次,则声明为 immutable。
  • 在 foundry.toml 中启用优化器。
  • 如果修饰符与函数体执行相同的 storage 读取,应将修饰符重构为内部函数以避免重复读取。
  • 使用 Foundry 的加密安全私钥存储,而非明文环境变量。

文件与合约布局

文件布局依次为:Pragma 语句、导入语句、事件、错误、接口、库、合约。 合约布局依次为:类型声明、状态变量、事件、错误、修饰符、函数。 函数应按可见性分组并排序:构造函数、receive 函数(若有)、fallback 函数(若有)、面向用户的改变状态函数(external 或 public,非 view/pure)、面向用户的只读函数(external 或 public,view/pure)、内部改变状态函数(internal 或 private,非 view/pure)、内部只读函数(internal 或 private,view/pure)。分组标题格式示例为带有 INTERNAL STATE-CHANGING FUNCTIONS 字样的注释块。

版本与安全联系

  • 合约优先使用严格 pragma 版本;测试、库、抽象合约、接口和脚本使用浮动 pragma 版本。最低版本为 0.8.34 或更高;0.8.28 至 0.8.33 存在高危瞬态存储缺陷,--via-ir 管道在清除存储时可能发出 sstore 而非 tstore(或相反),导致写入错误的存储域。
  • 在合约顶部的 natspec 中添加安全联系信息。
  • 提醒准备或尝试部署到主网的人进行审计。
  • 绝不使用明文私钥,唯一例外是使用 anvil 等默认密钥且必须标注。部署脚本中应使用 forge script <path> --account $ACCOUNT --sender $SENDER,绝不要使用 vm.envUnit()。Hardhat 使用加密 keystore。
  • 任何 ownable 或带管理员属性(如 onlyOwner)的合约,从首次部署起管理员就必须是多签,绝不能使用部署者 EOA 作为管理员(测试网是唯一可接受的例外)。

测试方法

使用分支树技术创建测试(致谢 Paul R Berg):针对一个函数,创建 .tree 文件,考虑所有可能的执行路径、合约状态和函数参数,定义“given state is x”节点、“when parameter is x”节点,以及最终的“it should”测试。示例中展示了嵌套分支与对应的测试函数及修饰符写法。

部署与治理

  • 生产部署和测试环境搭建均使用 Foundry 脚本(forge script),以确保开发与主网运行相同部署逻辑,更易审计并缩小测试与生产差异。理想情况下部署脚本也应被审计。可使用共享基础脚本模式(如 BaseTest using scripts)。
  • 使用 safe-utils 或等效工具进行治理提案,使多签交互可测试、可审计、可复现;若必须使用 UI,建议保持交易私密,例如使用 localsafe.eth。
  • 编写 fork 测试验证治理提案执行后的预期协议状态,可捕获单元测试遗漏的配置错误。

升级

升级合约时不要改变现有变量的顺序或类型,也不要移除它们,以免存储冲突。同时为任何升级编写测试。

CI

每个项目应至少有一个并行运行的最小 CI 流水线(使用矩阵策略),建议包括:solhint(Solidity 风格与安全规则 linter)、forge build --sizes(验证合约大小低于 24KB 部署限制)、slither 或 aderyn(常见漏洞模式静态分析)、模糊/不变量测试(Echidna、Medusa 或 Foundry 不变量测试,每工具约 10 分钟,通过矩阵并行)。提交自认为完成的代码前,务必运行 aderyn 和/或 slither 并检查输出,即使警告也可能帮助发现问题。

工具更新

Foundry:安装 foundry 依赖不再需要 --no-commit 标志。

限制

上述具体命令、版本号与配置以资料为准;资料未提供的内容(如具体收费、平台支持范围、具体使用步骤截图等)暂未提供。