Solidity中实现安全的代币转账

如何在Solidity里怎么实现安全的代币转账。Solidity是Ethereum区块链的智能合约开发语言,写代币合约是区块链开发的常见场景,但转账逻辑如果不小心,可能会被黑客钻空子,比如重入攻击、溢出问题,或者权限管理不当。本文把Solidity的代币转账核心机制讲清楚,基于ERC-20标准,结合

如何在Solidity里怎么实现安全的代币转账。Solidity是Ethereum区块链的智能合约开发语言,写代币合约是区块链开发的常见场景,但转账逻辑如果不小心,可能会被黑客钻空子,比如重入攻击、溢出问题,或者权限管理不当。本文把Solidity的代币转账核心机制讲清楚,基于ERC-20标准,结合OpenZeppelin库,从简单的转账到复杂的多签和权限控制,配合完整代码和Hardhat测试,一步步带你搞定安全的代币转账。重点是干货,少废话,直接上技术细节,帮你把Solidity代币转账写得稳如磐石!

核心概念

先搞清楚几个关键点:

  • ERC-20标准:Ethereum上的代币标准,定义了transfertransferFromapprove等接口,方便钱包和交易所交互。
  • Solidity:Ethereum智能合约语言,强类型,运行在EVM(以太坊虚拟机)。
  • 转账安全
    • 重入攻击:防止外部合约在转账时重复调用。
    • 溢出/下溢:确保余额计算不越界。
    • 权限控制:限制谁能转账、授权。
    • 事件日志:记录转账操作,便于追踪。
  • OpenZeppelin:开源的智能合约库,提供安全的ERC-20实现。
  • Hardhat:开发和测试工具,支持编译、部署、测试合约。

咱们用Solidity 0.8.x(自带溢出检查)和OpenZeppelin,结合Hardhat和TypeScript,展示安全的代币转账实现。

环境准备

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

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

初始化Hardhat:

npx hardhat init

选择TypeScript项目,安装依赖:

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

目录结构:

token-demo/
├── contracts/
│   ├── Token.sol
│   ├── AdvancedToken.sol
├── scripts/
│   ├── deploy.ts
├── test/
│   ├── Token.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: "0.8.20",
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

export default config;

跑本地节点:

npx hardhat node

基础ERC-20代币转账

实现一个简单的ERC-20代币,包含transfertransferFrom

合约代码

contracts/Token.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Token is ERC20 {
    constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
    }
}

解析

  • ERC20基类:继承OpenZeppelin的ERC20,实现标准接口:
    • transfer(address to, uint256 amount):转账。
    • transferFrom(address from, address to, uint256 amount):授权转账。
    • approve(address spender, uint256 amount):授权。
    • balanceOf(address account):查询余额。
    • totalSupply():总供应量。
    • allowance(address owner, address spender):授权额度。
  • _mint:在构造函数中铸造initialSupply代币给部署者。
  • 安全特性
    • Solidity 0.8.x自带溢出/下溢检查。
    • OpenZeppelin的SafeMath(0.8.x前)内置于编译器。
    • transfertransferFrom检查余额和授权。

测试

test/Token.test.ts

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

describe("Token", function () {
  let token: Token;
  let owner: any, addr1: any, addr2: any;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("Token");
    token = await TokenFactory.deploy("MyToken", "MTK", ethers.parseEther("1000"));
    await token.deployed();
  });

  it("should deploy with initial supply", async function () {
    const balance = await token.balanceOf(owner.address);
    expect(balance).to.equal(ethers.parseEther("1000"));
  });

  it("should transfer tokens", async function () {
    await token.transfer(addr1.address, ethers.parseEther("100"));
    expect(await token.balanceOf(addr1.address)).to.equal(ethers.parseEther("100"));
    expect(await token.balanceOf(owner.address)).to.equal(ethers.parseEther("900"));
  });

  it("should fail if insufficient balance", async function () {
    await expect(token.transfer(addr1.address, ethers.parseEther("1001"))).to.be.revertedWith(
      "ERC20: transfer amount exceeds balance"
    );
  });

  it("should handle transferFrom", async function () {
    await token.approve(addr1.address, ethers.parseEther("100"));
    await token.connect(addr1).transferFrom(owner.address, addr2.address, ethers.parseEther("50"));
    expect(await token.balanceOf(addr2.address)).to.equal(ethers.parseEther("50"));
    expect(await token.allowance(owner.address, addr1.address)).to.equal(ethers.parseEther("50"));
  });

  it("should fail if insufficient allowance", async function () {
    await token.approve(addr1.address, ethers.parseEther("50"));
    await expect(
      токен.connect(addr1).transferFrom(owner.address, addr2.address, ethers.parseEther("51"))
    ).to.be.revertedWith("ERC20: insufficient allowance");
  });
});

