ERC-20是什么?如何在以太坊上发行数字货币(Token)?

    随着上周五到来的好消息,政府认可区块链技术,国内开始大力推广宣传区块链技术,“区块链是什么”甚至上了百度热搜。区块链行业在国内算是迎来了春天,不少大厂也开始纷纷打着自己有区块链业务的旗号开始宣传着自己的企业。但其实真的做的也没几个,咱就当时看戏看着乐吧。因为也不干咱啥事。

    我进入区块链行业快两年了,一直以写以太坊智能合约为主,慢慢着去了解区块链更底层的技术。现在也在努力学习go语言,以后能够写一些关于公链开发和一些优秀的公链源码解析的文章,来帮助大家更快学习和了解区块链和区块链技术。

    我长期“潜伏”在一些以太坊智能合约的qq群,微信群等地方,发现群里任然有很多人在问ERC-20是什么?所以感觉任然有必要写一篇关于ERC-20标准的Token合约开发的文章。所以我大半夜的不睡觉开始写起了这篇博文。不说废话了,说正事。

 

ERC-20是什么?

ERC(Etherum Request for Comments)表示以太坊开发者提交的协议提案,而20表示的是议案的编号。

ERC-20是一个基于以太坊(Ethereum)代币(Token)的接口标准(协议)。任何基于ERC20标准的Token都能立即兼容以太坊钱包,同时支持共享和交换。

我们可以在https://eips.ethereum.org/EIPS/eip-20查看ERC20代币的标准API。

 

看我博文的相信大家基本都是技术,那咱就按技术的路子来走。

首先咱说以太坊智能合约是用solidity语言开发的,solidity是一门高级编程语言,它也支持接口(Interface)类型,那我们就先来码出  IERC20 。

openzeppelin库是一个对合约开发小白入门很好的一个开源库,里面写好了很多了现成的库和一些常用标准的接口,例如咱们今天说的ERC-20,还有另一个常用的ERC-721标准库都能在这里面找到。

pragma solidity ^0.5.0;

interface IERC20 {
    //返回代币的名称-例如"MyToken"。
    function name() public view returns (string);

    //返回代币的符号,也就是代币的简称,例如:MY。
    function symbol() public view returns (string);

    //返回令牌使用的小数位数-常见代币都是18。也就是支持最小以0.000000000000000001为单位交易。
    function decimals() public view returns (uint8);

    //发行代币的总量,可以通过这个函数来获取。所有智能合约发行的代币总量是并不是一定的,有的可以通过后期增发和销毁来调整Token总量。
    function totalSupply() external view returns (uint256);

    //输入有效的账户地址,可以获取该账户中此代币的余额。
    function balanceOf(address _owner) public view returns (uint256 balance);

    //调用transfer函数可以将自己的代币转账给_to地址,_value为转账数量。
    function transfer(address _to, uint256 _value) public returns (bool success);

    //配合approve使用,approve执行成功之后,调用transferFrom函数来转移token,_to可以为任意有效的地址。
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);

    //批准_spender账户从自己的账户转移_value个代币。可以分多次转移。
    function approve(address _spender, uint256 _value) public returns (bool success);

    //返回_spender能从_owner的账户中转移走的代币数量。
    function allowance(address _owner, address _spender) public view returns (uint256 remaining);

    //Transfer事件,当成功转移token时,一定要触发该事件。
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    //Approval事件,执行批准方法时,一定要触发该事件。
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

 

pragma solidity ^0.5.0;

是当前合约所基于的solidity语法版本,需要在合约首部声明。

其实solidity的语法并不复杂,接触过javascript的老铁们一看就会觉得很眼熟。Interface申明了一个IERC20接口,接口里包含了很多的方法,方法是干啥的我也都注释了,这我们可以理解为就是一个ERC-20标准。我们的Token合约只需要继承自这个接口,并实现其里面的方法就算是完成了一个简单的ERC-20代币的开发。

下面我们来具体实现这些接口。我们来新建一个contract 并继承自 IERC20,solidity中继承使用 is。

contract ERC20 is IERC20 {
    using SafeMath for uint256;
    
    string private _name;//代币名称
    string private _symbol;//代币符号,简称
    uint8 private _decimals;//代币小数位数量
    uint256 private _totalSupply;//代币发行总量

    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;

    //totalSupply方法的具体实现,这里返回了一个私有变量_totalSupply,这个值可以直接就是我们的代币总量,这也写的原因是把代币总量的修改隐藏,不能对外随意修改。
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    //balanceOf方法的具体实现,mapping是一个键值对数据结构。
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    //这里是transfer方法的具体实现,也是对_transfer方法的公开,因为我们把transfer的功能实现在了_transfer方法体内。
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

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

    //这里是approve方法的具体实现,也是对approve方法的公开,因为我们把approve的功能实现在了_approve方法体内。
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    //这里是transferFrom方法的具体实现,也是对_transferFrom方法的公开,因为我们把transferFrom的功能实现在了_transferFrom方法体内。
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    //增加批准数量,我们能看到,这里是直接对批准的_allowances所对应的值进行了一个修改。
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    //减少批准数量,批准数量最小为0,不能为负数。solidity本身是没有add()sub()方法的,这是SafeMath库里的方法。
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    //transfer方法的具体实现
    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");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);//这里是触发Transfer事件
    }

    //(挖矿)增发代币,给指定有效地址添加一定数量的代币,因为增发了,所以要修改_totalSupply代币总量值。使用internal 修饰符表示内部函数,根据项目需求选择是否实现公开方法。
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    //(销毁)减少代币,销毁该地址添加一定数量的代币,同样需要修改_totalSupply代币总量值。
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    //approve方法的具体实现
    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);//这里是触发Approval事件
    }

    //销毁的另一种形式,需要批准,A 批准 B 10个代币的转移权,B 可销毁 A 账户下10个代币,同时也要减少 A 对B 的批准转移数量。
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
}

随便附上safemath的具体实现,方便大家学习时使用。使用safemath是为了避免我们在开发中对数值有错误的操作。

pragma solidity ^0.5.0;

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

到此,基本上一个简单的代币合约就开发完成了。有问题可以留言,有建议欢迎指出。

 

Searching...

发布了18 篇原创文章 · 获赞 15 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章