欢迎光临
我们一直在努力

ERC-20 代币实战:从铸造到转账的完整开发指南

1. 引言:什么是 ERC-20?

ERC-20 是以太坊上最流行的代币标准,它定义了一套统一的接口规则,使得任何遵循该标准的代币都能与钱包、交易所和去中心化应用(DApp)无缝交互。简单来说,ERC-20 为代币的创建和管理提供了“通用说明书”。

本文将带你从零开始,实战开发一个具备铸造(Mint) 和转账(Transfer) 功能的 ERC-20 代币。你将学习到:

  • ERC-20 标准的核心接口。
  • 如何使用 Solidity 和 Hardhat 编写安全的代币合约。
  • 如何实现铸造功能,为特定地址生成新代币。
  • 如何实现并测试转账功能。
  • 将合约部署到测试网并进行验证。

2. 环境与工具准备

在开始编码前,请确保你的开发环境已就绪。

2.1 安装 Node.js 和 npm

访问 Node.js 官网 下载并安装 LTS 版本。安装完成后,在终端运行以下命令验证:

node –version
npm –version

2.2 初始化 Hardhat 项目

我们将使用 Hardhat 作为开发、测试和部署框架。

mkdir erc20-tutorial && cd erc20-tutorial
npm init -y
npm install –save-dev hardhat
npx hardhat init

在初始化向导中,选择 “Create a JavaScript project”,并安装所有推荐的依赖。

2.3 安装 OpenZeppelin 合约库

OpenZeppelin 提供了经过严格审计、安全可靠的合约实现,我们将基于它来构建我们的代币。

npm install @openzeppelin/contracts

3. ERC-20 标准核心接口解析

ERC-20 标准规定了代币合约必须实现的最小接口。理解这些接口是开发的基础。