跑测试:

npx hardhat test

解析

  • 部署:铸造1000 MTK给owner
  • transfer:从owner转100 MTK到addr1,检查余额。
  • transferFromowner授权addr1100 MTK,addr1转50 MTK到addr2
  • 安全检查
    • 余额不足:抛出transfer amount exceeds balance
    • 授权不足:抛出insufficient allowance
  • 事件TransferApproval事件自动触发,记录日志。

防止重入攻击

重入攻击是转账中的常见风险,外部合约可能在转账完成前重复调用。

重入攻击示例

contracts/VulnerableToken.sol

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

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

    constructor(uint256 initialSupply) {
        balances[msg.sender] = initialSupply;
    }

    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        (bool success, ) = to.call{value: 0}("");
        require(success, "Transfer failed");
        balances[to] += amount;
    }
}

攻击合约:

contracts/Attacker.sol

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

import "./VulnerableToken.sol";

contract Attacker {
    VulnerableToken public token;
    uint256 public count;

    constructor(address _token) {
        token = VulnerableToken(_token);
    }

    receive() external payable {
        if (count < 2) {
            count++;
            token.transfer(address(this), 100);
        }
    }

    function attack() external {
        token.transfer(address(this), 100);
    }
}

测试:

test/VulnerableToken.test.ts

import { ethers } from "hardhat";
import { expect } from "chai";
import { VulnerableToken, Attacker } from "../typechain-types";

describe("VulnerableToken", function () {
  let token: VulnerableToken;
  let attacker: Attacker;
  let owner: any, attackerAddr: any;

  beforeEach(async function () {
    [owner, attackerAddr] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("VulnerableToken");
    token = await TokenFactory.deploy(ethers.parseEther("1000"));
    await token.deployed();

    const AttackerFactory = await ethers.getContractFactory("Attacker");
    attacker = await AttackerFactory.deploy(token.address);
    await attacker.deployed();

    await token.transfer(attacker.address, ethers.parseEther("200"));
  });

  it("should be vulnerable to reentrancy", async function () {
    await attacker.connect(attackerAddr).attack();
    expect(await token.balances(attacker.address)).to.equal(ethers.parseEther("300"));
  });
});
  • 问题transfer在更新balances[to]前调用to.call,攻击合约通过receive重复调用,窃取代币。
  • 结果:攻击者余额增加到300,而应为200。

使用ReentrancyGuard

OpenZeppelin的ReentrancyGuard防止重入攻击。

contracts/SafeToken.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SafeToken is ERC20, ReentrancyGuard {
    constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
    }

    function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        return super.transfer(to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        return super.transferFrom(from, to, amount);
    }
}

测试:

test/SafeToken.test.ts

import { ethers } from "hardhat";
import { expect } from "chai";
import { SafeToken, Attacker } from "../typechain-types";

