Solidity合约暂停与恢复:让你的区块链项目稳如老狗随时刹车

Solidity合约的暂停和恢复!区块链上跑的智能合约,资金和数据都在链上,遇到紧急情况,比如发现漏洞、黑客攻击,或者需要维护,咋办?暂停功能就是救命稻草!它能让合约“刹车”,阻止关键操作,等修好再恢复。暂停与恢复的核心概念先搞清楚几个关键点:暂停功能:暂时禁用合约的关键功能(如转账、存款)

Solidity合约的暂停和恢复!区块链上跑的智能合约,资金和数据都在链上,遇到紧急情况,比如发现漏洞、黑客攻击,或者需要维护,咋办?暂停功能就是救命稻草!它能让合约“刹车”,阻止关键操作,等修好再恢复。

暂停与恢复的核心概念

先搞清楚几个关键点:

  • 暂停功能:暂时禁用合约的关键功能(如转账、存款),但保留状态(如余额)。
  • 恢复功能:重新启用暂停的功能,恢复正常运行。
  • 使用场景
    • 发现漏洞,暂停修复。
    • 防止黑客攻击,保护资金。
    • 计划性维护,如升级逻辑。
  • 安全风险
    • 权限控制:谁能暂停/恢复?没限制可能被恶意用户滥用。
    • 状态一致性:暂停后需确保数据不被意外修改。
    • 事件缺失:无事件记录,链上追踪困难。
    • 误操作:无意暂停影响用户体验。
    • Gas限制:复杂暂停逻辑可能耗尽Gas。
  • 工具
    • Solidity 0.8.x:自带溢出/下溢检查,安全可靠。
    • OpenZeppelin:提供Pausable和访问控制库。
    • Hardhat:测试和调试暂停逻辑。
  • 事件:用事件记录暂停和恢复,便于链上追踪。
  • EVM特性
    • 暂停只影响函数调用,不影响存储读取。
    • delegatecall需考虑暂停状态传递。

咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,从基础暂停到多签和条件暂停,逐步实现安全的暂停与恢复机制。

环境准备

用Hardhat搭建开发环境,写和测试合约。

mkdir pause-resume-demo
cd pause-resume-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts @openzeppelin/contracts-upgradeable
npm install ethers

初始化Hardhat:

npx hardhat init

选择TypeScript项目,安装依赖:

npm install --save-dev ts-node typescript @types/node @types/mocha

目录结构:

pause-resume-demo/
├── contracts/
│   ├── BasicPause.sol
│   ├── RoleBasedPause.sol
│   ├── MultiSigPause.sol
│   ├── ConditionalPause.sol
│   ├── UpgradablePause.sol
├── scripts/
│   ├── deploy.ts
├── test/
│   ├── PauseResume.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./"
  },
  "include": ["hardhat.config.ts", "scripts", "test"]
}

hardhat.config.ts

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

export default config;

跑本地节点:

npx hardhat node

基础暂停与恢复

先从简单的暂停机制开始,用OpenZeppelin的Pausable

合约代码

contracts/BasicPause.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract BasicPauseToken is ERC20, Ownable, Pausable {
    constructor() ERC20("BasicPauseToken", "BPT") Ownable() {
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        super.transfer(to, amount);
    }

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

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

解析

  • 逻辑
    • 继承PausableOwnableERC20
    • transfer:用whenNotPaused修饰符,暂停时禁用。
    • pause:调用_pause,设置暂停状态,仅owner可调用。
    • unpause:调用_unpause,恢复正常,仅owner可调用。
  • 安全特性
    • onlyOwner限制暂停/恢复权限。
    • PausablewhenNotPaused保护关键函数。
    • 暂停不影响状态读取(如balanceOf)。
  • 问题
    • 单人控制风险高,误操作可能影响用户。
    • 无事件记录,链上追踪困难。
  • Gas
    • pause/~5k Gas(SSTORE)。
    • whenNotPaused增加~1k Gas/调用。

测试

test/PauseResume.test.ts

import { ethers } from "hardhat";
import { expect } from "chai";
import { BasicPauseToken } from "../typechain-types";

describe("BasicPause", function () {
  let token: BasicPauseToken;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("BasicPauseToken");
    token = await TokenFactory.deploy();
    await token.deployed();
    await token.transfer(user1.address, ethers.utils.parseEther("1000"));
  });

  it("should pause and block transfers", async function () {
    await token.pause();
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
  });

  it("should unpause and allow transfers", async function () {
    await token.pause();
    await token.unpause();
    await token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
    expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("999100"));
  });

  it("should restrict pause to owner", async function () {
    await expect(token.connect(user1).pause()).to.be.revertedWith("Ownable: caller is not the owner");
  });
});

