Cover photo

用Solidity构造ERC20代币

0x01 ERC20简介

ERC20 token是以太坊用于制造同质化代币(Fungible Token)的协议,每一个token都是等于其他token的。ERC20 token可以用于交换货币、投票权、质押等媒介。

OpenZeppelin提供了许多ERC20关联合约,详见https://docs.openzeppelin.com/contracts/4.x/erc20

0x02ERC20合约解读

合约链接https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol

1、Functions

totalSupply()

返回代币总供给

function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

balanceOf(address account) → uint256

返回账户拥有的剩余token

function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

transfer(address to, uint amount) → bool

转账 amount 单位代币,从调用者账户到另一账户 to,成果则返回 true。释放{transfer}事件。

function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

allowance(address owner, address spender) → uint256

返回owner对spender的授权额度,默认为0。当{approve} 或 {transferFrom} 被调用时,allowance会改变。

function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

approve(address spender, uint256 amount) → bool

调用者账户给`spender`账户授权 `amount`数量代币,如果成果则返回true,释放Approval事件。

function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

transferFrom(address from, address to, uint256 amount) → bool

从from地址向to地址转账amount数量token。转账的部分会从调用者的`allowance`中扣除。

function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

2、Events

定义在IREC20.sol中https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol

Transfer(address from, address to, uint256 value)

当 ‘value’ 单位的货币从账户 (from) 转账到另一账户 (to)时被释放

event Transfer(address indexed from, address indexed to, uint256 value);

Approval(address indexed owner, address indexed spender, uint256 value)

当 ‘value’ 单位的货币从账户 (owner) 授权给另一账户 (spender)时被释放

event Approval(address indexed owner, address indexed spender, uint256 value);

0x03 构建ERC20代币

pragma solidity ^0.8.0;

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

contract YXYToken is ERC20{
    constructor(uint256 _initalSupply) ERC20("LFToken","LFT"){
        _mint(msg.sender,_initalSupply); //铸造token
    }
}

铸造ERC20 Token通常需要使用继承方法。、

这里_initSupply代表初始化挖掘的Token数量,LFToken和LFT是我们ERC20 token的名称和缩写。_mint(msg.sender,_initalSupply) 表示对合约调用者铸造_initSupply 这么多个token。

如下图我们成果创建了100个LFT。

在Remix上部署智能合约并铸造100个Token
在Remix上部署智能合约并铸造100个Token
查看当前账户余额有100个Token
查看当前账户余额有100个Token

默认情况下ERC20使用小数点后18位,如果需要更改,可以重写decimals()

function decimals() public view virtual override returns (uint8) {
  return 16;
}

0x04 Token供应

1、修改供给数

在OpenZeppelin v1中使用如下方法修改totalSupply的数量。

contract ERC20FixedSupply is ERC20 {
    constructor() {
        totalSupply += 1000;
        balances[msg.sender] += 1000;
    }
}

但我们必须手动使修改后的totalSupply与修改后的余额保持同步。于是V2改进使用了封装的方法。_totalSupply_balance被设为私有的,不能直接写入。

\_balance和\_totalSupply
\_balance和\_totalSupply

于是V2开始使用了一个internal _mint() 函数来做此操作

contract ERC20FixedSupply is ERC20 {
    constructor() ERC20("Fixed", "FIX") {
        _mint(msg.sender, 1000);
    }
}

2、挖矿奖励

在 Solidity 中,我们可以在全局变量 block.coinbase中访问当前区块的矿工地址。我们在只要访问mintMinerReward() 就可以获得挖token的奖励。

contract ERC20WithMinerReward is ERC20 {
    constructor() ERC20("Reward", "RWD") {}

    function mintMinerReward() public {
        _mint(block.coinbase, 1000);
    }
}

3、模块化机制

ERC20PresetMinterPauser 合约中已有包含。这是一种通用机制,其中一组帐户被分配了 minter 角色,授予他们调用 mint 函数(_mint 的外部版本)的权限。

这个机制可以被用在中心化挖矿中,一个外部账户决定给谁创建多少供应。这个机制有合法化的使用案例,就是传统资产支持稳定币(traditional asset-backed stablecoins)

contract MinerRewardMinter {
    ERC20PresetMinterPauser _token;

    constructor(ERC20PresetMinterPauser token) {
        _token = token;
    }

    function mintMinerReward() public {
        _token.mint(block.coinbase, 1000);
    }
}

4、自动化奖励

ERC20 还允许我们通过 _beforeTokenTransfer hook扩展代币的核心功能。我们可以使用hook为区块链中包含的每个代币传输铸造矿工奖励。

contract ERC20WithAutoMinerReward is ERC20 {
    constructor() ERC20("Reward", "RWD") {}

    function _mintMinerReward() internal {
        _mint(block.coinbase, 1000);
    }

    function _beforeTokenTransfer(address from, address to, uint256 value) internal virtual override {
        //判断调用函数的是创世区块,以及发送奖励的地址是矿工
                if (!(from == address(0) && to == block.coinbase)) {
          _mintMinerReward();
        }
        super._beforeTokenTransfer(from, to, value);
    }
}