3.1 必须实现的函数

  • totalSupply(): 返回代币的总供应量。
  • balanceOf(address account): 返回指定 account 地址的代币余额。
  • transfer(address recipient, uint256 amount): 从调用者地址向 recipient 地址转账 amount 数量的代币。
  • allowance(address owner, address spender): 返回 owner 授权给 spender 使用的代币数量。
  • approve(address spender, uint256 amount): 授权 spender 可以从调用者地址转出最多 amount 数量的代币。
  • transferFrom(address sender, address recipient, uint256 amount): 在授权额度内,从 sender 地址向 recipient 地址转账 amount 数量的代币。
  • 3.2 必须触发的事件

    • Transfer(address indexed from, address indexed to, uint256 value): 当代币被转账(包括铸造和销毁)时触发。
    • Approval(address indexed owner, address indexed spender, uint256 value): 当授权成功时触发。

    4. 编写我们的代币合约:MyToken

    我们将创建一个名为 MyToken 的可铸造 ERC-20 代币。

    4.1 创建合约文件

    在 contracts/ 目录下创建 MyToken.sol 文件。

    4.2 合约代码详解

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

    // 导入 OpenZeppelin 的 ERC-20 实现和拥有者权限控制
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";

    /**
    * @title MyToken – 一个可铸造的 ERC-20 代币
    * @dev 继承自 ERC20 和 Ownable,只有合约所有者可以铸造新代币。
    */
    contract MyToken is ERC20, Ownable {
    /**
    * @dev 构造函数,初始化代币名称和符号,并将合约部署者设为所有者。
    * @param initialOwner 初始合约所有者地址,拥有铸造权限。
    */
    constructor(address initialOwner)
    ERC20("MyToken", "MTK")
    Ownable(initialOwner)
    {
    // 构造函数体可以为空,或者执行一些初始设置
    // 例如,可以在这里进行初始铸造:_mint(initialOwner, 1000000 * 10 ** decimals());
    }

    /**
    * @dev 铸造新代币的函数,仅所有者可调用。
    * @param to 接收新铸造代币的地址。
    * @param amount 要铸造的代币数量(以最小单位计,例如 wei)。
    */
    function mint(address to, uint256 amount) public onlyOwner {
    _mint(to, amount);
    }
    }

    代码解析:

    • ERC20("MyToken", "MTK"): 继承 OpenZeppelin 的 ERC20 合约,并传入代币名称和符号。
    • Ownable(initialOwner): 继承 Ownable 合约,实现所有权管理。initialOwner 在部署时被设为所有者。
    • onlyOwner: 这是一个函数修饰器(modifier),确保只有合约所有者才能调用 mint 函数。
    • _mint(to, amount): 调用 ERC-20 内部函数,安全地向地址 to 铸造 amount 数量的代币,并自动触发 Transfer 事件。

    5. 编译与本地测试

    编写测试是确保合约安全可靠的关键步骤。

    5.1 编写测试脚本

    在 test/ 目录下创建 MyToken.test.js 文件。

    const { expect } = require("chai");
    const { ethers } = require("hardhat");

    describe("MyToken", function () {
    let MyToken;
    let myToken;
    let owner;
    let addr1;
    let addr2;

    beforeEach(async function () {
    // 获取合约工厂
    MyToken = await ethers.getContractFactory("MyToken");
    // 获取测试账户
    [owner, addr1, addr2] = await ethers.getSigners();
    // 部署合约,将部署者(owner)设为初始所有者
    myToken = await MyToken.deploy(owner.address);
    await myToken.waitForDeployment();
    });

    describe("部署与初始化", function () {
    it("应该设置正确的名称和符号", async function () {
    expect(await myToken.name()).to.equal("MyToken");
    expect(await myToken.symbol()).to.equal("MTK");
    });

    it("应该将部署者设为所有者", async function () {
    expect(await myToken.owner()).to.equal(owner.address);
    });
    });

    describe("铸造功能", function () {
    it("只有所有者可以铸造代币", async function () {
    // 所有者铸造成功
    await expect(myToken.connect(owner).mint(addr1.address, 1000))
    .to.emit(myToken, "Transfer")
    .withArgs(ethers.ZeroAddress, addr1.address, 1000);

    expect(await myToken.balanceOf(addr1.address)).to.equal(1000);

    // 非所有者铸造失败
    await expect(
    myToken.connect(addr1).mint(addr2.address, 500)
    ).to.be.revertedWithCustomError(myToken, "OwnableUnauthorizedAccount");
    });
    });

    describe("转账功能", function () {
    beforeEach(async function () {
    // 先给 addr1 铸造一些代币用于测试转账
    await myToken.connect(owner).mint(addr1.address, 1000);
    });

    it("应该允许用户转账代币", async function () {
    await expect(myToken.connect(addr1).transfer(addr2.address, 300))
    .to.emit(myToken, "Transfer")
    .withArgs(addr1.address, addr2.address, 300);

    expect(await myToken.balanceOf(addr1.address)).to.equal(700);
    expect(await myToken.balanceOf(addr2.address)).to.equal(300);
    });

    it("转账金额不能超过余额", async function () {
    await expect(
    myToken.connect(addr1).transfer(addr2.address, 1500)
    ).to.be.revertedWithCustomError(myToken, "ERC20InsufficientBalance");
    });
    });
    });

    5.2 运行测试

    在项目根目录下运行:

    npx hardhat test

    如果一切正常,你将看到所有测试用例通过。

    6. 部署到测试网

    让我们将合约部署到 Sepolia 测试网。

    6.1 配置网络和私钥

  • 在 hardhat.config.js 中添加 Sepolia 网络配置。
  • 从 Alchemy 或 Infura 获取一个 Sepolia RPC URL。
  • 将你的测试账户私钥(切勿使用主网私钥!)添加到环境变量或配置中。
  • 一个简化的 hardhat.config.js 示例:

    require("@nomicfoundation/hardhat-toolbox");
    require("dotenv").config();

    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
    solidity: "0.8.20",
    networks: {
    sepolia: {
    url: process.env.SEPOLIA_RPC_URL || "",
    accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    },
    };

    6.2 编写部署脚本

    在 scripts/ 目录下创建 deploy.js:

    async function main() {
    const [deployer] = await ethers.getSigners();
    console.log("使用账户部署合约:", deployer.address);

    const MyToken = await ethers.getContractFactory("MyToken");
    // 部署时将部署者地址作为 initialOwner 传入
    const myToken = await MyToken.deploy(deployer.address);

    await myToken.waitForDeployment();

    console.log("MyToken 合约已部署到地址:", await myToken.getAddress());
    console.log("代币名称:", await myToken.name());
    console.log("代币符号:", await myToken.symbol());
    console.log("合约所有者:", await myToken.owner());
    }

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

    6.3 执行部署

    运行以下命令进行部署:

    npx hardhat run scripts/deploy.js –network sepolia

    部署成功后,控制台会输出合约地址。你可以使用 Etherscan Sepolia 查看你的合约。

    7. 与合约交互

    部署后,你可以通过 Hardhat Console 或编写前端 DApp 与合约交互。

    7.1 使用 Hardhat Console 铸造和转账

    npx hardhat console –network sepolia

    在控制台中:

    // 连接到已部署的合约
    const MyToken = await ethers.getContractFactory("MyToken");
    const myToken = await MyToken.attach("YOUR_DEPLOYED_CONTRACT_ADDRESS");

    // 获取当前账户(部署者)
    const [owner, addr1] = await ethers.getSigners();

    // 所有者铸造 1000 MTK 给 addr1
    const tx = await myToken.mint(addr1.address, ethers.parseUnits("1000", 18));
    await tx.wait();
    console.log("铸造成功!");

    // 检查 addr1 的余额
    const balance = await myToken.balanceOf(addr1.address);
    console.log("addr1 余额:", ethers.formatUnits(balance, 18), "MTK");

    // addr1 向 owner 转账 500 MTK
    const tx2 = await myToken.connect(addr1).transfer(owner.address, ethers.parseUnits("500", 18));
    await tx2.wait();
    console.log("转账成功!");

    7.2 前端集成(简要)

    你可以使用 ethers.js 或 web3.js 库在网页中连接用户钱包(如 MetaMask),并调用合约的 mint 和 transfer 函数。核心逻辑与上述控制台脚本类似。

    8. 安全注意事项与最佳实践

  • 使用经过审计的库:始终优先使用 OpenZeppelin 等成熟库,避免重复造轮子。
  • 权限控制:像 mint 这样的敏感操作必须严格限制访问权限(如 onlyOwner)。
  • 正确处理小数:ERC-20 使用 decimals 变量处理小数,默认是 18。在交互时,注意单位转换(如 parseUnits, formatUnits)。
  • 充分测试:覆盖正常路径、边界情况和异常情况(如余额不足、未授权访问)。
  • 考虑升级性:如果未来可能需要修复 bug 或添加功能,可以考虑使用可升级合约模式(如 OpenZeppelin Upgrades)。
  • 主网部署前:务必在测试网上进行全面测试,并考虑进行第三方审计。
  • 9. 总结

    通过本文,你已完成了一个具备铸造和转账功能的完整 ERC-20 代币项目。你学会了:

    • ERC-20 标准的核心。
    • 基于 OpenZeppelin 编写安全合约。
    • 使用 Hardhat 进行开发、测试和部署。
    • 实现并测试了核心的铸造与转账逻辑。

    下一步,你可以尝试为代币添加更多功能,如销毁(Burn)、暂停(Pausable)、快照(Snapshot)或实现代币销售(ICO)逻辑。区块链开发的世界广阔而有趣,祝你探索愉快!

    相关资源:

    • OpenZeppelin Contracts Documentation
    • Hardhat Documentation
    • Ethereum Official Website
    • Solidity Documentation
    赞(0)
    未经允许不得转载:171主机测评 » ERC-20 代币实战:从铸造到转账的完整开发指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址