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

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

## 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 避免依赖 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.5 严格的权限控制

```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 全面的单元测试

```javascript
// test/SafeAuction.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

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();
    });

    describe("State transitions", function () {
        it("Should not allow bidding before auction starts", async function () {
            await expect(
                auction.connect(bidder1).placeBid({ value: ethers.utils.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.utils.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.utils.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(auction.address);
            // 测试代码
        });
    });

    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.constants.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：成熟的开发套件
* Foundry：高性能的 Rust 实现
* etherscan API：链上数据获取

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yeasy.gitbook.io/blockchain_guide/05_crypto/smart_contract_best_practices.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
