Solidity安全性Skill solidity-security

这个技能专注于Solidity智能合约的安全最佳实践,包括防止重入攻击、整数溢出/下溢、访问控制等常见漏洞,并实施安全的开发模式。适用于编写智能合约、审计现有合约、实施安全措施,支持DeFi、Web3等区块链应用。关键词:Solidity,智能合约,安全,漏洞预防,DeFi,区块链,重入防护,燃气优化。

智能合约 0 次安装 0 次浏览 更新于 3/16/2026

名称: solidity-security 描述: 掌握智能合约安全最佳实践,以预防常见漏洞并实施安全的Solidity模式。适用于编写智能合约、审计现有合约或为区块链应用实施安全措施。

Solidity安全性

掌握智能合约安全最佳实践、漏洞预防和安全的Solidity开发模式。

何时使用此技能

  • 编写安全的智能合约
  • 审计现有合约的漏洞
  • 实施安全的DeFi协议
  • 预防重入、溢出和访问控制问题
  • 优化燃气使用,同时保持安全性
  • 为专业审计准备合约
  • 理解常见攻击向量

关键漏洞

1. 重入

攻击者在状态更新前回调到您的合约。

易受攻击的代码:

// 易受重入攻击
contract VulnerableBank {
    mapping(address => uint256) public balances;

    function withdraw() public {
        uint256 amount = balances[msg.sender];

        // 危险:在状态更新前进行外部调用
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success);

        balances[msg.sender] = 0;  // 太晚了!
    }
}

安全模式(检查-效果-交互):

contract SecureBank {
    mapping(address => uint256) public balances;

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "余额不足");

        // 效果:在外部调用前更新状态
        balances[msg.sender] = 0;

        // 交互:最后进行外部调用
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "转账失败");
    }
}

替代方案:重入防护

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, "余额不足");

        balances[msg.sender] = 0;

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "转账失败");
    }
}

2. 整数溢出/下溢

易受攻击的代码(Solidity < 0.8.0):

// 易受攻击
contract VulnerableToken {
    mapping(address => uint256) public balances;

    function transfer(address to, uint256 amount) public {
        // 无溢出检查 - 可能环绕
        balances[msg.sender] -= amount;  // 可能下溢!
        balances[to] += amount;          // 可能溢出!
    }
}

安全模式(Solidity >= 0.8.0):

// Solidity 0.8+ 具有内置溢出/下溢检查
contract SecureToken {
    mapping(address => uint256) public balances;

    function transfer(address to, uint256 amount) public {
        // 自动在溢出/下溢时回滚
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

对于 Solidity < 0.8.0,使用 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);
    }
}

3. 访问控制

易受攻击的代码:

// 易受攻击:任何人都可以调用关键函数
contract VulnerableContract {
    address public owner;

    function withdraw(uint256 amount) public {
        // 无访问控制!
        payable(msg.sender).transfer(amount);
    }
}

安全模式:

import "@openzeppelin/contracts/access/Ownable.sol";

contract SecureContract is Ownable {
    function withdraw(uint256 amount) public onlyOwner {
        payable(owner()).transfer(amount);
    }
}

// 或实现自定义基于角色的访问
contract RoleBasedContract {
    mapping(address => bool) public admins;

    modifier onlyAdmin() {
        require(admins[msg.sender], "不是管理员");
        _;
    }

    function criticalFunction() public onlyAdmin {
        // 受保护的函数
    }
}

4. 抢先交易

易受攻击:

// 易受抢先交易攻击
contract VulnerableDEX {
    function swap(uint256 amount, uint256 minOutput) public {
        // 攻击者在内存池中看到此交易并抢先交易
        uint256 output = calculateOutput(amount);
        require(output >= minOutput, "滑点太高");
        // 执行交换
    }
}

缓解措施:

contract SecureDEX {
    mapping(bytes32 => bool) public usedCommitments;

    // 步骤 1:提交交易
    function commitTrade(bytes32 commitment) public {
        usedCommitments[commitment] = true;
    }

    // 步骤 2:揭示交易(下一个区块)
    function revealTrade(
        uint256 amount,
        uint256 minOutput,
        bytes32 secret
    ) public {
        bytes32 commitment = keccak256(abi.encodePacked(
            msg.sender, amount, minOutput, secret
        ));
        require(usedCommitments[commitment], "无效提交");
        // 执行交换
    }
}

安全最佳实践

检查-效果-交互模式

contract SecurePattern {
    mapping(address => uint256) public balances;

    function withdraw(uint256 amount) public {
        // 1. 检查:验证条件
        require(amount <= balances[msg.sender], "余额不足");
        require(amount > 0, "金额必须为正");

        // 2. 效果:更新状态
        balances[msg.sender] -= amount;

        // 3. 交互:外部调用最后
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "转账失败");
    }
}

拉取优于推送模式

// 偏好此(拉取)
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, "无提款");

        pendingWithdrawals[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
    }
}

// 与此相比(推送)
contract RiskyPayment {
    function distributePayments(address[] memory recipients, uint256[] memory amounts) public {
        for (uint i = 0; i < recipients.length; i++) {
            // 如果任何转账失败,整个批次失败
            payable(recipients[i]).transfer(amounts[i]);
        }
    }
}

输入验证

