ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Solidity 智能合约安全实战指南:基于 agents 市场 solidity-security 技能的重入、溢出、访问控制与审计模式全解

Solidity 智能合约安全实战指南:基于 agents 市场 solidity-security 技能的重入、溢出、访问控制与审计模式全解 Solidity 智能合约安全实战指南基于 agents 市场 solidity-security 技能的重入、溢出、访问控制与审计模式全解【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents本文以当前开源仓库中blockchain-web3插件的solidity-security技能为蓝本系统讲解智能合约最常见的高危漏洞及其安全防御模式覆盖 Checks-Effects-InteractionsCEI、Pull Over Push、Emergency Stop、Gas 优化与 Hardhat 安全回归测试等完整实战方案。读完本文你将掌握一套可直接照抄、可运行、可复用的脆弱代码 → 安全代码对照模板并能独立完成合约安全审计前的自检与面向专业审计的代码整理。本文核心素材来自 SKILL.md 及其渐进式披露的深层文档 references/details.md两者共同构成了该技能的两层内容外层导航层提供何时使用 安全测试 审计准备内层 details 层提供四大高危漏洞的脆弱/安全对照代码、安全最佳实践与 Gas 优化模式。一、技能文档概览solidity-security 能做什么该技能位于仓库blockchain-web3插件下其 SKILL.md 的 frontmatter 这样定义自己的用途Master smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. Use when writing smart contracts, auditing existing contracts, or implementing security measures for blockchain applications.即在编写智能合约、审计既有合约、为区块链应用实施安全防护时启用。其能力覆盖编写安全的智能合约审计既有合约中的漏洞实现安全的 DeFi 协议预防重入reentrancy、溢出overflow、访问控制access control问题在保持安全的前提下优化 Gas为专业审计做准备理解常见攻击向量。这与仓库中同插件的 blockchain-developer.md Agent 的定位相互印证——该 Agent 明确将智能合约安全审计重入、溢出、访问控制漏洞Gas 优化技术与合约体积最小化使用 Certora、Slither、Mythril 等工具的正式验证列为能力项。两者配合使用Agent 负责整体开发决策solidity-security 技能提供具体漏洞模式与代码模板。二、在 agents 仓库中的定位与使用方式这是一个Multi-harness agentic plugin marketplace多运行环境智能体插件市场按 docs/plugins.md 的说明整个市场包含 94 个插件每个插件按agents/、commands/、skills/三类组件组织。blockchain-web3插件属于 Blockchain分类安装命令为/plugin install blockchain-web3安装后只会把该插件自己的 Agent、命令与技能加载进上下文。solidity-security是插件下四个技能之一目录结构为plugins/blockchain-web3/ ├── agents/ │ └── blockchain-developer.md # 该领域的主 Agent └── skills/ ├── defi-protocol-templates/ # DeFi 协议模板Staking、AMM 等 ├── nft-standards/ # NFT 标准 ├── solidity-security/ # ← 本文主角 │ ├── SKILL.md # 导航层何时用、安全测试、审计准备 │ └── references/details.md # 细节层漏洞对照代码与全部模式 └── web3-testing/ # Web3 合约测试Hardhat/Foundry技能采用渐进式披露结构Agent 或 LLM 先在导航层 SKILL.md 判断任务匹配度当导航层信息不足时再读取深层的 references/details.md。因此本文后续将按同样的两层顺序展开确保不漏掉任何一处可执行的细节。三、四大高危漏洞与安全模式对照references/details.md 将最关键的知识浓缩为脆弱代码 → 安全代码的直接对照。下面逐一拆解。3.1 重入攻击Reentrancy原理攻击者在合约状态更新之前通过外部调用回调进入你的合约从而重复提取资金。它的根因是先做外部交互、后改状态的错误顺序。脆弱代码balances[msg.sender] 0发生在外部调用之后形同虚设// VULNERABLE TO REENTRANCY contract VulnerableBank { mapping(address uint256) public balances; function withdraw() public { uint256 amount balances[msg.sender]; // DANGER: External call before state update (bool success, ) msg.sender.call{value: amount}(); require(success); balances[msg.sender] 0; // Too late! } }攻击者合约的 fallback 会在call触发时再次进入withdraw而此时余额尚未清零可以无限次重复取款——这正是历史上著名智能合约攻击如 2016 年 The DAO 事件采用的核心手法也是每一轮 DeFi 审计中最优先检查的项。安全模式一Checks-Effects-Interactions先检查、再改状态、最后外部交互contract SecureBank { mapping(address uint256) public balances; function withdraw() public { uint256 amount balances[msg.sender]; require(amount 0, Insufficient balance); // EFFECTS: Update state BEFORE external call balances[msg.sender] 0; // INTERACTIONS: External call last (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }关键差异只有一行顺序先在 EFFECTS 阶段把余额清零攻击者重入时读到的余额已经是 0require(amount 0)会直接拒绝第二轮调用。安全模式二ReentrancyGuard 互斥锁多函数间交叉重入时的兜底import openzeppelin/contracts/security/ReentrancyGuard.sol; contract SecureBank is ReentrancyGuard { mapping(address uint256) public balances; function withdraw() public nonReentrant { uint256 amount balances[msg.sender]; require(amount 0, Insufficient balance); balances[msg.sender] 0; (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }nonReentrant修饰符相当于一把一次性互斥锁同一事务内禁止嵌套调用被它保护的函数。实践建议CEI 是纪律、Guard 是保险——只要对外部调用不够确定就把两者叠加。注意 OpenZeppelin 的导入路径会随版本调整v4 位于security/更新版本中可能移动请以你工程内实际安装的版本为准。3.2 整数溢出 / 下溢Integer Overflow / Underflow原理无符号整数减法可以回绕成一个巨大数字加法可以溢出为 0从而绕过余额检查或凭空增发代币。脆弱代码Solidity 0.8.0// VULNERABLE contract VulnerableToken { mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { // No overflow check - can wrap around balances[msg.sender] - amount; // Can underflow! balances[to] amount; // Can overflow! } }安全模式一Solidity 0.8.0编译器内置了算术检查溢出或下溢会自动 revert无需额外代码// Solidity 0.8 has built-in overflow/underflow checks contract SecureToken { mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { // Automatically reverts on overflow/underflow balances[msg.sender] - amount; balances[to] amount; } }安全模式二Solidity 0.8.0使用 OpenZeppelin SafeMath 显式检查import openzeppelin/contracts/utils/math/SafeMath.sol; contract SecureToken { using SafeMath for uint256; mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { balances[msg.sender] balances[msg.sender].sub(amount); balances[to] balances[to].add(amount); } }这里的技术要点是版本分水岭 0.8.0低于此版本必须显式启用 SafeMath或unchecked之外的检查0.8.0 及以上则依赖内置检查。审计存量合约时第一件事就是确认pragma solidity版本及其是否全面启用了安全算术。3.3 访问控制缺失Access Control原理关键函数提款、铸造、升级、暂停没有权限校验任何地址都可以调用。脆弱代码withdraw无任何限制任何人都可以把合约余额取走// VULNERABLE: Anyone can call critical functions contract VulnerableContract { address public owner; function withdraw(uint256 amount) public { // No access control! payable(msg.sender).transfer(amount); } }安全模式一继承 OpenZeppelin Ownableimport openzeppelin/contracts/access/Ownable.sol; contract SecureContract is Ownable { function withdraw(uint256 amount) public onlyOwner { payable(owner()).transfer(amount); } }安全模式二自定义角色映射 修饰符适用于多管理员、分层权限场景// Or implement custom role-based access contract RoleBasedContract { mapping(address bool) public admins; modifier onlyAdmin() { require(admins[msg.sender], Not an admin); _; } function criticalFunction() public onlyAdmin { // Protected function } }审计要点在 references/details.md 给出的常见漏洞自查清单里访问控制与不把tx.origin用于认证应使用msg.sender是并列的两条独立检查项——前者控制谁能调用后者防止钓鱼合约借tx.origin冒充用户。3.4 抢跑攻击Front-Running原理交易提交后先进入公开的内存池mempool等待打包矿工或机器人可以观察到你的交易参数抢先提交一笔相同参数的交易赚取差价经典场景是 DEX 大额兑换被抢先吃走滑点。脆弱代码swap的minOutput与金额全部公开可见// VULNERABLE TO FRONT-RUNNING contract VulnerableDEX { function swap(uint256 amount, uint256 minOutput) public { // Attacker sees this in mempool and front-runs uint256 output calculateOutput(amount); require(output minOutput, Slippage too high); // Perform swap } }缓解手段Commit-Reveal先提交承诺、后揭示contract SecureDEX { mapping(bytes32 bool) public usedCommitments; // Step 1: Commit to trade function commitTrade(bytes32 commitment) public { usedCommitments[commitment] true; } // Step 2: Reveal trade (next block) function revealTrade( uint256 amount, uint256 minOutput, bytes32 secret ) public { bytes32 commitment keccak256(abi.encodePacked( msg.sender, amount, minOutput, secret )); require(usedCommitments[commitment], Invalid commitment); // Perform swap } }承诺阶段只上链一个keccak256哈希攻击者无法在揭示前知道真实参数揭示阶段用msg.sender amount minOutput secret重新计算哈希并与承诺比对从而锁定这个交易只能由承诺者本人按原参数执行。usedCommitments映射同时保证每个承诺只能被消费一次。四、安全最佳实践模式库在上述四大漏洞之上references/details.md 还沉淀了一组更高层、可组合的设计模式。4.1 Checks-Effects-InteractionsCEI完整范式任何涉及外部调用的函数都应遵循三段式且三段顺序不可调换contract SecurePattern { mapping(address uint256) public balances; function withdraw(uint256 amount) public { // 1. CHECKS: Validate conditions require(amount balances[msg.sender], Insufficient balance); require(amount 0, Amount must be positive); // 2. EFFECTS: Update state balances[msg.sender] - amount; // 3. INTERACTIONS: External calls last (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }CHECKS 负责把所有require前置EFFECTS 在外部交互前完成全部状态写入INTERACTIONS 放在最后并对call的返回值做require(success)检查——检查外部调用返回值也是漏洞自查清单中的独立条目。这样即便外部调用被恶意重入状态早已不可再次利用。4.2 Pull Over Push拉取优于推送资金分发场景中让每个收款人自己来取pull远优于合约逐个向外转账push// Prefer this (pull) contract SecurePayment { mapping(address uint256) public pendingWithdrawals; function recordPayment(address recipient, uint256 amount) internal { pendingWithdrawals[recipient] amount; } function withdraw() public { uint256 amount pendingWithdrawals[msg.sender]; require(amount 0, Nothing to withdraw); pendingWithdrawals[msg.sender] 0; payable(msg.sender).transfer(amount); } } // Over this (push) contract RiskyPayment { function distributePayments(address[] memory recipients, uint256[] memory amounts) public { for (uint i 0; i recipients.length; i) { // If any transfer fails, entire batch fails payable(recipients[i]).transfer(amounts[i]); } } }Push 模式的致命缺陷写在注释里只要recipients数组中有一个地址是拒绝接收 ETH 的合约或 gas 不足整个循环 revert全部用户资金被卡死。Pull 模式把失败风险转移给单个收款人同时天然契合 CEI先记待取账、再清零、后转账。凡是批量发钱的逻辑都应当改写成记账式拉取。4.3 输入验证Input Validation把边界检查全部前置到函数入口用require拒绝非法输入contract SecureContract { function transfer(address to, uint256 amount) public { // Validate inputs require(to ! address(0), Invalid recipient); require(to ! address(this), Cannot send to contract); require(amount 0, Amount must be positive); require(amount balances[msg.sender], Insufficient balance); // Proceed with transfer balances[msg.sender] - amount; balances[to] amount; } }四个require分别拦掉了零地址转账、把资金转给自己合约常见于误操作/陷阱、零金额交易、超余额交易。同类插件 web3-testing/SKILL.md 中的 Foundry 测试也专门用vm.expectRevert(Invalid recipient)验证转账到零地址必须失败与这里的错误信息完全对齐说明输入验证是与测试联动设计的。4.4 应急熔断Emergency Stop / Circuit Breaker发现漏洞时第一时间冻结关键功能为修复争取时间import openzeppelin/contracts/security/Pausable.sol; contract EmergencyStop is Pausable, Ownable { function criticalFunction() public whenNotPaused { // Function logic } function emergencyStop() public onlyOwner { _pause(); } function resume() public onlyOwner { _unpause(); } }继承Pausable后所有加了whenNotPaused的函数在熔断期间一律 revertemergencyStop/resume只对owner开放。把熔断权限与多签/时间锁timelock结合是专业 DeFi 协议的标准配置。这一模式也呼应了 blockchain-developer.md 中优先使用久经考验的库与既有模式的行为准则。五、安全前提下的 Gas 优化安全与效率不是对立的——details 文档明确将 Gas 优化列入安全技能references/details.md因为更高 Gas 消耗本身就是 DoS 攻击面。文档给出四条可直接套用的规则。5.1 用uint256而非更小类型EVM 的存储槽固定 256 位宽uint8并不会省存储反而可能引入额外的类型转换开销// More gas efficient contract GasEfficient { uint256 public value; // Optimal function set(uint256 _value) public { value _value; } } // Less efficient contract GasInefficient { uint8 public value; // Still uses 256-bit slot function set(uint8 _value) public { value _value; // Extra gas for type conversion } }单值场景直接用uint256更小类型只在需要把多个变量塞进同一存储槽见下一条时才有意义。5.2 打包存储变量Storage Packing把较小的类型相邻声明让多个变量共享一个 256 位存储槽写槽次数从 N 次降为 1 次// Gas efficient (3 variables in 1 slot) contract PackedStorage { uint128 public a; // Slot 0 uint64 public b; // Slot 0 uint64 public c; // Slot 0 uint256 public d; // Slot 1 } // Gas inefficient (each variable in separate slot) contract UnpackedStorage { uint256 public a; // Slot 0 uint256 public b; // Slot 1 uint256 public c; // Slot 2 uint256 public d; // Slot 3 }uint128 uint64 uint64 256恰好填满一个槽而四个uint256各占一个槽共 4 次 SSTORE。打包后仅需 2 次 SSTORE。这是所有存储密集型合约代币、账本、治理的首选优化。5.3 函数参数用calldata而非memory只读的外部入参声明为calldata可直接引用交易数据区避免先拷贝到内存contract GasOptimized { // More gas efficient function processData(uint256[] calldata data) public pure returns (uint256) { return data[0]; } // Less efficient function processDataMemory(uint256[] memory data) public pure returns (uint256) { return data[0]; } }5.4 适当用 Event 代替存储发事件的开销远低于写合约存储。对仅需可追溯、无需链上读取的数据用事件落账即可contract EventStorage { // Emitting events is cheaper than storage event DataStored(address indexed user, uint256 indexed id, bytes data); function storeData(uint256 id, bytes calldata data) public { emit DataStored(msg.sender, id, data); // Dont store in contract storage unless needed } }注意两个indexed参数索引字段可被链下索引器如 The Graph高效检索。在web3-testing技能的 Hardhat 示例中也有对应的应正确发出 Transfer 事件的断言模式expect(...).to.emit(token, Transfer)说明事件既是存储优化手段也是链下集成的公共接口规范发出事件同时是审计清单条目之一。六、用 Hardhat 把攻击写进测试安全回归测试实战SKILL.md 给出的核心可执行产出是一组把前三类漏洞当作回归测试用例的 Hardhat 测试。其思路是真正部署攻击者合约去打你的安全合约并断言攻击被 revert——这是证明防护有效的最直接证据。// Hardhat test example const { expect } require(chai); const { ethers } require(hardhat); describe(Security Tests, function () { it(Should prevent reentrancy attack, async function () { const [attacker] await ethers.getSigners(); const VictimBank await ethers.getContractFactory(SecureBank); const bank await VictimBank.deploy(); const Attacker await ethers.getContractFactory(ReentrancyAttacker); const attackerContract await Attacker.deploy(bank.address); // Deposit funds await bank.deposit({ value: ethers.utils.parseEther(10) }); // Attempt reentrancy attack await expect( attackerContract.attack({ value: ethers.utils.parseEther(1) }), ).to.be.revertedWith(ReentrancyGuard: reentrant call); }); it(Should prevent integer overflow, async function () { const Token await ethers.getContractFactory(SecureToken); const token await Token.deploy(); // Attempt overflow await expect(token.transfer(attacker.address, ethers.constants.MaxUint256)) .to.be.reverted; }); it(Should enforce access control, async function () { const [owner, attacker] await ethers.getSigners(); const Contract await ethers.getContractFactory(SecureContract); const contract await Contract.deploy(); // Attempt unauthorized withdrawal await expect(contract.connect(attacker).withdraw(100)).to.be.revertedWith( Ownable: caller is not the owner, ); }); });三个用例的价值在于断言信息与前面安全代码中的 revert 文案严格对应重入用例断言ReentrancyGuard: reentrant call对应 3.1 节SecureBank is ReentrancyGuard中nonReentrant的默认报错同时反向证明了攻击者合约确实尝试了重入溢出用例用ethers.constants.MaxUint256直接向目标转账断言 Solidity 0.8 内置检查会 revert只断言 revert、不关心具体文案因为内置检查文案在不同编译器版本间不统一访问控制用例用contract.connect(attacker)以非 owner 身份调用withdraw断言Ownable: caller is not the owner与 3.3 节SecureContract is OwnableonlyOwner一一呼应。这种红队测试驱动的写法把漏洞预防从代码审查靠肉眼升级为每次 CI 都真实重放一次攻击。若需要更全面的测试基建Foundry 模糊测试、主网 fork、账户模拟、Gas 报告可直接复用同插件 web3-testing/SKILL.md 中现成的hardhat.config.js含solidity-coverage、hardhat-gas-reporter、主网 fork 配置与 Forge 测试模板。七、面向专业审计的合约编写正式提交第三方审计前代码应达到可审计的文档标准。SKILL.md 提供了一个最佳实践范例WellDocumentedContract它同时示范了三件事合约级 NatSpec、函数级 NatSpec、以及用注释明确标注 CEI 各阶段contract WellDocumentedContract { /** * title Well Documented Contract * dev Example of proper documentation for audits * notice This contract handles user deposits and withdrawals */ /// notice Mapping of user balances mapping(address uint256) public balances; /** * dev Deposits ETH into the contract * notice Anyone can deposit funds */ function deposit() public payable { require(msg.value 0, Must send ETH); balances[msg.sender] msg.value; } /** * dev Withdraws users balance * notice Follows CEI pattern to prevent reentrancy * param amount Amount to withdraw in wei */ function withdraw(uint256 amount) public { // CHECKS require(amount balances[msg.sender], Insufficient balance); // EFFECTS balances[msg.sender] - amount; // INTERACTIONS (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }可审计代码的三个要领声明意图合约级用title/notice一句话说清这个合约管什么资金、做什么事函数级说明调用条件与副作用声明安全策略如notice Follows CEI pattern to prevent reentrancy——直接告诉审计员我在这里应用了哪个模式、防的是什么大幅降低漏审风险代码内留安全标注把CHECKS/EFFECTS/INTERACTIONS写成注释分区让审计流程可以逐区核对而不是在整段逻辑里找状态变更点。这也与 blockchain-developer.md 的 Response Approach 第 6 条记录合约行为并产出审计就绪的代码文档的要求吻合。八、上线前漏洞自查清单references/details.md 以一份清单合约的形式收束全部要点。虽然它不能编译执行但其注释本身就是上线前的逐项审查表// Security Checklist Contract contract SecurityChecklist { /** * [ ] Reentrancy protection (ReentrancyGuard or CEI pattern) * [ ] Integer overflow/underflow (Solidity 0.8 or SafeMath) * [ ] Access control (Ownable, roles, modifiers) * [ ] Input validation (require statements) * [ ] Front-running mitigation (commit-reveal if applicable) * [ ] Gas optimization (packed storage, calldata) * [ ] Emergency stop mechanism (Pausable) * [ ] Pull over push pattern for payments * [ ] No delegatecall to untrusted contracts * [ ] No tx.origin for authentication (use msg.sender) * [ ] Proper event emission * [ ] External calls at end of function * [ ] Check return values of external calls * [ ] No hardcoded addresses * [ ] Upgrade mechanism (if proxy pattern) */ }15 项清单可归并为四条主线方便记忆与执行状态一致性重入防护Guard 或 CEI、外部调用置于函数末尾并检查返回值——对应 3.1 与 4.1 节数值与身份0.8 或 SafeMath 防溢出只用msg.sender认证、绝不信任tx.origin函数入口全面require输入校验——对应 3.2、3.3、4.3 节抗操控与韧性对公开可抢跑的交易使用 commit-reveal资金用 Pull Over Push预留 Pausable 熔断不向不可信合约delegatecall——对应 3.4、4.2、4.4 节工程与审计存储打包 calldata控制成本事件规范发出地址不硬编码而走构造函数参数/配置若采用可升级代理则设计并审查升级机制。九、总结solidity-security 技能把智能合约安全从抽象原则压缩成了一套可直接对照修改的代码模板库四大高危漏洞重入、溢出、访问控制、抢跑各自给出脆弱版与安全版安全最佳实践CEI、Pull Over Push、输入验证、熔断提供可组合的设计范式Gas 优化四则保证安全与效率兼得Hardhat 攻击回归测试让防护可被持续验证审计准备范例与 15 项自查清单让合约达到专业审计门槛。全部代码均可从 SKILL.md 与 references/details.md 直接复制使用并与 blockchain-developer.md Agent、web3-testing/SKILL.md 测试技能形成写 → 测 → 审完整闭环。对于正在开发 DeFi、NFT 或任何托管用户资产的团队把本文的脆弱代码当作黑名单、把安全模式当作默认写法即是抵御绝大多数已知攻击向量最经济有效的起点。【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表