跑测试:

npx hardhat test
  • 解析
    • 暂停后transfer失败。
    • 恢复后transfer成功,余额正确更新。
    • owner无法暂停,验证权限控制。
  • 问题:需添加事件和多签控制。

角色-based暂停

用角色管理支持多用户控制暂停。

合约代码

contracts/RoleBasedPause.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract RoleBasedPauseToken is ERC20, Pausable, AccessControl {
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    event Pause(address indexed account);
    event Unpause(address indexed account);

    constructor() ERC20("RoleBasedPauseToken", "RBPT") {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(PAUSER_ROLE, msg.sender);
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        super.transfer(to, amount);
    }

    function pause() public onlyRole(PAUSER_ROLE) {
        _pause();
        emit Pause(msg.sender);
    }

    function unpause() public onlyRole(PAUSER_ROLE) {
        _unpause();
        emit Unpause(msg.sender);
    }
}

解析

  • 逻辑
    • 继承AccessControl,定义PAUSER_ROLE
    • pause/unpause:限制为PAUSER_ROLE,触发Pause/Unpause事件。
    • transfer:用whenNotPaused保护。
    • 构造函数:授予msg.sender管理员和暂停者角色。
  • 安全特性
    • 角色分离,管理员可分配PAUSER_ROLE
    • 事件记录暂停/恢复操作。
    • onlyRole限制权限。
  • Gas
    • 角色检查增加~2k Gas/调用。
    • 事件增加~2k Gas。
  • 适用场景:多团队成员管理暂停。

测试

test/PauseResume.test.ts(add):

import { RoleBasedPauseToken } from "../typechain-types";

describe("RoleBasedPause", function () {
  let token: RoleBasedPauseToken;
  let owner: any, pauser: any, user1: any;

  beforeEach(async function () {
    [owner, pauser, user1] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("RoleBasedPauseToken");
    token = await TokenFactory.deploy();
    await token.deployed();
    await token.grantRole(await token.PAUSER_ROLE(), pauser.address);
    await token.transfer(user1.address, ethers.utils.parseEther("1000"));
  });

  it("should allow pauser to pause", async function () {
    await expect(token.connect(pauser).pause())
      .to.emit(token, "Pause")
      .withArgs(pauser.address);
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
  });

  it("should allow pauser to unpause", async function () {
    await token.connect(pauser).pause();
    await expect(token.connect(pauser).unpause())
      .to.emit(token, "Unpause")
      .withArgs(pauser.address);
    await token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
    expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("999100"));
  });

  it("should restrict pause to pauser role", async function () {
    await expect(token.connect(user1).pause()).to.be.revertedWith("AccessControl: account is missing role");
  });
});
  • 解析
    • PAUSER_ROLE用户可暂停/恢复,触发事件。
    • PAUSER_ROLE用户无法操作。
    • 恢复后transfer正常运行。
  • 优势:角色管理支持多用户控制。

多签控制暂停

为暂停加多签机制,需多人同意。

合约代码

contracts/MultiSigPause.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract MultiSigPauseToken is ERC20, Ownable, Pausable {
    address[] public pausers;
    uint256 public required;
    uint256 public transactionCount;
    mapping(uint256 => Transaction) public transactions;
    mapping(uint256 => mapping(address => bool)) public confirmations;

    struct Transaction {
        bool isPause;
        bool executed;
        uint256 confirmationCount;
    }

    event SubmitPause(uint256 indexed txId, bool isPause);
    event ConfirmPause(uint256 indexed txId, address indexed pauser);
    event ExecutePause(uint256 indexed txId, bool isPause);
    event RevokeConfirmation(uint256 indexed txId, address indexed pauser);
    event Pause(address indexed account);
    event Unpause(address indexed account);

    modifier onlyPauser() {
        bool isPauser = false;
        for (uint256 i = 0; i < pausers.length; i++) {
            if (pausers[i] == msg.sender) {
                isPauser = true;
                break;
            }
        }
        require(isPauser, "Not pauser");
        _;
    }

    constructor(address[] memory _pausers, uint256 _required) ERC20("MultiSigPauseToken", "MSPT") Ownable() {
        require(_pausers.length > 0, "Pausers required");
        require(_required > 0 && _required <= _pausers.length, "Invalid required");
        pausers = _pausers;
        required = _required;
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        super.transfer(to, amount);
    }

    function submitPause(bool isPause) public onlyPauser {
        uint256 txId = transactionCount++;
        transactions[txId] = Transaction({
            isPause: isPause,
            executed: false,
            confirmationCount: 0
        });
        emit SubmitPause(txId, isPause);
    }

    function confirmPause(uint256 txId) public onlyPauser {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(!confirmations[txId][msg.sender], "Already confirmed");
        confirmations[txId][msg.sender] = true;
        transaction.confirmationCount++;
        emit ConfirmPause(txId, msg.sender);
        if (transaction.confirmationCount >= required) {
            executePause(txId);
        }
    }

    function executePause(uint256 txId) internal {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(transaction.confirmationCount >= required, "Insufficient confirmations");
        transaction.executed = true;
        if (transaction.isPause) {
            _pause();
            emit Pause(msg.sender);
        } else {
            _unpause();
            emit Unpause(msg.sender);
        }
        emit ExecutePause(txId, transaction.isPause);
    }

    function revokeConfirmation(uint256 txId) public onlyPauser {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction executed");
        require(confirmations[txId][msg.sender], "Not confirmed");
        confirmations[txId][msg.sender] = false;
        transaction.confirmationCount--;
        emit RevokeConfirmation(txId, msg.sender);
    }
}