describe("SafeToken", function () {
  let token: SafeToken;
  let attacker: Attacker;
  let owner: any, attackerAddr: any;

  beforeEach(async function () {
    [owner, attackerAddr] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("SafeToken");
    token = await TokenFactory.deploy("SafeToken", "STK", ethers.parseEther("1000"));
    await token.deployed();

    const AttackerFactory = await ethers.getContractFactory("Attacker");
    attacker = await AttackerFactory.deploy(token.address);
    await attacker.deployed();

    await token.transfer(attacker.address, ethers.parseEther("200"));
  });

  it("should prevent reentrancy", async function () {
    await expect(attacker.connect(attackerAddr).attack()).to.be.revertedWith("ReentrancyGuard: reentrant call");
    expect(await token.balanceOf(attacker.address)).to.equal(ethers.parseEther("200"));
  });
});
  • ReentrancyGuard:用nonReentrant修饰符,设置锁状态,防止重复调用。
  • 解析:攻击失败,余额保持200。

权限控制:多签转账

实现多签机制,只有多个管理者同意才能转账大额代币。

合约代码

contracts/MultiSigToken.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MultiSigToken is ERC20, ReentrancyGuard {
    address[] public owners;
    uint256 public required;
    mapping(uint256 => Transaction) public transactions;
    mapping(uint256 => mapping(address => bool)) public confirmations;
    uint256 public transactionCount;

    struct Transaction {
        address to;
        uint256 amount;
        bool executed;
        uint256 confirmationCount;
    }

    event SubmitTransaction(uint256 indexed txId, address indexed to, uint256 amount);
    event ConfirmTransaction(uint256 indexed txId, address indexed owner);
    event ExecuteTransaction(uint256 indexed txId);
    event RevokeConfirmation(uint256 indexed txId, address indexed owner);

    modifier onlyOwner() {
        bool isOwner = false;
        for (uint256 i = 0; i < owners.length; i++) {
            if (owners[i] == msg.sender) {
                isOwner = true;
                break;
            }
        }
        require(isOwner, "Not owner");
        _;
    }

    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply,
        address[] memory _owners,
        uint256 _required
    ) ERC20(name, symbol) {
        require(_owners.length > 0 && _required > 0 && _required <= _owners.length, "Invalid params");
        owners = _owners;
        required = _required;
        _mint(msg.sender, initialSupply);
    }

    function submitTransaction(address to, uint256 amount) public onlyOwner {
        require(balanceOf(address(this)) >= amount, "Insufficient contract balance");
        uint256 txId = transactionCount++;
        transactions[txId] = Transaction({
            to: to,
            amount: amount,
            executed: false,
            confirmationCount: 0
        });
        emit SubmitTransaction(txId, to, amount);
    }

    function confirmTransaction(uint256 txId) public onlyOwner nonReentrant {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction already executed");
        require(!confirmations[txId][msg.sender], "Already confirmed");

        confirmations[txId][msg.sender] = true;
        transaction.confirmationCount++;
        emit ConfirmTransaction(txId, msg.sender);

        if (transaction.confirmationCount >= required) {
            executeTransaction(txId);
        }
    }

    function executeTransaction(uint256 txId) internal nonReentrant {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction already executed");
        require(transaction.confirmationCount >= required, "Insufficient confirmations");

        transaction.executed = true;
        _transfer(address(this), transaction.to, transaction.amount);
        emit ExecuteTransaction(txId);
    }

    function revokeConfirmation(uint256 txId) public onlyOwner {
        Transaction storage transaction = transactions[txId];
        require(!transaction.executed, "Transaction already executed");
        require(confirmations[txId][msg.sender], "Not confirmed");

        confirmations[txId][msg.sender] = false;
        transaction.confirmationCount--;
        emit RevokeConfirmation(txId, msg.sender);
    }

    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        if (amount > 100 ether) {
            _transfer(msg.sender, address(this), amount);
            submitTransaction(to, amount);
            return true;
        }
        return super.transfer(to, amount);
    }
}

解析

  • 多签机制
    • owners:管理者地址列表。
    • required:需要的最少确认数。
    • transactions:存储转账请求。
    • confirmations:记录每个管理者的确认状态。
  • 转账逻辑
    • 小额(≤100 ether):直接调用super.transfer
    • 大额(>100 ether):转到合约地址,发起多签流程。
  • 流程
    • submitTransaction:提交转账请求。
    • confirmTransaction:管理者确认,达到required后自动执行。
    • executeTransaction:执行转账。
    • revokeConfirmation:撤销确认。
  • 安全特性
    • nonReentrant防止重入。
    • onlyOwner限制操作者。
    • 检查余额和执行状态。

