> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/blockchain_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/blockchain_guide/05_crypto/smart_contract_best_practices.md).

# 智能合约安全开发最佳实践

智能合约一旦部署就几乎无法修改，且往往控制着大量资金，因此需要比传统软件更高的安全标准。本节总结了业界公认的最佳实践，帮助开发者在整个生命周期中防止安全事故。

## 1. 设计阶段最佳实践

### 1.1 最小化复杂度原则

**核心理念**：代码越简单，越容易被审计和验证，漏洞越少。

**实践建议**：

* 单一职责：每个合约应该只负责一个核心功能，避免臃肿合约
* 模块化设计：将复杂逻辑分解为多个独立的小合约
* 避免过度优化：可读性和安全性比 Gas 优化更重要

**示例 - 不好的做法**：

```solidity
pragma solidity ^0.8.24;

// 一个合约承载了太多职责：转账、质押、治理、预言机等
contract Monolith {
    // 数百行代码，混杂各种逻辑
    function complexFunction() public {
        // 代码越长，越容易出现漏洞
    }
}
```

**示例 - 好的做法**：

```solidity
// 分离职责，使用接口定义清晰的边界
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IOracle {
    function getPrice(address token) external view returns (uint256);
}

contract TransferModule {
    using SafeERC20 for IERC20;

    IERC20 public immutable token;

    constructor(IERC20 token_) {
        token = token_;
    }

    function safeTransfer(address to, uint256 amount) external {
        token.safeTransfer(to, amount);
    }
}

contract StakingModule is ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable token;
    mapping(address => uint256) public stakes;

    constructor(IERC20 token_) {
        token = token_;
    }

    function stake(uint256 amount) external nonReentrant {
        require(amount > 0, "Zero amount");       // Checks
        stakes[msg.sender] += amount;             // Effects
        token.safeTransferFrom(                   // Interactions
            msg.sender,
            address(this),
            amount
        );
    }
}
```

实际合约中，`unstake`、`claim` 等共享 `stakes` 状态的外部入口也应使用同一套 CEI 和重入保护策略；若代币存在转账税或 rebasing 行为，应改用余额差额记账。

### 1.2 状态机设计

**核心理念**：为合约定义明确的状态转换规则，防止非法状态。

```solidity
pragma solidity ^0.8.0;

contract SafeAuction {
    enum State { Created, Active, Ended, Settled }
    State public state = State.Created;
    uint public endTime;
    mapping(address => uint) public bids;

    // 状态修饰符
    modifier atState(State _state) {
        require(state == _state, "Invalid state");
        _;
    }

    function startAuction() public atState(State.Created) {
        state = State.Active;
        endTime = block.timestamp + 7 days;
    }

    function placeBid() public payable atState(State.Active) {
        require(block.timestamp < endTime, "Auction has ended");
        bids[msg.sender] += msg.value;
    }

    function endAuction() public atState(State.Active) {
        require(block.timestamp >= endTime, "Auction not ended");
        state = State.Ended;
    }

    function settle() public atState(State.Ended) {
        state = State.Settled;
        // 清算逻辑
    }
}
```

### 1.3 明确定义业务规则和约束

在代码中清晰地记录所有约束条件，这些将成为后续审计的依据。

```solidity
/**
 * @title SafeTokenVault
 * @notice 代币保管合约
 * @dev 业务约束：
 *  1. 每次提取最多 50% 余额，防止突然大额提取
 *  2. 每个账户每天最多提取一次
 *  3. 紧急情况下（>50% 用户冻结账户），启动冷却期
 */
contract SafeTokenVault {
    uint public constant MAX_WITHDRAWAL_PERCENT = 50; // 最多提取 50%
    uint public constant WITHDRAWAL_COOLDOWN = 1 days;

    mapping(address => uint) public lastWithdrawal;
    mapping(address => bool) public frozenAccounts;

    function withdraw(uint amount) external {
        require(block.timestamp >= lastWithdrawal[msg.sender] + WITHDRAWAL_COOLDOWN,
                "Too frequent withdrawals");
        require(amount <= balanceOf(msg.sender) * MAX_WITHDRAWAL_PERCENT / 100,
                "Exceeds max withdrawal limit");
        require(!frozenAccounts[msg.sender], "Account frozen");

        // 实现
    }
}
```

