本文深入解析了ERC-20标准,这是以太坊区块链上用于创建和管理代币的核心标准。文章详细介绍了ERC-20标准的主要功能、关键函数及其实现方式,同时探讨了在代币开发中可能遇到的挑战和安全问题
参考:github.com/AmazingAng/WTF-Solidity
ERC20
是以太坊上的代币标准,它实现了游戏代币转账的基本逻辑:
IERC20
是ERC20
代币标准的接口合约,规定了ERC20
代币需要实现的函数和事件。
在接口函数中,只需要定义函数名称,输入参数,输出参数。后续将在接口实现合约当中完成接口的代码业务逻辑。
IERC20
定义了2
个事件:Transfer
事件和Approval
事件,分别在转账和授权时被释放
/**
* @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时.
* param (address , address , uint256)
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时.
* param (address , address , uint256)
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
IERC20
定义了6
个函数,提供了转移代币的基本功能,并允许代币获得批准,以便其他链上第三方使用。
totalSupply()
用于返回代币总供给
/**
* @dev 返回代币总供给.
* param
* return uint256 代币总供给
*/
function totalSupply() external view returns (uint256);
balanceOf()
用于返回账户余额
/**
* @dev 返回账户`account`所持有的代币数.
* param address 账户地址
* return uint256 账户余额
*/
function balanceOf(address account) external view returns (uint256);
transfer()
代币转账
/**
* @dev 转账 `amount` 单位代币,从调用者账户到另一账户 `to`.
* param (address,uint256) (接收地址,转账金额)
* return bool
* 如果成功,返回 `true`.
*
* 释放 {Transfer} 事件.
*/
function transfer(address to, uint256 amount) external returns (bool);
allowance()
返回授权额度
/**
* @dev 返回`owner`账户授权给`spender`账户的额度,默认为0。
* param (address , address) (授权账户 , 接收账户 )
* return uint256 授权额度
* 当{approve} 或 {transferFrom} 被调用时,`allowance`会改变.
*/
function allowance(address owner, address spender) external view returns (uint256);
approve()
代币授权
/**
* @dev 调用者账户给`spender`账户授权 `amount`数量代币。
* param (address , uint256) (目标账户,授权额度)
* return bool
* 如果成功,返回 `true`.
*
* 释放 {Approval} 事件.
*/
function approve(address spender, uint256 amount) external returns (bool);
transferFrom()
授权转账
/**
* @dev 通过授权机制,从`from`账户向`to`账户转账`amount`数量代币。转账的部分会从调用者的`allowance`中扣除。
* param (address , address , amount) (转账账户 , 目标账户,转账金额)
* return bool
* 如果成功,返回 `true`.
*
* 释放 {Transfer} 事件.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
IERC20Errors
定义了6个错误,帮助我们在实现代码业务逻辑时捕获错误异常
ERC20InsufficientBalance
错误在转账错误时候触发,表明发送方余额不足
/**
* @dev 表示与 “发送方 ”当前 “余额 ”有关的错误。用于转账
* param (address , uint256,uint256) (转账账户 , 账户余额 ,转账金额)
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
ERC20InvalidSender
错误在转账错误时候触发,多用于零地址发送转账,即发送方为零地址
/**
* @dev 表示代币 “发送 ”失败。用于转账。
* param (address) (转账地址)
*/
error ERC20InvalidSender(address sender);
ERC20InvalidReceiver
错误在转账错误时候触发,多于与向零地址发送转账。即接收方为零地址
/**
* @dev 表示代币 “接收 ”失败。用于转账
* param (address) (接收地址)
*/
error ERC20InvalidReceiver(address receiver);
ERC20InsufficientAllowance
错误多用于检查授权额度时候触发,表明支出人spender的可支配额度不足以消耗此次转账
/**
* @dev 表示 “spender ”的 “allowance ”失败。用于转账.
* param (address , uint256 , uint256) (支出人,授权额度,转账金额)
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
ERC20InvalidApprover
错误在授权的时候触发,多用于零地址向其他账户授权token
/**
* @dev 表示授权token的 `approver` 失败。用于approve
* param (address) (授权账户)
*/
error ERC20InvalidApprover(address approver);
ERC20InvalidSpender
错误在授权的时候触发,多用于向零地址授权token
/**
* @dev 表示授权token的 `spender` 失败。用于approve。
* param (address) (支出账户)
*/
error ERC20InvalidSpender(address spender);
现在我们写一个ERC20
,将IERC20
规定的函数实现。
我们需要状态变量来记录账户余额,授权额度和代币信息。其中balanceOf
, allowance
和totalSupply
为public
类型,会自动生成一个同名getter
函数,实现IERC20
规定的balanceOf()
, allowance()
和totalSupply()
。而name
, symbol
, decimals
则对应代币的名称,代号和小数位数。
注意:用override
修饰public
变量,会重写继承自父合约的与变量同名的getter
函数,比如IERC20
中的balanceOf()
函数。
mapping(address => uint256) public override balanceOf;
mapping(address => mapping(address => uint256)) public override allowance;
uint256 public override totalSupply; // 代币总供给
string public name; // 名称
string public symbol; // 代号
uint8 public decimals = 18; // 小数位数
constructor(string memory name_, string memory symbol_){
name = name_;
symbol = symbol_;
}
update()
函数:在转账时候更新代币状态变量
从 from
到 to
转移 value
数量的代币,或者,如果 from
(或 to
是零地址),则进行mint(或burn)。或 to
)为零地址时,则可使用mint(或burn)。
function _update(address from , address to , uint256 value) internal {
if (from == address(0)){
//from 为零地址 代表代币新铸造 非用户之间转账
//溢出检查
totalSupply += value;
}else {
uint256 fromBalance = balanceOf[from];
if (fromBalance < value){
//发送方余额小于转账金额 触发ERC20InsufficientBalance错误
revert ERC20InsufficientBalance(from,fromBalance,value);
}
//溢出已检查 发送方余额足够 使用unchecked节省gas费
unchecked{
balanceOf[from] = fromBalance - value;
}
}
if (to == address(0)){
//溢出已在from代码校验检查
unchecked{
totalSupply -= value;
}
}else {
unchecked{
balanceOf[to] += value;
}
}
//触发转账事件
emit Transfer(from, to, value);
}
_mint()
函数:铸造代币function _mint(address account , uint256 value)internal {
//地址检查 mint铸造接收方不应该是零地址 捕获错误ERC20InvalidReceiver()
if (account == address(0)){
revert ERC20InvalidReceiver(address(0));
}
//mint也可视作代币之间的转账 由零地址向接收方转账,所以底层调用update()更新代币状态
_update(address(0), account, value);
}
_burn()
函数:销毁代币function _burn(address account , uint256 value)internal {
//地址检查 burn销毁发送方不应是零地址 捕获错误ERC20InvalidSender()
if (account == address(0)){
revert ERC20InvalidSender(account);
}
//burn也可视作代币之间的转账,由发送方向零地址转账,代币进入黑洞,底层调用_update()更新代币状态
_update(account, address(0), value);
}
_transfer()
函数:底层转账函数function _transfer(address from , address to , uint256 value) internal {
//transfer底层逻辑,地址校验
if ( from == address(0)){
revert ERC20InvalidSender(address(0));
}
if (to == address (0)){
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
_approve()
函数:底层授权函数授权函数即更改授权其他用户可供支配自己账户余额的额度
function _approve(address owner , address spender , uint256 value , bool emitEvent)internal {
//地址校验
if (owner == address(0)){
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)){
revert ERC20InvalidSpender(address(0));
}
allowance[owner][spender] = value;
//判断是否需要释放授权事件
if (emitEvent){
emit Approval(owner, spender, value);
}
注意:如果是spender在消耗owner的余额,是不用释放授权事件的,所以在这里引入emitEvent
参数区分不同场景。
_spendAllowance()
函数:消耗授权额度 function _spendAllowance(address owner , address spender , uint256 value)internal {
//获取当前授权额度
uint256 currentAllowance = allowance[owner][spender];
if (currentAllowance != type(uint256).max){
//额度校验 捕获ERC20InsufficientAllowance异常
if (currentAllowance < value){
revert ERC20InsufficientAllowance(spender , currentAllowance , value);
}
//更新授权额度 不用释放授权事件
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
transferFrom()
函数:spender
转账owner
代币function transferFrom(address from , address to , uint256 amount)public returns(bool){
address spender = msg.sender;
//更新授权额度
_spendAllowance(from, spender, amount);
//执行转账逻辑
_transfer(from, to, amount);
return true;
}
transfer()
函数:owner
转账代币 function transfer(address to , uint256 amount) public returns (bool){
address owner = msg.sender;
//执行转账逻辑
_transfer(owner, to, amount);
return true;
}
approve()
函数:owner
授权spender
代币 function approve(address spender , uint256 value) public returns (bool){
address owner = msg.sender;
//执行授权逻辑
_approve(owner, spender, value, true);
return true;
}
新建Token.sol
文件,继承ERC20
合约
contract Token is ERC20 {
address private _owner ;
constructor(address owner_,string memory name_, string memory symbol_)ERC20(name_,symbol_){
_owner = owner_;
}
modifier onlyOwner() {
require(msg.sender == _owner);
_;
}
function mint(address to , uint256 amount)public onlyOwner{
_mint(to, amount);
}
function burn(address to , uint256 amount) public onlyOwner{
_burn(to, amount);
}
}
<!--StartFragment--> 总结: 文章全面解析ERC-20标准,介绍了该标准的核心功能和实现细节。ERC-20标准作为以太坊区块链上的通用代币接口,定义了一系列规范,使得代币可以在不同的应用和平台之间互操作。文章深入探讨了ERC-20标准中的关键函数及其作用,并强调了代币开发中需要注意的安全性和兼容性问题。通过对这些内容的理解,读者可以更好地掌握如何在以太坊上创建和管理自己的代币,以及如何在实际应用中有效利用ERC-20标准。
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!