测试

test/MultiSigToken.test.ts

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

describe("MultiSigToken", function () {
  let token: MultiSigToken;
  let owner: any, addr1: any, addr2: any, addr3: any;

  beforeEach(async function () {
    [owner, addr1, addr2, addr3] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("MultiSigToken");
    token = await TokenFactory.deploy(
      "MultiSigToken",
      "MST",
      ethers.parseEther("1000"),
      [owner.address, addr1.address, addr2.address],
      2
    );
    await token.deployed();
  });

  it("should deploy with initial supply", async function () {
    expect(await token.balanceOf(owner.address)).to.equal(ethers.parseEther("1000"));
  });

  it("should handle small transfers", async function () {
    await token.transfer(addr3.address, ethers.parseEther("50"));
    expect(await token.balanceOf(addr3.address)).to.equal(ethers.parseEther("50"));
  });

  it("should handle multisig transfer", async function () {
    await token.transfer(addr3.address, ethers.parseEther("200"));
    expect(await token.balanceOf(address(token))).to.equal(ethers.parseEther("200"));

    await token.connect(addr1).confirmTransaction(0);
    expect(await token.balanceOf(addr3.address)).to.equal(ethers.parseEther("0"));

    await token.connect(addr2).confirmTransaction(0);
    expect(await token.balanceOf(addr3.address)).to.equal(ethers.parseEther("200"));
  });

  it("should fail if insufficient confirmations", async function () {
    await token.transfer(addr3.address, ethers.parseEther("200"));
    await token.connect(addr1).confirmTransaction(0);
    expect(await token.balanceOf(addr3.address)).to.equal(ethers.parseEther("0"));
  });

  it("should allow revoking confirmation", async function () {
    await token.transfer(addr3.address, ethers.parseEther("200"));
    await token.connect(addr1).confirmTransaction(0);
    await token.connect(addr1).revokeConfirmation(0);
    await token.connect(addr2).confirmTransaction(0);
    expect(await token.balanceOf(addr3.address)).to.equal(ethers.parseEther("0"));
  });
});
  • 解析
    • 小额转账:直接完成。
    • 大额转账:需2个管理者确认。
    • 撤销确认:可取消确认,阻止执行。
  • 安全nonReentrant和状态检查防止攻击。

代币冻结与权限管理

实现账户冻结和管理员权限。

contracts/AdvancedToken.sol

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

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

contract AdvancedToken is ERC20, ReentrancyGuard, Ownable {
    mapping(address => bool) public frozenAccounts;

    event FreezeAccount(address indexed account);
    event UnfreezeAccount(address indexed account);

    constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) Ownable() {
        _mint(msg.sender, initialSupply);
    }

    function freezeAccount(address account) public onlyOwner {
        frozenAccounts[account] = true;
        emit FreezeAccount(account);
    }

    function unfreezeAccount(address account) public onlyOwner {
        frozenAccounts[account] = false;
        emit UnfreezeAccount(account);
    }

    function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        require(!frozenAccounts[msg.sender], "Account is frozen");
        require(!frozenAccounts[to], "Recipient account is frozen");
        return super.transfer(to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        require(!frozenAccounts[from], "Account is frozen");
        require(!frozenAccounts[to], "Recipient account is frozen");
        return super.transferFrom(from, to, amount);
    }
}

解析

  • Ownable:继承OpenZeppelin的OwnableonlyOwner限制管理员操作。
  • 冻结机制
    • frozenAccounts:记录账户冻结状态。
    • freezeAccount/unfreezeAccount:管理员控制。
  • 转账检查transfertransferFrom检查账户状态。
  • 安全特性
    • nonReentrant防止重入。
    • onlyOwner限制冻结权限。

测试

test/AdvancedToken.test.ts

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

