安全模式 #

一、检查-生效-交互模式 #

1.1 模式描述 #

这是最重要的安全模式,确保状态在交互前正确更新。

1.2 错误示例 #

solidity
// 错误:先交互,后更新状态
function withdraw() public {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");
    
    // 交互:先转账
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
    
    // 生效:后更新状态 - 危险!
    balances[msg.sender] = 0;
}

1.3 正确示例 #

solidity
// 正确:检查-生效-交互
function withdraw() public {
    // 1. 检查
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");
    
    // 2. 生效:先更新状态
    balances[msg.sender] = 0;
    
    // 3. 交互:后转账
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

二、互斥锁模式 #

2.1 基本实现 #

solidity
contract Mutex {
    bool private _locked;
    
    modifier nonReentrant() {
        require(!_locked, "Reentrant call");
        _locked = true;
        _;
        _locked = false;
    }
    
    mapping(address => uint256) public balances;
    
    function withdraw() public nonReentrant {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "No balance");
        
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

2.2 OpenZeppelin实现 #

solidity
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyContract is ReentrancyGuard {
    mapping(address => uint256) public balances;
    
    function withdraw() public nonReentrant {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "No balance");
        
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

三、拉取支付模式 #

3.1 问题:推送支付 #

solidity
// 有问题:推送支付可能导致DoS
contract PushPayment {
    mapping(address => uint256) public balances;
    
    function withdrawAll() public {
        for (uint256 i = 0; i < investors.length; i++) {
            payable(investors[i]).transfer(balances[investors[i]]);
        }
    }
}

3.2 解决:拉取支付 #

solidity
contract PullPayment {
    mapping(address => uint256) public balances;
    mapping(address => bool) public withdrawn;
    
    function withdraw() public {
        require(!withdrawn[msg.sender], "Already withdrawn");
        require(balances[msg.sender] > 0, "No balance");
        
        withdrawn[msg.sender] = true;
        uint256 amount = balances[msg.sender];
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

四、紧急停止模式 #

4.1 基本实现 #

solidity
contract Pausable {
    bool public paused;
    address public owner;
    
    event Paused(address account);
    event Unpaused(address account);
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }
    
    modifier whenPaused() {
        require(paused, "Contract is not paused");
        _;
    }
    
    function pause() public onlyOwner whenNotPaused {
        paused = true;
        emit Paused(msg.sender);
    }
    
    function unpause() public onlyOwner whenPaused {
        paused = false;
        emit Unpaused(msg.sender);
    }
}

4.2 OpenZeppelin实现 #

solidity
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyContract is Pausable, Ownable {
    function sensitiveOperation() public whenNotPaused {
        // 敏感操作
    }
    
    function pause() public onlyOwner {
        _pause();
    }
    
    function unpause() public onlyOwner {
        _unpause();
    }
}

五、访问控制模式 #

5.1 基于角色 #

solidity
import "@openzeppelin/contracts/access/AccessControl.sol";

contract RoleBased is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }
    
    function adminOperation() public onlyRole(ADMIN_ROLE) {
        // 管理员操作
    }
    
    function operatorOperation() public onlyRole(OPERATOR_ROLE) {
        // 操作员操作
    }
}

六、总结 #

安全模式要点:

模式 说明
检查-生效-交互 先更新状态,后外部调用
互斥锁 防止重入攻击
拉取支付 避免批量转账DoS
紧急停止 可暂停合约
访问控制 基于角色的权限

接下来,让我们学习最佳实践!

最后更新:2026-03-27