解析

  • 逻辑
    • pausersrequired控制多签。
    • submitPause:提交暂停/恢复提案。
    • confirmPause:确认提案,达标后执行。
    • executePause:调用_pause_unpause,触发事件。
    • revokeConfirmation:撤销确认。
  • 安全特性
    • 多签防止单人误操作。
    • 检查执行状态和确认数。
    • 事件记录提案和操作。
  • Gas
    • 提案和确认各~10k Gas。
    • 执行~5k Gas。
  • 适用场景:团队管理暂停,防止滥用。

测试

test/PauseResume.test.ts(add):

import { MultiSigPauseToken } from "../typechain-types";

describe("MultiSigPause", function () {
  let token: MultiSigPauseToken;
  let owner: any, pauser1: any, pauser2: any, pauser3: any, user1: any;

  beforeEach(async function () {
    [owner, pauser1, pauser2, pauser3, user1] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("MultiSigPauseToken");
    token = await TokenFactory.deploy([pauser1.address, pauser2.address, pauser3.address], 2);
    await token.deployed();
    await token.transfer(user1.address, ethers.utils.parseEther("1000"));
  });

  it("should execute pause with multi-sig", async function () {
    await token.connect(pauser1).submitPause(true);
    await token.connect(pauser2).confirmPause(0);
    await expect(token.connect(pauser3).confirmPause(0))
      .to.emit(token, "Pause")
      .withArgs(pauser3.address);
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
  });

  it("should execute unpause with multi-sig", async function () {
    await token.connect(pauser1).submitPause(true);
    await token.connect(pauser2).confirmPause(0);
    await token.connect(pauser3).confirmPause(0);
    await token.connect(pauser1).submitPause(false);
    await token.connect(pauser2).confirmPause(1);
    await expect(token.connect(pauser3).confirmPause(1))
      .to.emit(token, "Unpause")
      .withArgs(pauser3.address);
    await token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
    expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("999100"));
  });

  it("should not execute without enough confirmations", async function () {
    await token.connect(pauser1).submitPause(true);
    await token.connect(pauser2).confirmPause(0);
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"))).to.not.be.reverted;
  });

  it("should allow revoking confirmation", async function () {
    await token.connect(pauser1).submitPause(true);
    await token.connect(pauser2).confirmPause(0);
    await token.connect(pauser2).revokeConfirmation(0);
    await token.connect(pauser3).confirmPause(0);
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"))).to.not.be.reverted;
  });
});
  • 解析
    • 2/3确认后执行暂停,transfer失败。
    • 恢复后transfer成功。
    • 单人确认不触发暂停。
    • 撤销确认阻止暂停。
  • 优势:多签增加安全性。

条件暂停

根据特定条件自动暂停(如余额异常)。

合约代码

contracts/ConditionalPause.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract ConditionalPauseToken is ERC20, Ownable, Pausable {
    uint256 public maxBalance = 100000 * 10**18; // Max 100k tokens per address
    event Pause(address indexed account, string reason);
    event Unpause(address indexed account);

    constructor() ERC20("ConditionalPauseToken", "CPT") Ownable() {
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        super.transfer(to, amount);
        if (balanceOf(to) > maxBalance) {
            _pause();
            emit Pause(msg.sender, "Max balance exceeded");
        }
    }

    function pause() public onlyOwner {
        _pause();
        emit Pause(msg.sender, "Manual pause");
    }

    function unpause() public onlyOwner {
        _unpause();
        emit Unpause(msg.sender);
    }
}

