本文介绍了以太坊上的ERC-20代币标准。首先解释了代币的概念,然后深入探讨了ERC-20标准的目的和功能,包括transfer、balanceOf、totalSupply等关键函数,以及Transfer和Approval事件。最后,通过一个简单的ERC-20合约示例,展示了如何实现minting和burning功能,总结了ERC20代币标准。
在 互联网上创建和部署你自己的 tokens 太容易了。区块链和智能合约使之成为可能。是的,你没听错。你可以在互联网上部署你自己的 tokens。而且不需要像比特币或以太坊那样创建一个全新的区块链。
这些 tokens 仅仅是一段运行在区块链之上的代码。
如果你不了解区块链,请参考这篇 文章,以便更好地理解区块链。
我为什么要写这个?
作为我学习的一部分,我开始学习 token 标准,我面临的问题是缺乏资源,你可以在这里找到关于 token 标准的所有信息。没有一个资源包含关于 token 标准的所有内容。
ERC20 很容易,所以没花多少时间。但是 ERC721 和 ERC1155 花费了太多时间来收集信息并将它们放在一起。
是的,导入 Openzeppelin 合约并创建 tokens 很容易,但是只有当你自己练习时才会学到这些概念。所以,只需阅读这些帖子,然后开始开发和部署你的 tokens。
先决条件
对 Solidity 语言及其工作原理(部署和交互)的了解。
关于接口的知识。 资源
基本上,tokens 是一种加密货币,它运行在区块链之上,具有一些特殊的用例,例如,你可以创建 tokens 来资助你的项目,这被称为 ICO(首次代币发行),类似于股票市场,或者你也可以使用 tokens 来分配一种特定的数字资产,该资产具有特定的价值,除此之外,tokens 可以用来表示某些类型数据的所有权。
它们也被用于治理,如何使用?持有公司特定数量 token 的人有权对公司的决策进行投票。还有更多的用例。你只需要一个部署在区块链上的智能合约。
现在存在一个互操作性问题,因为有数十亿人有不同的想法,如果每个人都创建一种新的 token,那么利用这些 tokens 将变得非常困难。这是因为每个 token 都不过是一个运行在区块链上的智能合约,而智能合约是开发人员编写的代码。不同的开发人员可能会以不同的方式实现代码。整个网络将会一片混乱。因为钱包提供商和交易所将不得不编写自定义代码来与具有不同功能的不同的合约进行通信。
简而言之,为每个合约单独编写自定义代码是不可能的。
为了克服这个问题,我们有 ERC token 标准。
ERC 意味着 Ethereum Request for Comments(以太坊征求意见稿)。这有助于以太坊环境为以太坊区块链的应用层设置规则或标准,并使其更安全和可扩展。不要混淆 ERC 和 EIP,EIP 是以太坊核心功能的提案,而 ERC 是用于应用层的。
ERCs 是 EIPs 的一部分。了解更多。
ERC token 标准有一套预定义的规则,开发人员在开发 token 时应严格遵守。根据需求有不同的 token 标准。它们都有不同的规则集。
Token 标准为你提供了一个接口,在开发相应的 token 合约时必须实现该接口。你也可以根据你的需求添加其他功能,但应实现标准接口。
有很多不同的 ERC token 标准,但我们将讨论一些最受欢迎和更常用的标准。它们是 ERC-20、ERC-721 和 ERC-1155。还有更多的标准正在被提出来改进旧的标准,并具有向后兼容性,我们将在进行中讨论它们。
ERC-20 - 同质化 Tokens
ERC-721 - 非同质化 Tokens
ERC-1155 - 同质化和非同质化 Tokens(也是半同质化)
不知道同质化和非同质化之间的区别?让我们来学习一下。
同质化 tokens 可以定义为可互换的,这意味着如果你和我都有 100 个 tokens,那么我们可以交换它们,但不会有任何改变。没有利润,没有损失。一切都和以前一样。类似于任何法定货币,如美元、欧元、卢比等。
非同质化 tokens(NFTs)恰恰相反。这意味着每个 token 都与其他 token 不同。每个 token 都是独一无二的。因为非同质化 tokens 不仅是数字的表示,而且它们还分配有外部资产,例如,任何图像、绘画、音乐会门票、音乐文件、游戏收藏品等等。这就是为什么它们是独一无二的。
第一个在以太坊网络上提出的标准,也是当我们谈论 token 标准时,每个人都会想到的最著名和最基本的标准是 ERC-20 token 标准。
ERC-20 token 标准于 2015 年提出,用于同质化 tokens。同质化意味着可互换,即每个 token 都与另一个 token 相同。这个标准有九个函数和两个事件,在创建 ERC-20 token 时应该实现这些函数和事件。
ERC-20 具有以下功能:
让我们来看看 ERC-20 接口。
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, 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 from, address to, uint256 amount) external returns (bool);
}
首先,我们将学习这些函数。
前三个函数,即 name()
、symbol()
和 decimal()
被认为是可选的,不包含在 ERC20 合约的接口中。但它们确实很重要,我们不能忽略它们。
1. name() 函数应该返回存储在状态变量中的 token 的名称。
function name()public view returns (string);
2. symbol() 函数应该返回 token 的缩写,这也存储在一个状态变量中。
function symbol()public view returns (string);
3. decimals() 是需要注意的重要函数,因为 Solidity 不支持浮点数,为了执行诸如 1.5、2.4 等值的交易,我们必须将数字分成更小的面额。记住以太币 token 如何可以分解为更小的面额,如 Gwei、Finney 和 Wei。
通常我们分配 18 个小数位,建议这样做。
function decimals()public view returns (uint8);
4. totalSupply() 此函数应返回市场上 token 的总数量。token 的总供应量应该只受两个函数的影响,即铸造或销毁。铸造意味着供应新创建的 tokens,销毁意味着销毁 tokens。我们稍后再讨论这两个函数。
function totalSupply()public view returns (uint256);
5. balanceOf () 接受一个地址作为参数,并返回其 token 余额。它如何知道我的余额?我们在合约中定义了一个映射,用于跟踪我们的余额,每次有 token 交易时,这个映射应该被更新,因为这将是余额的唯一来源。
这是映射的样子:
mapping(address => uint ) internal balance;
balanceOf
函数只是从映射中返回值。
function balanceOf(address _owner)public viewreturns (uint256 balance);
6. transfer() 是用于将 tokens 从一个地址发送到另一个地址的函数。它接受一个 address,tokens 应该被发送到这个地址,以及一个 uint,表示要发送的 tokens 数量,作为参数。它应该从 msg.sender 中扣除 tokens,并将金额添加到接收者。扣除和添加是如何工作的?
正如我之前所说的关于 balance 映射。我们只是简单地减少和增加映射中的 token 数量。
function transfer(address _to, uint256 _value)public returns (bool success);
7. transferFrom() 的作用与 transfer
函数相同,但存在一个区别。此函数用于通过第三方地址将 tokens 从一个地址发送到另一个地址。别人怎么能花费我的 tokens?这安全吗?是的,因为只有在你批准的情况下,任何地址才能花费你的 token。我们如何做到这一点?这就是下一个函数的作用。
function transferFrom(address _from, address _to, uint256 _value)public returns (bool success);
8. approve() 用于批准任何帐户从批准它的帐户中花费 tokens。它接受两个参数,第一个是 spender 的地址,第二个是 spender 允许花费的 tokens 价值。然后我们使用这个输入更新嵌套映射。哪个映射?
批准的映射是这样写的:
mapping (address=> mapping (address => amount)) internal allowed;
function approve(address _spender, uint256 _value)public returns (bool success);
9. allowance() 用于检查已批准的地址可以花费的剩余 tokens。它只是从 allowed
映射中返回数据。
function allowance(address _owner, address _spender)public view returns (uint256 remaining);
这些是在创建 ERC20 token 时要定义的函数。除此之外,我们还有两个事件,必须在所需的时间触发。它们是:
但在实现时,还有两个函数会发出此事件。Mint
和 burn
函数。我们稍后会讨论这些。现在,只需记住在 mint
函数中发出此事件时,发送者地址应为零地址,而在 burn
函数中,接收者应为零地址。
零地址看起来像这样:0x0000000000000000000000000000000000000000
在 Solidity 中,address(0)
表示这个零地址。
event Transfer(address indexed _from, address indexed _to, uint256 _value);
approve
函数中触发此事件。event Approval(address indexed _owner, address indexed _spender, uint256 _value);
每个 ERC20 token 都应该在合约中实现这些函数。
看一下这个简单的已实现的智能合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ERC20Interface {
function allowance(address tokenOwner, address spender) public virtual view returns (uint remaining);
function balanceOf(address tokenOwner) public virtual view returns (uint balance);
function totalSupply() public view virtual returns (uint);
function transferFrom(address from, address to, uint tokens) public virtual returns (bool success);
function transfer(address to, uint tokens) public virtual returns (bool success);
function approve(address spender, uint tokens) public virtual returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
event Supply(address indexed to, uint indexed amount, uint remainingSupply);
}
contract Token is ERC20Interface {
string public name;
string public symbol;
uint8 public decimals;
uint256 public _totalSupply;
uint public maximumSupply;
address public owner;
mapping(address => uint) private balances;
mapping(address => mapping(address => uint)) private allowed;
constructor() {
name = "MyFirstToken";
symbol = "MFT";
decimals = 18;
_totalSupply = 5000000 * 10**18;
maximumSupply = 10000000 * 10**18;
owner= msg.sender;
balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function remainingToken() public view returns (uint remainingSupply) {
return ( maximumSupply - _totalSupply);
}
modifier ownable () {
require (msg.sender== owner," only owner can perform this");
_;
}
function totalSupply() public override view returns (uint) {
return _totalSupply ;
}
function balanceOf(address tokenOwner) public override view returns (uint balance) {
return balances[tokenOwner];
}
function allowance(address tokenOwner, address spender) public override view returns (uint remaining) {
return allowed[tokenOwner][spender];
}
function approve(address spender, uint tokens) public override returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
function transfer(address to, uint tokens) public override returns (bool success) {
balances[msg.sender] = (balances[msg.sender] -= tokens);
balances[to] = (balances[to] += tokens);
emit Transfer(msg.sender, to, tokens);
return true;
}
function transferFrom(address from, address to, uint tokens) public override returns (bool success) {
require(balances[from] >=tokens, "You don't have enough tokens");
require(allowed[from][msg.sender] >=tokens, "You are not allowed to send tokens");
balances[from] = (balances[from] -= tokens);
allowed[from][msg.sender] = (allowed[from][msg.sender] -= tokens);
balances[to] = (balances[to]+= tokens);
emit Transfer(from, to, tokens);
return true;
}
function mint (address to ,uint amount) public ownable {
require(_totalSupply + amount <= maximumSupply,"You are exceeding the maximum supply");
balances[to] += amount;
_totalSupply += amount;
emit Supply(to,amount,remainingToken());
}
function burn (uint amount) public {
require(balances[msg.sender] >= amount, "not enough balance to burn");
balances[msg.sender] -= amount;
emit Supply(msg.sender, amount, remainingToken());
}
}
这是一个非常简单的实现,你可以看到合约 token 从接口继承并实现接口中定义的每个函数。
请注意,还有两个额外的函数 mint
和 burn
。
mint
函数用于铸造新的 tokens。当你看到其中的代码时,你会注意到它增加了任何地址的余额。这意味着有新的 tokens 被铸造到该钱包中。但是我们为什么要在这里发出 transfer 事件?这是因为铸造被认为是 tokens 从零地址转移到给定地址。
现在,burn
函数怎么样?burn
函数实际上是 tokens 的转移,但接收者始终是零地址。为什么呢?零地址被用作以太坊中的黑洞,这意味着不可能猜出零地址的私钥。让我们不要太技术性,只需了解无论何时需要销毁一些 token,即从市场上移除 token,你都会将其发送到此地址,然后,它将永远消失。现在没有人可以访问这些 tokens。这就是零地址持有价值数百万美元的 tokens 的原因。
这就是关于 ERC20 token 标准的全部内容。这非常简单容易。
但这仅适用于同质化 tokens,那么非同质化 tokens NFTs 呢?ERC-721 是 NFTs 的标准。让我们在下一篇文章中讨论这个问题。
- 原文链接: decipherclub.com/token-s...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!