contract SecureContract {
    function transfer(address to, uint256 amount) public {
        // 验证输入
        require(to != address(0), "无效收件人");
        require(to != address(this), "不能发送给合约");
        require(amount > 0, "金额必须为正");
        require(amount <= balances[msg.sender], "余额不足");

        // 执行转账
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

紧急停止(断路器)

import "@openzeppelin/contracts/security/Pausable.sol";

contract EmergencyStop is Pausable, Ownable {
    function criticalFunction() public whenNotPaused {
        // 函数逻辑
    }

    function emergencyStop() public onlyOwner {
        _pause();
    }

    function resume() public onlyOwner {
        _unpause();
    }
}

燃气优化

使用 uint256 代替较小类型

// 更燃气高效
contract GasEfficient {
    uint256 public value;  // 最优

    function set(uint256 _value) public {
        value = _value;
    }
}

// 较低效
contract GasInefficient {
    uint8 public value;  // 仍使用256位槽

    function set(uint8 _value) public {
        value = _value;  // 额外燃气用于类型转换
    }
}

打包存储变量

// 燃气高效(3个变量在1个槽中)
contract PackedStorage {
    uint128 public a;  // 槽 0
    uint64 public b;   // 槽 0
    uint64 public c;   // 槽 0
    uint256 public d;  // 槽 1
}

// 燃气低效(每个变量在单独槽中)
contract UnpackedStorage {
    uint256 public a;  // 槽 0
    uint256 public b;  // 槽 1
    uint256 public c;  // 槽 2
    uint256 public d;  // 槽 3
}

使用 calldata 代替 memory 用于函数参数

contract GasOptimized {
    // 更燃气高效
    function processData(uint256[] calldata data) public pure returns (uint256) {
        return data[0];
    }

    // 较低效
    function processDataMemory(uint256[] memory data) public pure returns (uint256) {
        return data[0];
    }
}

使用事件进行数据存储(当适用时)

contract EventStorage {
    // 发出事件比存储更便宜
    event DataStored(address indexed user, uint256 indexed id, bytes data);

    function storeData(uint256 id, bytes calldata data) public {
        emit DataStored(msg.sender, id, data);
        // 除非需要,不存储在合约存储中
    }
}

常见漏洞清单

// 安全检查清单合约
contract SecurityChecklist {
    /**
     * [ ] 重入防护(ReentrancyGuard 或 CEI 模式)
     * [ ] 整数溢出/下溢(Solidity 0.8+ 或 SafeMath)
     * [ ] 访问控制(Ownable、角色、修饰符)
     * [ ] 输入验证(require 语句)
     * [ ] 抢先交易缓解(如果适用,提交-揭示)
     * [ ] 燃气优化(打包存储、calldata)
     * [ ] 紧急停止机制(Pausable)
     * [ ] 拉取优于推送模式用于支付
     * [ ] 不对不受信任的合约进行委托调用
     * [ ] 不使用 tx.origin 进行身份验证(使用 msg.sender)
     * [ ] 正确发出事件
     * [ ] 函数结束时进行外部调用
     * [ ] 检查外部调用的返回值
     * [ ] 无硬编码地址
     * [ ] 升级机制(如果使用代理模式)
     */
}

安全测试

// Hardhat 测试示例
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("安全测试", function () {
    it("应防止重入攻击", 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);

        // 存入资金
        await bank.deposit({value: ethers.utils.parseEther("10")});

        // 尝试重入攻击
        await expect(
            attackerContract.attack({value: ethers.utils.parseEther("1")})
        ).to.be.revertedWith("ReentrancyGuard: reentrant call");
    });

    it("应防止整数溢出", async function () {
        const Token = await ethers.getContractFactory("SecureToken");
        const token = await Token.deploy();

        // 尝试溢出
        await expect(
            token.transfer(attacker.address, ethers.constants.MaxUint256)
        ).to.be.reverted;
    });

    it("应强制执行访问控制", async function () {
        const [owner, attacker] = await ethers.getSigners();

        const Contract = await ethers.getContractFactory("SecureContract");
        const contract = await Contract.deploy();

        // 尝试未经授权的提款
        await expect(
            contract.connect(attacker).withdraw(100)
        ).to.be.revertedWith("Ownable: caller is not the owner");
    });
});

审计准备

contract WellDocumentedContract {
    /**
     * @title 良好文档合约
     * @dev 审计的适当文档示例
     * @notice 此合约处理用户存款和提款
     */

    /// @notice 用户余额映射
    mapping(address => uint256) public balances;

    /**
     * @dev 将ETH存入合约
     * @notice 任何人都可以存入资金
     */
    function deposit() public payable {
        require(msg.value > 0, "必须发送ETH");
        balances[msg.sender] += msg.value;
    }

    /**
     * @dev 提款用户的余额
     * @notice 遵循CEI模式以防止重入
     * @param amount 以wei为单位的提款金额
     */
    function withdraw(uint256 amount) public {
        // 检查
        require(amount <= balances[msg.sender], "余额不足");

        // 效果
        balances[msg.sender] -= amount;

        // 交互
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "转账失败");
    }
}

资源

  • references/reentrancy.md:全面重入预防
  • references/access-control.md:基于角色的访问模式
  • references/overflow-underflow.md:SafeMath 和整数安全
  • references/gas-optimization.md:燃气节省技术
  • references/vulnerability-patterns.md:常见漏洞目录
  • assets/solidity-contracts-templates.sol:安全合约模板
  • assets/security-checklist.md:审计前清单
  • scripts/analyze-contract.sh:静态分析工具

安全分析工具

  • Slither:静态分析工具
  • Mythril:安全分析工具
  • Echidna:模糊测试工具
  • Manticore:符号执行工具
  • Securify:自动化安全扫描器

常见陷阱

  1. 使用 tx.origin 进行身份验证:改用 msg.sender
  2. 未检查的外部调用:始终检查返回值
  3. 对不受信任合约的委托调用:可能劫持您的合约
  4. 浮动版本:固定到特定 Solidity 版本
  5. 缺少事件:为状态变更发出事件
  6. 循环中燃气过多:可能达到区块燃气限制
  7. 无升级路径:如果需要升级,考虑代理模式