解析

  • 逻辑
    • maxBalance限制单地址最大持仓。
    • transfer:检查接收者余额,超限自动暂停。
    • pause/unpause:手动控制,仅owner
    • 触发Pause/Unpause事件,记录原因。
  • 安全特性
    • 自动暂停防止异常累积。
    • 事件记录触发原因。
    • onlyOwner限制手动操作。
  • Gas
    • 条件检查增加~1k Gas/转账。
    • 暂停触发~5k Gas。
  • 适用场景:自动保护机制,如防止代币集中。

测试

test/PauseResume.test.ts(add):

import { ConditionalPauseToken } from "../typechain-types";

describe("ConditionalPause", function () {
  let token: ConditionalPauseToken;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("ConditionalPauseToken");
    token = await TokenFactory.deploy();
    await token.deployed();
    await token.transfer(user1.address, ethers.utils.parseEther("50000"));
  });

  it("should auto-pause on max balance", async function () {
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("60000")))
      .to.emit(token, "Pause")
      .withArgs(user1.address, "Max balance exceeded");
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
  });

  it("should allow manual pause and unpause", async function () {
    await token.pause();
    await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
    await token.unpause();
    await token.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
    expect(await token.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("950100"));
  });
});
  • 解析
    • 转账超maxBalance触发暂停。
    • 手动暂停/恢复正常工作。
    • 事件记录触发原因。

可升级合约的暂停

结合代理模式支持暂停。

合约代码