describe("AdvancedToken", function () {
  let token: AdvancedToken;
  let owner: any, addr1: any, addr2: any;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("AdvancedToken");
    token = await TokenFactory.deploy("AdvancedToken", "ATK", ethers.parseEther("1000"));
    await token.deployed();
  });

  it("should deploy with initial supply", async function () {
    expect(await token.balanceOf(owner.address)).to.equal(ethers.parseEther("1000"));
  });

  it("should allow transfer when not frozen", async function () {
    await token.transfer(addr1.address, ethers.parseEther("100"));
    expect(await token.balanceOf(addr1.address)).to.equal(ethers.parseEther("100"));
  });

  it("should prevent transfer when sender frozen", async function () {
    await token.freezeAccount(addr1.address);
    await token.transfer(addr1.address, ethers.parseEther("100"));
    await expect(
      token.connect(addr1).transfer(addr2.address, ethers.parseEther("50"))
    ).to.be.revertedWith("Account is frozen");
  });

  it("should prevent transfer to frozen recipient", async function () {
    await token.freezeAccount(addr2.address);
    await expect(
      token.transfer(addr2.address, ethers.parseEther("100"))
    ).to.be.revertedWith("Recipient account is frozen");
  });

  it("should allow unfreezing and transfer", async function () {
    await token.freezeAccount(addr1.address);
    await token.unfreezeAccount(addr1.address);
    await token.transfer(addr1.address, ethers.parseEther("100"));
    await token.connect(addr1).transfer(addr2.address, ethers.parseEther("50"));
    expect(await token.balanceOf(addr2.address)).to.equal(ethers.parseEther("50"));
  });

  it("should restrict freeze to owner", async function () {
    await expect(
      token.connect(addr1).freezeAccount(addr2.address)
    ).to.be.revertedWith("Ownable: caller is not the owner");
  });
});
  • 解析
    • 冻结账户无法转账或接收。
    • 解冻后恢复正常。
    • 非管理员无法冻结账户。

综合应用:限额转账与黑名单

实现限额转账和黑名单功能。

contracts/AdvancedToken.sol(更新):

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

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

contract AdvancedToken is ERC20, ReentrancyGuard, Ownable {
    mapping(address => bool) public frozenAccounts;
    mapping(address => bool) public blacklisted;
    uint256 public transferLimit;

    event FreezeAccount(address indexed account);
    event UnfreezeAccount(address indexed account);
    event BlacklistAccount(address indexed account);
    event UnblacklistAccount(address indexed account);
    event SetTransferLimit(uint256 limit);

    constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) Ownable() {
        _mint(msg.sender, initialSupply);
        transferLimit = 500 ether;
    }

    function setTransferLimit(uint256 limit) public onlyOwner {
        transferLimit = limit;
        emit SetTransferLimit(limit);
    }

    function freezeAccount(address account) public onlyOwner {
        frozenAccounts[account] = true;
        emit FreezeAccount(account);
    }

    function unfreezeAccount(address account) public onlyOwner {
        frozenAccounts[account] = false;
        emit UnfreezeAccount(account);
    }

    function blacklistAccount(address account) public onlyOwner {
        blacklisted[account] = true;
        emit BlacklistAccount(account);
    }

    function unblacklistAccount(address account) public onlyOwner {
        blacklisted[account] = false;
        emit UnblacklistAccount(account);
    }

    function transfer(address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        require(!frozenAccounts[msg.sender], "Account is frozen");
        require(!frozenAccounts[to], "Recipient account is frozen");
        require(!blacklisted[msg.sender], "Sender is blacklisted");
        require(!blacklisted[to], "Recipient is blacklisted");
        require(amount <= transferLimit, "Amount exceeds transfer limit");
        return super.transfer(to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) public virtual override nonReentrant returns (bool) {
        require(!frozenAccounts[from], "Account is frozen");
        require(!frozenAccounts[to], "Recipient account is frozen");
        require(!blacklisted[from], "Sender is blacklisted");
        require(!blacklisted[to], "Recipient is blacklisted");
        require(amount <= transferLimit, "Amount exceeds transfer limit");
        return super.transferFrom(from, to, amount);
    }
}

