深入浅出,如何编写一个以太坊ERC20代币合约

在区块链的世界里,特别是以太坊生态系统中,ERC20代币标准无疑是最具影响力和广泛应用的协议之一,它定义了一套统一的接口(Interface),使得各种代币能够在以太坊网络上互操作,就像比特币或以太坊本身一样可以被轻松交易、转移和集成到各种去中心化应用(DApps)和交易所中,本文将带你一步步了解如何编写一个基本的以太坊ERC20代币合约。

什么是ERC20

ERC20是“Ethereum Request for Comments 20”的缩写,即以太坊社区提出的第20号改进提案,它不是一个具体的合约,而是一套标准,规定了以太坊上代币合约必须实现的一组方法和事件,主要包括:

  • 方法(Functions)
    • name(): 返回代币名称,"MyToken"。
    • symbol(): 返回代币符号,"MTK"。
    • decimals(): 返回代币小数位数,用于表示代币的最小单位。
    • totalSupply(): 返回代币的总供应量。
    • balanceOf(address): 查询指定地址的代币余额。
    • transfer(address, uint256): 向指定地址转移指定数量的代币。
    • transferFrom(address, address, uint256): 从一个地址转移到另一个地址(通常需要授权)。
    • approve(address, uint256): 授权另一个地址可以调用你的transferFrom方法,最多可转移数量。
    • allowance(address, address): 查询一个地址对另一个地址的授权额度。
  • 事件(Events)
    • Transfer(address indexed from, address indexed to, uint256 value): 当代币转移时触发。
    • Approval(address indexed owner, address indexed spender, uint256 value): 当授权额度设置或修改时触发。

遵循这些标准,确保了代币的兼容性和易用性。

编写一个简单的ERC20代币合约

我们将使用Solidity语言,这是以太坊智能合约的主要编程语言,在开始之前,请确保你已经安装了必要的开发环境,如Solidity编译器(solc)和Truffle或Hardhat等开发框架,或者使用在线的Solidity IDE(如Remix IDE)。

下面是一个基本的ERC20代币合约示例,我们将逐步解释其组成部分。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 1000000 * 10**decimals()); // 初始发行100万个代币,考虑小数位数
    }
}

如果你希望完全手动实现ERC20接口(而不是继承OpenZeppelin的实现,虽然在实际生产中推荐使用OpenZeppelin的安全审计代码),可以这样写:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract MyToken is IERC20 {
    string private _name;
    string private _symbol;
    uint8 private _decimals;
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
        _decimals = 18; // 默认小数位数为18,与以太坊一致
        _totalSupply = 1000000 * (10**uint256(_decimals)); // 初始供应量
        _balances[msg.sender] = _totalSupply; // 将初始供应量全部转给合约部署者
        emit Transfer(address(0), msg.sender, _totalSupply); // 触发Transfer事件,0地址表示代币的创世
    }
    function name() public view override returns (string memory) {
        return _name;
    }
    function symbol() public view override returns (string memory) {
        return _symbol;
    }
    function decimals() public view override returns (uint8) {
        return _decimals;
    }
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }
    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }
    function allowance(address owner, address spender) public view override returns (uint256) {
        return _allowances[owner][spender];
    }
    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }
    function transferFrom(address sender, address recipient, uint256 amount) public
随机配图
override returns (bool) { uint256 currentAllowance = _allowances[sender][msg.sender]; require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); _approve(sender, msg.sender, currentAllowance - amount); _transfer(sender, recipient, amount); return true; } function _transfer(address sender, address recipient, uint256 amount) internal { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); require(_balances[sender] >= amount, "ERC20: transfer amount exceeds balance"); _balances[sender] -= amount; _balances[recipient] += amount; emit Transfer(sender, recipient, amount); } function _approve(address owner, address spender, uint256 amount) internal { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } function mint(address to, uint256 amount) internal { require(to != address(0), "ERC20: mint to the zero address"); _totalSupply += amount; _balances[to] += amount; emit Transfer(address(0), to, amount); } }

合约代码解析(以手动实现为例)

  1. SPDX-License-Identifier 和 pragma solidity:

    • SPDX-License-Identifier: MIT: 指定了合约的许可证类型,MIT是一种宽松的开源许可证。
    • pragma solidity ^0.8.20;: 指定编译器版本,^0.8.20表示使用0.8.20到0.9.0(不含0.9.0)之间的编译器版本。
  2. 接口定义 (IERC20):

    • 我们定义了IERC20接口,包含ERC20标准要求的所有方法和事件,这确保了我们的合约将实现这些接口。
  3. 合约状态变量:

    • _name, _symbol, _decimals: 存储代币的名称、符号和小数位数。
    • _totalSupply: 存储代币的总供应量。
    • _balances: 一个mapping,存储每个地址的代币余额。
    • _allowances: 一个嵌套的mapping,存储一个地址授权给另一个地址的代币数量。
  4. 构造函数 (constructor):

    • 在合约部署时调用一次,用于初始化代币的基本信息。
    • _name = name_;_symbol = symbol_; 设置代币名称和符号。
    • _decimals = 18; 设置小数位数,这是ERC20最常见的设置。
    • _totalSupply = 1000000 * (10**uint256(_decimals)); 计算初始总供应量,乘以10**decimals是为了将整数转换为最小单位(如果小数位是18,1个代币实际上表示1 * 10^18个最小单位)。
    • _balances[msg.sender] = _totalSupply; 将所有初始代币分配给合约的部署者(msg.sender)。
    • emit Transfer(address(0), msg.sender, _totalSupply); 触发Transfer事件,记录代

本文由用户投稿上传,若侵权请提供版权资料并联系删除!