## 2. 编码阶段最佳实践

### 2.1 Checks-Effects-Interactions 模式

这是防止重入攻击的标准模式。

```solidity
pragma solidity ^0.8.0;

contract GoodWithdrawal {
    mapping(address => uint) public balances;

    // 不良模式：交互在前，更新在后
    function badWithdraw(uint amount) external {
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success);
        balances[msg.sender] -= amount;  // 重入时此处未执行
    }

    // 正确模式：检查 -> 更新 -> 交互
    function goodWithdraw(uint amount) external {
        // 1. Checks：验证前置条件
        require(balances[msg.sender] >= amount, "Insufficient balance");

        // 2. Effects：修改状态
        balances[msg.sender] -= amount;

        // 3. Interactions：执行外部调用
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}
```

### 2.2 使用重入锁

对于无法完全避免重入的复杂场景，使用重入锁提供保护。

```solidity
pragma solidity ^0.8.24;

contract WithReentrancyGuard {
    uint private locked = 1;
    address public externalContract;

    modifier nonReentrant() {
        require(locked == 1, "No reentrancy");
        locked = 2;
        _;
        locked = 1;
    }

    function complexOperation() external nonReentrant {
        // 即使这里调用不信任的外部合约，也不会重入
        (bool success, ) = externalContract.call("");
        require(success, "External call failed");
    }
}

// 或使用 OpenZeppelin 的实现
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SafeContract is ReentrancyGuard {
    function withdraw() public nonReentrant {
        // 代码
    }
}
```

### 2.3 显式处理低级调用的返回值

```solidity
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract ProperErrorHandling {
    using SafeERC20 for IERC20;

    address public token;

    // 不良做法：忽略返回值
    function bad_transfer() public {
        address(0x123).call{value: 1 ether}("");  // 失败时无提示
        // 继续执行下一行，可能导致状态不一致
    }

    // 正确做法 1：检查返回值
    function good_transfer_v1() public {
        (bool success, ) = address(0x123).call{value: 1 ether}("");
        require(success, "Transfer failed");
    }

    // 正确做法 2：使用高级接口
    function good_transfer_v2() public {
        require(
            IERC20(token).transfer(address(0x123), 1 ether),
            "Transfer failed"
        );
    }

    // 正确做法 3：使用 SafeERC20 兼容不返回 bool 的 ERC-20
    function good_transfer_v3() public {
        IERC20(token).safeTransfer(address(0x123), 1 ether);
    }
}
```

### 2.4 使用自定义错误表达失败原因

Solidity 0.8.x 中可以用 custom errors 替代长字符串错误。它们更适合 ABI 解码、测试断言和 gas 成本控制。

```solidity
pragma solidity ^0.8.24;

contract CheckedAuction {
    enum State { Created, Active, Ended }

    error InvalidState(State expected, State actual);
    error BidMustBePositive();

    State public state = State.Created;

    modifier atState(State expected) {
        if (state != expected) {
            revert InvalidState(expected, state);
        }
        _;
    }

    function placeBid() external payable atState(State.Active) {
        if (msg.value == 0) {
            revert BidMustBePositive();
        }
        // 记录出价
    }
}
```

测试中应断言具体错误，而不是只匹配字符串：

```typescript
await expect(auction.placeBid({ value: 0 }))
    .to.be.revertedWithCustomError(auction, "BidMustBePositive");
```

### 2.5 避免依赖 block.timestamp 进行关键判断

```solidity
pragma solidity ^0.8.0;

contract TimeDependenceIssues {
    uint lastRewardTime;

    // 不良做法：仅依赖 block.timestamp
    function vulnerable_reward() public {
        if (block.timestamp >= lastRewardTime + 1 days) {
            grantReward();
            lastRewardTime = block.timestamp;
        }
    }

    // 改进做法：添加额外保护
    function safe_reward() public {
        require(block.timestamp >= lastRewardTime + 1 days, "Too early");
        require(block.timestamp - lastRewardTime < 2 days, "Too late");
        grantReward();
        lastRewardTime = block.timestamp;
    }
}
```

### 2.6 严格的权限控制

