
foundry 使用指南
简介 入门指南 👉 foundry 入门指南 Forge 👉 foundry Forge 使用指南 Cast 👉 foundry Cast 使用指南 Anvil 👉 foundry Anvil 使用指南 Chisel 👉 foundry Chisel 使用指南 Configuration 👉 foundry 配置指南 Cheatcode 👉 foundry Cheatcode 使用指南

简介 入门指南 👉 foundry 入门指南 Forge 👉 foundry Forge 使用指南 Cast 👉 foundry Cast 使用指南 Anvil 👉 foundry Anvil 使用指南 Chisel 👉 foundry Chisel 使用指南 Configuration 👉 foundry 配置指南 Cheatcode 👉 foundry Cheatcode 使用指南
简介 拒绝服务 ( Dos - Denial of Service ) 简要概述 DoS 攻击在智能合约中常出现于以下场景: 攻击者通过让某个必要操作持续失败,从而阻止整个合约继续执行关键流程(如奖励分发、批量提款、循环遍历列表等)。 典型示例: 合约在循环中向所有参与者发送 ETH,攻击者故意将自己的 fallback/receive 函数设计为 永远 revert,导致程序在遍历到攻击者地址时整个交易回滚,进而导致奖励分发/提款功能长期瘫痪 易受攻击模式: function distribute() external { for (uint256 i = 0; i < participants.length; i++) { address user = participants[i]; uint256 amount = rewards[user]; // 任意一次 transfer 失败,整个分发流程就会 revert payable(user).transfer(amount); } } 攻击者只需在列表中占据一个位置,即可长期阻塞整个流程。 改进方法 使用 Pull Payment 不再向用户“发送奖励”,而是让用户自己来“领取奖励”,这样单个用户的失败不会影响其他用户 function claim() external { uint256 amount = rewards[msg.sender]; require(amount > 0, "no reward"); // Effects(先修改状态) rewards[msg.sender] = 0; // Interactions(再转账) (bool ok, ) = payable(msg.sender).call{value: amount}(""); require(ok, "send failed"); } CEI 说明: Checks:检查 reward 是否大于 0 Effects:先清零 reward 防止重入 Interactions:最后转账,不会阻塞其他用户 在必须 push 的场景中采用“非阻塞式分发” 避免因为某个地址转账失败而导致整个循环回滚 function distribute() external { for (uint256 i = 0; i < participants.length; i++) { address user = participants[i]; uint256 amount = rewards[user]; (bool ok, ) = payable(user).call{value: amount}(""); if (!ok) { // 可记录失败用户,稍后单独处理 // 不阻塞整体流程 } } } 处理 ETH 不当 ( Mishandling of ETH ) 简要概述 在Solidity智能合约中,“mishandling ETH”错误常指对以太币(ETH)接收和处理的不当管理,尤其在使用delegatecall的批量函数(如提供的batch函数)时。该函数允许通过delegatecall执行多个内部调用,并在同一交易上下文中共享msg.value。主要问题是:如果批量调用包括payable函数(如需要特定msg.value的进入抽奖函数),每个子调用都会看到相同的msg.value,导致合约仅接收一次ETH,但执行多次操作(如多次进入)。例如,在Puppy Raffle审计中,攻击者可通过batch调用多次enterRaffle(每个检查msg.value == entranceFee),仅支付一次费用却进入多次,造成资金损失或不公平。此外,依赖address(this).balance计算费用也易被操纵(如通过selfdestruct强制发送ETH) ...
简介 整数溢出(Integer Overflow) 简要概述 在Solidity中,无符号整数(如uint256)在加/减/乘除运算时,若超出类型范围(例如uint256最大值为2^256-1),会发生“回绕”(overflow/underflow),导致意外结果,如余额错误计算、资金无限铸币或丢失。在Solidity <0.8.0版本中无内置检查,易被利用 典型示例: // 错误示范(无溢出检查) pragma solidity ^0.7.0; contract VulnerableToken { mapping(address => uint256) public balances; function transfer(address to, uint256 amount) external { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; // <-- 可能下溢(underflow) balances[to] += amount; // <-- 可能上溢(overflow) } } 攻击者可利用下溢:若余额为0,减去1会回绕成最大值,导致盗取巨额资金 改进方法 升级到Solidity ^0.8.0+(内置溢出检查,会revert),或使用SafeMath库进行安全运算 示例: // 使用内置检查(Solidity ^0.8.0) pragma solidity ^0.8.0; contract SecureToken { mapping(address => uint256) public balances; function transfer(address to, uint256 amount) external { require(balances[msg.sender] >= amount, "insufficient"); balances[msg.sender] -= amount; // 内置检查:下溢会revert balances[to] += amount; // 内置检查:上溢会revert } } 额外建议:对于旧版本,使用OpenZeppelin的SafeMath库(如balances[msg.sender] = balances[msg.sender].sub(amount));始终审计数学运算,并使用有界类型(如uint128)减少风险 不安全类型转换(Unsafe Casting) 简要概述 Solidity中进行类型转换时(如从较大类型uint256到较小类型uint8),若值超出目标类型范围,会发生隐式截断(truncation)或溢出,导致数据丢失、意外行为或安全漏洞,如余额计算错误或权限绕过。Solidity不默认检查转换安全,易被利用。 典型示例: // 错误示范(无安全检查) pragma solidity ^0.8.0; contract VulnerableVault { mapping(address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw(uint8 amount) external { // uint8 仅0-255 require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; payable(msg.sender).transfer(amount); // <-- 若amount实际>255,转换截断导致少转账 } } 攻击者传入>255的值,转换截断成小值,导致实际提取远超预期(但余额扣除完整值) ...
摘要 引入一种新的EIP-2718交易类型,允许外部拥有账户(EOA)为其账户设置代码。这是通过在交易中附加授权元组列表来实现的,每个元组的格式为[chain_id, address, nonce, y_parity, r, s]。对于每个元组,授权账户的代码将被写入委托指示符0xef0100 || address。所有代码执行操作都必须加载并执行委托指向的代码 动机 尽管智能合约钱包生态系统取得了巨大进步,但EOA阻碍了用户体验改进在整个应用栈中的广泛采用。因此,本EIP重点为EOA添加短期功能改进,使UX改进能够渗透到整个应用栈。本EIP设计的三个特定功能是: 批处理:允许同一用户在单个原子交易中执行多个操作 赞助:账户X代表账户Y支付交易费用 权限降级:用户可以签署子密钥并赋予其特定权限 规范 参数 参数 值 SET_CODE_TX_TYPE 0x04 MAGIC 0x05 PER_AUTH_BASE_COST 12500 PER_EMPTY_ACCOUNT_COST 25000 设置代码交易 引入新的EIP-2718交易类型"设置代码交易",其中: TransactionType为SET_CODE_TX_TYPE TransactionPayload是以下字段的RLP序列化: rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s]) authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...] 外部交易的字段遵循EIP-4844语义 签名基于keccak256(SET_CODE_TX_TYPE || TransactionPayload) 授权列表长度不能为零 授权元组中的字段必须符合特定边界 行为 在交易执行开始前(发送方nonce递增后)处理授权列表: 对于每个[chain_id, address, nonce, y_parity, r, s]元组: 验证chain ID为0或当前链的ID 验证nonce小于2⁶⁴ - 1 使用ecrecover恢复授权地址 验证签名符合EIP-2标准 将授权地址添加到accessed_addresses 验证授权账户的代码为空或已委托 验证授权账户的nonce匹配 如果授权账户非空,添加gas退款 将授权账户的代码设置为0xef0100 || address(委托指示符) 如果address为全零地址,则清除账户代码 将授权账户的nonce加1 如果任何步骤失败,停止处理当前元组并继续下一个 ...
简介 此章节主要整理一些智能合约开发中的低级漏洞,适合初学者了解和防范。这些漏洞虽然"低级",但在实际项目中仍然频繁出现,可能导致严重的安全问题 不正确的 NatSpec 注释 什么是 NatSpec NatSpec(Ethereum Natural Language Specification Format)是 Solidity 的文档格式标准,用于生成用户文档和开发者文档 常见问题 // ❌ 现实审计中高频出现:transferFrom 注释完全错误 /// @title 代币转账函数 /// @notice 将代币从接收者地址转给发送者地址 ← ❌ 完全颠倒 /// @dev 使用用户的授权额度进行转账 /// @param from 接收代币的人 ← ❌ 注释与代码逻辑相反 /// @param to 发送代币的人 ← ❌ 注释与代码逻辑相反 /// @param amount 转账数量 function transferFrom(address from, address to, uint256 amount) external returns (bool); // ✅ 正确的 NatSpec 注释 /// @title 授权转账函数 /// @notice 从 `from` 账户扣除代币并转入 `to` 账户 /// @dev 需要调用者拥有足够的授权额度 /// @param from 代币来源账户(被扣款方) /// @param to 代币接收账户 /// @param amount 转账代币数量 /// @return success 转账是否成功 function transferFrom(address from, address to, uint256 amount) external returns (bool success); NatSpec 标签说明 @title:合约或函数的标题 @author:作者信息 @notice:给最终用户看的说明 @dev:给开发者看的详细说明 @param:参数说明 @return:返回值说明 @inheritdoc:继承父合约的文档 将隐私信息存于链上 问题描述 开发者误以为将变量声明为 private 就能保护数据隐私。 ...
EtherScan Etherscan 是以太坊区块链浏览器,用于查询交易、合约和账户信息。 在审计过程中可用于: 检查合约部署地址及交易历史 验证合约源代码与链上字节码一致性 查询 ERC-20/ERC-721 代币持有人和转账记录 cloc 统计代码行数的工具,可用于: 了解代码规模 比较不同版本的代码变化 支持多种语言,包括 Solidity notion 可用于汇总和管理审计过程中的资料、笔记和报告: 可以建立审计任务列表 追踪漏洞修复进度 整合团队协作 Documentation ( 需求方提供 ) 审计文档部分通常包含以下内容: Roles:合约中涉及的角色定义,例如 Owner、Admin、User 等 Scope:审计范围,包括智能合约模块、外部依赖、第三方库 Commit Hash (github repository):记录审计所使用的代码版本 Known Issues:已知问题或潜在风险列表 Solidity Metrics ( vscode 扩展 ) Solidity 代码度量工具,用于评估合约复杂度和安全性 Note.md ( 自己撰写 ) 审计笔记记录,可包含: 潜在漏洞说明 安全优化建议 审计过程中发现的异常行为 链上交互测试结果
简介 在识别代码库中的漏洞时,评估其潜在影响与风险至关重要。漏洞(finding)通常分为三类: High(高危) Medium(中危) Low(低危) 如何评估漏洞严重性 漏洞的严重性可被归类为 High(高)、Medium(中) 或 Low(低),其判定基于多个因素: 对协议的影响(Impact):如果漏洞被利用,会造成多严重的损害? 被利用的可能性(Likelihood):攻击者利用该漏洞的概率有多大? 评审/协议主观性程度(Degree of judge/protocol subjectivity) 下面用矩阵给出一个结构化参考: 可能性 \ 影响 高 (High) 中 (Medium) 低 (Low) 高 (High) 🔴 High 🟠 High / Medium 🟡 Medium 中 (Medium) 🟠 High / Medium 🟡 Medium 🟢 Medium / Low 低 (Low) 🟡 Medium 🟢 Medium / Low 🔵 Low 分类的主观性 虽然“影响 × 可能性”矩阵提供了结构化方法,但分类仍然带有一定主观性,判断者(judge)的裁量权在最终分类中起关键作用 如果被审计的协议有明确的判定标准,则应以协议规定为判定基准 如何评估漏洞的影响(Impact) Impact 指漏洞被利用后对用户或协议造成的潜在损害或后果 High(高影响): 资金直接或几乎直接处于风险中。 协议功能或可用性发生严重中断。 Medium(中影响): ...
简介 静态分析工具是智能合约安全审计的重要辅助手段,能够在代码执行前自动检测潜在的安全漏洞、编码问题和最佳实践违规。这些工具通过分析源代码或字节码的模式来识别风险,极大提高了审计效率和漏洞发现率 主要静态分析工具对比 工具名称 语言支持 分析类型 特点 适用场景 Slither Solidity 源代码分析 快速、检测种类多、可定制规则 常规安全审计、开发中检查 Mythril EVM字节码 符号执行 深度分析、路径覆盖广 复杂逻辑漏洞、重入攻击检测 Aderyn Solidity 源代码分析 专注于Rust实现、速度快 Rust开发者、性能敏感场景 Semgrep 多语言 模式匹配 简单规则、学习曲线低 团队编码规范检查 Slither 概述 Slither 是由 Trail of Bits 开发的 Solidity 静态分析框架,速度快、检测能力强,支持自定义检测规则。 安装 python3 -m pip install slither-analyzer 升级 python3 -m pip install --upgrade slither-analyzer 基本使用 扫描当前目录 slither . 扫描特定文件 slither tests/uninitialized.sol 检测器选择 Slither 默认会运行 所有内置检测器 指定检测器 slither file.sol --detect arbitrary-send,pragma 排除指定检测器 slither file.sol --exclude naming-convention,unused-state,suicidal 排除全部 informational / low 级别的检测器 slither file.sol --exclude-informational slither file.sol --exclude-low 查看所有可用检测器 slither --list-detectors 打印器 默认情况下,所有 printers 都不会运行 ...
摘要 委托代理合约被广泛用于可升级性和节省 Gas。这些代理合约依赖于一个逻辑合约(也称为实现合约或主拷贝),通过 delegatecall 进行调用。这使得代理可以保持持久状态(存储和余额),同时将代码执行委托给逻辑合约 为避免代理和逻辑合约之间的存储使用冲突,逻辑合约的地址通常保存在一个特定的存储槽中(例如,OpenZeppelin 合约中的 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc),该槽位保证永远不会被编译器分配。本 EIP 提议一组标准存储槽来保存代理信息。这使得区块浏览器等客户端能够正确提取并向最终用户展示此信息,同时也允许逻辑合约据此执行操作 动机 委托代理被广泛使用,作为支持升级和降低部署 Gas 成本的手段。这类代理的例子可见于 OpenZeppelin Contracts、Gnosis、AragonOS、Melonport、Limechain、WindingTree、Decentraland 等众多项目 一个典型的例子是区块浏览器。最终用户希望与底层逻辑合约交互,而不是代理本身。拥有从代理检索逻辑合约地址的通用方法,允许区块浏览器展示逻辑合约的 ABI 而非代理的 ABI。浏览器通过检查合约在特定存储槽的内容来判断它是否是一个代理,如果是,则同时展示代理和逻辑合约的信息。例如,Etherscan 上对地址 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 的展示就是如此 另一个例子是那些明确知晓自身正被代理的逻辑合约。这使得它们可以基于此事实,在其逻辑中触发代码更新。一个通用的存储槽使得这些用例可以独立于所使用的具体代理实现 规范 对代理的监控对许多应用的安全至关重要。因此,必须能够跟踪实现槽和管理员槽的变更。遗憾的是,跟踪存储槽的变更并不容易。因此,建议任何改变这些槽位的函数都应当 同时发出相应的事件。这包括从 0x0 到第一个非零值的初始化 以下是提议的用于存储代理特定信息的存储槽。后续可以根据需要通过其他 ERC 添加更多槽位 逻辑合约地址 存储槽:0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc(通过 bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) 计算获得) 保存此代理委托调用的逻辑合约地址。如果使用信标,则此槽应当为空。此槽的变更应当通过以下事件通知: event Upgraded(address indexed implementation); 信标合约地址 存储槽:0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50(通过 bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) 计算获得) 保存此代理所依赖的信标合约地址(作为后备)。如果直接使用逻辑地址,则此槽应当为空,并且仅在逻辑合约地址槽为空时才应考虑。此槽的变更应当通过以下事件通知: event BeaconUpgraded(address indexed beacon); 信标用于在单一位置维护多个代理的逻辑地址,允许通过修改单个存储槽来升级多个代理。信标合约必须实现以下函数: function implementation() returns (address) 基于信标的代理合约不使用逻辑合约地址槽。相反,它们使用信标合约地址槽来存储它们所连接的信标地址。为了了解信标代理所使用的逻辑合约,客户端应当: 从信标逻辑存储槽读取信标地址 调用该信标合约的 implementation() 函数 信标合约上 implementation() 函数的结果不应依赖于调用者 (msg.sender) 管理员地址 存储槽:0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103(通过 bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) 计算获得) 保存允许为此代理升级逻辑合约地址的地址(可选)。此槽的变更应当通过以下事件通知: event AdminChanged(address previousAdmin, address newAdmin); 原理阐述 本 EIP 标准化了逻辑合约地址的存储槽,而不是在代理合约上提供公开方法。其原理在于,代理永远不应该向最终用户暴露任何可能与逻辑合约函数发生冲突的函数 ...