解析

  • 限额transferLimit限制单次转账金额,管理员可调整。
  • 黑名单blacklisted阻止特定账户转账。
  • 安全特性
    • onlyOwner限制限额和黑名单操作。
    • nonReentrant防止重入。
    • 多重检查确保合规性。

测试

test/AdvancedToken.test.ts(更新):

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

describe("AdvancedToken", function () {
  let token: AdvancedToken;
  let owner: any, addr1: any, addr2: any;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();
    const TokenFactory = await ethers.getContractFactory("AdvancedToken");
    token = await TokenFactory.deploy("AdvancedToken", "ATK", ethers.parseEther("1000"));
    await token.deployed();
  });

  it("should deploy with initial supply", async function () {
    expect(await token.balanceOf(owner.address)).to.equal(ethers.parseEther("1000"));
  });

  it("should enforce transfer limit", async function () {
    await expect(
      token.transfer(addr1.address, ethers.parseEther("501"))
    ).to.be.revertedWith("Amount exceeds transfer limit");
    await token.transfer(addr1.address, ethers.parseEther("500"));
    expect(await token.balanceOf(addr1.address)).to.equal(ethers.parseEther("500"));
  });

  it("should allow owner to change transfer limit", async function () {
    await token.setTransferLimit(ethers.parseEther("100"));
    await expect(
      token.transfer(addr1.address, ethers.parseEther("101"))
    ).to.be.revertedWith("Amount exceeds transfer limit");
    await token.transfer(addr1.address, ethers.parseEther("100"));
    expect(await token.balanceOf(addr1.address)).to.equal(ethers.parseEther("100"));
  });

  it("should prevent blacklisted sender", async function () {
    await token.blacklistAccount(addr1.address);
    await token.transfer(addr1.address, ethers.parseEther("100"));
    await expect(
      token.connect(addr1).transfer(addr2.address, ethers.parseEther("50"))
    ).to.be.revertedWith("Sender is blacklisted");
  });

  it("should prevent blacklisted recipient", async function () {
    await token.blacklistAccount(addr2.address);
    await expect(
      token.transfer(addr2.address, ethers.parseEther("100"))
    ).to.be.revertedWith("Recipient is blacklisted");
  });

  it("should allow unblacklisting", async function () {
    await token.blacklistAccount(addr1.address);
    await token.unblacklistAccount(addr1.address);
    await token.transfer(addr1.address, ethers.parseEther("100"));
    await token.connect(addr1).transfer(addr2.address, ethers.parseEther("50"));
    expect(await token.balanceOf(addr2.address)).to.equal(ethers.parseEther("50"));
  });

  it("should restrict blacklist to owner", async function () {
    await expect(
      token.connect(addr1).blacklistAccount(addr2.address)
    ).to.be.revertedWith("Ownable: caller is not the owner");
  });
});
  • 解析
    • 限额默认500 ether,超限转账失败。
    • 黑名单账户无法转账或接收。
    • 管理员可动态调整限额和黑名单。
  • 安全:多重检查和权限控制确保安全性。

部署脚本

scripts/deploy.ts

import { ethers } from "hardhat";

async function main() {
  const [owner, addr1, addr2] = await ethers.getSigners();
  const TokenFactory = await ethers.getContractFactory("AdvancedToken");
  const token = await TokenFactory.deploy("AdvancedToken", "ATK", ethers.parseEther("1000"));
  await token.deployed();
  console.log(`Token deployed to: ${token.address}`);

  const MultiSigFactory = await ethers.getContractFactory("MultiSigToken");
  const multiSigToken = await MultiSigFactory.deploy(
    "MultiSigToken",
    "MST",
    ethers.parseEther("1000"),
    [owner.address, addr1.address, addr2.address],
    2
  );
  await multiSigToken.deployed();
  console.log(`MultiSigToken deployed to: ${multiSigToken.address}`);
}

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

跑部署:

npx hardhat run scripts/deploy.ts --network hardhat
  • 解析:部署AdvancedTokenMultiSigToken,记录合约地址。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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