```solidity
pragma solidity ^0.8.24;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

// 简单场景：使用 Ownable
contract SimpleAccess is Ownable {
    constructor(address initialOwner) Ownable(initialOwner) {}

    function criticalFunction() public onlyOwner {
        // 仅拥有者可调用
    }
}

// 复杂场景：使用 AccessControl 支持多角色
contract ComplexAccess is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant MODERATOR_ROLE = keccak256("MODERATOR_ROLE");

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(ADMIN_ROLE, admin);
    }

    function withdraw() public onlyRole(ADMIN_ROLE) {
        // 仅管理员可调用
    }

    function moderate() public onlyRole(MODERATOR_ROLE) {
        // 仅审核员可调用
    }
}
```

## 3. 测试阶段最佳实践

### 3.1 全面的单元测试

```typescript
// test/SafeAuction.test.ts
import { expect } from "chai";
import { network } from "hardhat";

const { ethers } = await network.connect();

describe("SafeAuction", function () {
    let auction;
    let owner, bidder1, bidder2;

    beforeEach(async function () {
        [owner, bidder1, bidder2] = await ethers.getSigners();
        const Auction = await ethers.getContractFactory("SafeAuction");
        auction = await Auction.deploy();
        await auction.waitForDeployment();
    });

    describe("State transitions", function () {
        it("Should not allow bidding before auction starts", async function () {
            await expect(
                auction.connect(bidder1).placeBid({ value: ethers.parseEther("1") })
            ).to.be.revertedWith("Invalid state");
        });

        it("Should transition to Active state", async function () {
            await auction.startAuction();
            expect(await auction.state()).to.equal(1); // Active
        });
    });

    describe("Bid placement", function () {
        beforeEach(async function () {
            await auction.startAuction();
        });

        it("Should accept valid bids", async function () {
            const bidAmount = ethers.parseEther("1");
            await auction.connect(bidder1).placeBid({ value: bidAmount });
            expect(await auction.bids(bidder1.address)).to.equal(bidAmount);
        });

        it("Should reject bids after auction ends", async function () {
            // 快进时间超过拍卖期限
            await ethers.provider.send("evm_increaseTime", [7 * 24 * 60 * 60 + 1]);
            await ethers.provider.send("evm_mine");

            await expect(
                auction.connect(bidder1).placeBid({ value: ethers.parseEther("1") })
            ).to.be.revertedWith("Auction has ended");
        });
    });

    describe("Attack vectors", function () {
        it("Should prevent reentrancy in settlement", async function () {
            // 部署攻击合约并测试
            const AttackFactory = await ethers.getContractFactory("ReentrancyAttack");
            const attack = await AttackFactory.deploy(await auction.getAddress());
            await attack.waitForDeployment();
            // 测试代码
        });
    });

    describe("Edge cases", function () {
        it("Should handle zero amount bids", async function () {
            await auction.startAuction();
            await expect(
                auction.connect(bidder1).placeBid({ value: 0 })
            ).to.be.revertedWith("Bid must be positive");
        });

        it("Should handle uint256 overflow", async function () {
            // 测试边界值
            await auction.startAuction();
            const maxUint = ethers.MaxUint256;
            // 测试逻辑
        });
    });
});
```

### 3.2 覆盖率目标

```bash

# 运行覆盖率检查
npx hardhat coverage

# 输出示例：
# ¦ contracts/SafeAuction.sol ¦ 96.5 % ¦
# 目标：>95% 的语句覆盖率，>90% 的分支覆盖率
```

## 4. 审计阶段最佳实践

### 4.1 内部审计清单

```markdown

## 智能合约审计清单

### 安全性检查
- [ ] 没有重入漏洞（使用 CEI 模式或重入锁）
- [ ] 整数溢出/下溢已处理（Solidity 0.8.0+ 内置检查；遗留 v4 代码才考虑 SafeMath）
- [ ] 访问控制正确（所有 public 函数都检查权限）
- [ ] 外部调用都检查返回值
- [ ] 没有依赖 tx.origin 进行认证
- [ ] 没有不安全的随机数生成
- [ ] 预言机数据已验证（多源、TWAP 等）

### 代码质量
- [ ] 没有未使用的变量或函数
- [ ] 事件已正确触发（用于链上索引）
- [ ] 常数已标记为 constant/immutable
- [ ] 使用了最新的安全库版本
- [ ] 代码注释清晰，特别是复杂逻辑

### 功能验证
- [ ] 所有业务规则已在代码中实现
- [ ] 状态转换符合预期
- [ ] 边界条件处理正确
- [ ] Gas 优化不影响安全性
```