摘要 引入 UserOperation:一种高层“伪交易对象”,替代直接在共识层提交交易 用户将 UserOperation 发送到单独的 mempool Bundler 将多个 UserOperation 打包,通过 handleOps 调用 EntryPoint 合约,再打包进区块 目标是在不修改以太坊底层协议的情况下,实现账户抽象 动机 实现账户抽象 允许使用智能合约账户作为主账户,支持任意验证逻辑。 用户无需拥有外部账户(EOA)。 去中心化与开放参与 任何 bundler 都可参与交易打包。 依赖公共 mempool,无需用户直接知晓 bundler 地址。 不增加信任假设。 无需共识层修改 避免协议层改动,提高快速部署的可能性。 支持更多应用场景 隐私保护型应用 原子多操作 使用 ERC-20 代币支付手续费或开发者代付 灵活的验证逻辑(不同签名方案、多签、恢复方案) 灵活的手续费支付(第三方支付、跨链支付) 批量操作与打包执行 总结:该提案通过 UserOperation、Bundler 和 EntryPoint 合约实现智能合约账户的完全抽象化,支持多签、代付和批量操作,同时无需修改以太坊共识层 规范 术语定义 术语 定义 UserOperation 一种描述“代表用户发起一笔交易”的数据结构。为避免与原生交易混淆,特意不叫“transaction”。像交易一样,它包含 to、calldata、maxFeePerGas、maxPriorityFeePerGas、nonce、signature。不像交易,它包含以下所述的几个其他字段。特别注意,signature 字段的使用不由协议定义,而是由智能合约账户实现定义 Sender 发起 UserOperation 的智能合约账户 EntryPoint 用于执行 UserOperation 批次的单例合约。Bundler 应白名单支持的 EntryPoint Bundler 一个节点(区块构建者),它能处理 UserOperation、创建有效的 entryPoint.handleOps() 交易,并在交易仍有效时将其添加到区块中。这可以通过多种方式实现:Bundler 可以自己充当区块构建者。如果 bundler 不是区块构建者,它应通过 mev-boost 等基础设施或其他提议者-构建者分离机制与区块构建者合作 Paymaster 同意代替 sender 支付交易费用的辅助合约 Factory 必要时负责部署新 sender 合约的辅助合约 Aggregator 也称为“授权合约” - 一个允许多个 UserOperation 共享一次验证的合约。此类合约的完整设计超出本提案范围 Canonical UserOperation mempool 一个去中心化、无许可的 P2P 网络,bundler 在其中交换符合同一套共享验证规则的合法 UserOperation。具体规则的完整规范超出本提案范围 Alternative UserOperation mempool 任何其他 P2P 内存池,其中 UserOperation 的有效性由不同于共享规则的规则决定,这些规则以任何方式应用于验证代码 Deposit Sender 或 Paymaster 合约向 EntryPoint 合约转入的一笔以太币(或任何 L2 原生货币),用于支付未来 UserOperation 的 gas 费用 UserOperation 结构定义 字段 类型 描述 sender address 发起 UserOperation 的账户 nonce uint256 防重放参数(详见“半抽象 Nonce 支持”) factory address 新账户的账户工厂 OR 0x7702 标志用于 EIP-7702 账户,否则 address(0) factoryData bytes 如果提供 factory,则为账户工厂的数据 OR EIP-7702 初始化数据,或空数组 callData bytes 在主要执行调用期间传递给 sender 的数据 callGasLimit uint256 为主要执行调用分配的 gas 量 verificationGasLimit uint256 为验证步骤分配的 gas 量 preVerificationGas uint256 额外 gas 用于支付 bundler maxFeePerGas uint256 最大 gas 费(类似于 EIP-1559 max_fee_per_gas) maxPriorityFeePerGas uint256 最大优先费(类似于 EIP-1559 max_priority_fee_per_gas) paymaster address paymaster 合约地址(或空,如果 sender 自己支付 gas) paymasterVerificationGasLimit uint256 为 paymaster 验证代码分配的 gas 量(仅当 paymaster 存在时) paymasterPostOpGasLimit uint256 为 paymaster 后操作代码分配的 gas 量(仅当 paymaster 存在时) paymasterData bytes paymaster 的数据(仅当 paymaster 存在时) signature bytes 传入 sender 以验证授权的数据 上链时部分字段会被打包,转变成 PackedUserOperation ...