contracts/UpgradablePause.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract UpgradablePauseToken is ERC20Upgradeable, PausableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    event Pause(address indexed account);
    event Unpause(address indexed account);

    function initialize() public initializer {
        __ERC20_init("UpgradablePauseToken", "UPT");
        __Pausable_init();
        __Ownable_init();
        __UUPSUpgradeable_init();
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        super.transfer(to, amount);
    }

    function pause() public onlyOwner {
        _pause();
        emit Pause(msg.sender);
    }

    function unpause() public onlyOwner {
        _unpause();
        emit Unpause(msg.sender);
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

contracts/UpgradablePauseV2.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract UpgradablePauseTokenV2 is ERC20Upgradeable, PausableUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    uint256 public transferFee = 1 * 10**16; // 1% fee
    event Pause(address indexed account);
    event Unpause(address indexed account);

    function initialize() public initializer {
        __ERC20_init("UpgradablePauseTokenV2", "UPTV2");
        __Pausable_init();
        __Ownable_init();
        __UUPSUpgradeable_init();
        _mint(msg.sender, 1000000 * 10**decimals());
    }

    function transfer(address to, uint256 amount) public virtual override whenNotPaused {
        uint256 fee = (amount * transferFee) / 1e18;
        super.transfer(to, amount - fee);
        super.transfer(owner(), fee);
    }

    function pause() public onlyOwner {
        _pause();
        emit Pause(msg.sender);
    }

    function unpause() public onlyOwner {
        _unpause();
        emit Unpause(msg.sender);
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

contracts/UUPSProxy.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract UUPSProxy is ERC1967Proxy {
    constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) {}
}

解析

  • UpgradablePauseToken
    • 继承ERC20UpgradeablePausableUpgradeableUUPSUpgradeable
    • initialize:初始化代币和暂停状态。
    • transfer:用whenNotPaused保护。
    • pause/unpause:触发事件,仅owner
  • UpgradablePauseTokenV2
    • 添加1%转账费,逻辑升级。
    • 保持暂停功能和存储布局。
  • UUPSProxy
    • 继承ERC1967Proxy,支持UUPS升级。
  • 安全特性
    • UUPSUpgradeable确保升级安全。
    • PausableUpgradeable支持暂停。
    • 事件记录操作。
  • Gas
    • 升级~20k Gas。
    • 暂停~5k Gas。

测试

test/PauseResume.test.ts(add):

import { UUPSProxy, UpgradablePauseToken, UpgradablePauseTokenV2 } from "../typechain-types";

describe("UpgradablePause", function () {
  let proxy: UUPSProxy;
  let token: UpgradablePauseToken;
  let tokenV2: UpgradablePauseTokenV2;
  let owner: any, user1: any;

  beforeEach(async function () {
    [owner, user1] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("UpgradablePauseToken");
    token = await TokenFactory.deploy();
    await token.deployed();

    const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
    const initData = TokenFactory.interface.encodeFunctionData("initialize");
    proxy = await ProxyFactory.deploy(token.address, initData);
    await proxy.deployed();

    const TokenV2Factory = await ethers.getContractFactory("UpgradablePauseTokenV2");
    tokenV2 = await TokenV2Factory.deploy();
    await tokenV2.deployed();

    await (await ethers.getContractFactory("UpgradablePauseToken")).attach(proxy.address)
      .transfer(user1.address, ethers.utils.parseEther("1000"));
  });

  it("should pause and block transfers", async function () {
    const proxyAsToken = await ethers.getContractFactory("UpgradablePauseToken").then(f => f.attach(proxy.address));
    await proxyAsToken.pause();
    await expect(proxyAsToken.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
  });

  it("should upgrade and retain pause functionality", async function () {
    const proxyAsToken = await ethers.getContractFactory("UpgradablePauseToken").then(f => f.attach(proxy.address));
    await proxyAsToken.pause();
    await proxyAsToken.upgradeTo(tokenV2.address);
    const proxyAsTokenV2 = await ethers.getContractFactory("UpgradablePauseTokenV2").then(f => f.attach(proxy.address));
    await expect(proxyAsTokenV2.connect(user1).transfer(owner.address, ethers.utils.parseEther("100")))
      .to.be.revertedWith("Pausable: paused");
    await proxyAsTokenV2.unpause();
    await proxyAsTokenV2.connect(user1).transfer(owner.address, ethers.utils.parseEther("100"));
    expect(await proxyAsTokenV2.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("999099")); // 1% fee
  });
});
  • 解析
    • 暂停后transfer失败。
    • 升级到V2后,暂停功能保留,转账扣1%费用。
    • 事件和状态正确更新。

部署脚本

scripts/deploy.ts

import { ethers } from "hardhat";

async function main() {
  const [owner, pauser1, pauser2, pauser3] = await ethers.getSigners();

  const BasicPauseFactory = await ethers.getContractFactory("BasicPauseToken");
  const basicPause = await BasicPauseFactory.deploy();
  await basicPause.deployed();
  console.log(`BasicPauseToken deployed to: ${basicPause.address}`);

  const RoleBasedPauseFactory = await ethers.getContractFactory("RoleBasedPauseToken");
  const roleBasedPause = await RoleBasedPauseFactory.deploy();
  await roleBasedPause.deployed();
  console.log(`RoleBasedPauseToken deployed to: ${roleBasedPause.address}`);

  const MultiSigPauseFactory = await ethers.getContractFactory("MultiSigPauseToken");
  const multiSigPause = await MultiSigPauseFactory.deploy([pauser1.address, pauser2.address, pauser3.address], 2);
  await multiSigPause.deployed();
  console.log(`MultiSigPauseToken deployed to: ${multiSigPause.address}`);

  const ConditionalPauseFactory = await ethers.getContractFactory("ConditionalPauseToken");
  const conditionalPause = await ConditionalPauseFactory.deploy();
  await conditionalPause.deployed();
  console.log(`ConditionalPauseToken deployed to: ${conditionalPause.address}`);

  const UpgradablePauseFactory = await ethers.getContractFactory("UpgradablePauseToken");
  const upgradablePause = await UpgradablePauseFactory.deploy();
  await upgradablePause.deployed();
  const initData = UpgradablePauseFactory.interface.encodeFunctionData("initialize");
  const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
  const proxy = await ProxyFactory.deploy(upgradablePause.address, initData);
  await proxy.deployed();
  console.log(`UpgradablePauseToken deployed to: ${upgradablePause.address}, Proxy: ${proxy.address}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

跑部署:

npx hardhat run scripts/deploy.ts --network hardhat

安全审计要点

  • 权限控制
    • 限制pause/unpause为特定角色或多签。
    • 防止非授权用户操作。
  • 状态一致性
    • 暂停只影响关键函数,保留读取功能。
    • 确保升级不破坏暂停状态。
  • 事件追踪
    • 触发Pause/Unpause事件,记录原因。
  • Gas管理
    • 暂停逻辑简单,降低Gas成本。
    • 避免复杂条件检查。
  • 测试覆盖
    • 测试暂停/恢复、权限限制。
    • 模拟攻击(如非授权暂停)。
    • 验证升级后功能一致性。

跑代码,体验Solidity暂停与恢复的硬核玩法吧!

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!