### 4.2 自动化审计

```bash

# 运行 Slither
slither contracts/SafeAuction.sol

# 运行 Mythril
myth analyze contracts/SafeAuction.sol

# 运行 Echidna（需要定义属性）
echidna contracts/SafeAuction.sol --solc-args "--optimize"
```

## 5. 部署后最佳实践

### 5.1 合约验证

```bash

# 在区块链浏览器上验证源码
npx hardhat verify --network mainnet 0xContractAddress --constructor-args args.js
```

### 5.2 监控和告警

```solidity
pragma solidity ^0.8.0;

contract MonitorableAuction {
    event AuctionStarted(uint startTime, uint endTime);
    event BidPlaced(address indexed bidder, uint amount);
    event AuctionEnded(uint finalBid);
    event UnusualActivity(string reason);

    function startAuction() public onlyOwner {
        uint endTime = block.timestamp + 7 days;
        emit AuctionStarted(block.timestamp, endTime);
    }

    function placeBid() external payable {
        if (msg.value > 1000 ether) {
            emit UnusualActivity("Unusually large bid");
        }
        emit BidPlaced(msg.sender, msg.value);
    }
}
```

链下监控示例：

```python
import requests
from web3 import Web3

w3 = Web3(Web3.HTTPProvider('http://localhost:8545'))
contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=CONTRACT_ABI)

# 监听异常活动事件
def monitor_events():
    while True:
        logs = w3.eth.get_logs({
            'address': CONTRACT_ADDRESS,
            'topics': [w3.keccak(text='UnusualActivity(string)')]
        })
        for log in logs:
            print(f"Alert: Unusual activity detected!")
            # 发送告警
```

### 5.3 升级机制

```solidity
pragma solidity ^0.8.24;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract UpgradeableAuction is Initializable, OwnableUpgradeable, UUPSUpgradeable {
    uint256 public version;

    function initialize(address initialOwner) public initializer {
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();
        version = 1;
    }

    function _authorizeUpgrade(address newImplementation) internal onlyOwner override {
        // 升级授权逻辑
    }
}
```

## 6. 常见漏洞快速参考表

| 漏洞       | 表现           | 防护措施                     | 工具检测             |
| -------- | ------------ | ------------------------ | ---------------- |
| 重入       | 余额被重复扣除      | CEI 模式、重入锁               | Slither, Mythril |
| 整数溢出     | 余额变为负数       | Solidity 0.8.0+          | Slither          |
| 权限缺失     | 任何人可调用关键函数   | onlyOwner, AccessControl | Slither          |
| 外部调用失败忽略 | 状态不一致        | 检查返回值、SafeERC20          | Slither          |
| 时间戳依赖    | 矿工操纵         | 添加时间范围、预言机               | Mythril          |
| 闪电贷相关操纵  | 预言机或流动性被瞬时扭曲 | 多源预言机、TWAP、价格影响限制、熔断     | 属性测试、链上监控        |
| MEV 抢跑   | 交易被插队        | 批处理、承诺-reveal            | 链上分析             |

## 7. 资源与工具链

**必读资源**：

* OpenZeppelin Contracts：经审计的合约库（示例默认按 v5 路径和构造函数书写）
* Solidity 官方文档：语言规范和安全警告
* OWASP SCSVS / SCSTG / SCWE：智能合约安全需求、测试指南和弱点枚举
* EEA EthTrust Security Levels：审计和验收时可使用的安全等级基线
* SWC Registry：历史漏洞编号参考；不应作为当前唯一基线

**工具集**：

* Hardhat：开发和测试框架
* Truffle：成熟的开发套件（ConsenSys 已于 2023-09 宣布 sunset，存量项目可参考迁移说明，新项目推荐选 Hardhat 或 Foundry）
* Foundry：高性能的 Rust 实现
* etherscan API：链上数据获取

这些最佳实践的系统应用，能够从设计、编码、测试、审计到部署，全面保护智能合约的安全性。
