{"content":{"title":"ERC20 代币","body":"我们现在准备好创建一个 ERC20 代币了！\r\n\r\nERC20 代币通常有一个 **名称** 和一个 **符号**。例如，ApeCoin 的名称是 “ApeCoin”，符号是 “APE”。代币的名称一般不会改变，因此我们会在构造函数中设置它，并且不提供任何函数让它在后续改变。我们将这些变量设为公开，以便任何人都可以查看合约的名称和符号。 \r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n    }\r\n}\r\n```\r\n\r\n接下来，我们需要储存每个人的余额。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n    }\r\n}\r\n```\r\n\r\n我们说 “balanceOf” 是因为这部分是 ERC20 的 [规范](https://eips.ethereum.org/EIPS/eip-20)。ERC20 作为一个规范意味着人们可以在你的合约上调用 “balanceOf” 函数，提供一个地址，并获取该地址拥有多少代币。\r\n\r\n现在每个人的余额都是零，所以我们需要一种方法来创造代币。我们将允许一个特殊地址，即部署合约的人，自由创造代币。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        balanceOf[owner] += amount;\r\n    }\r\n}\r\n```\r\n\r\n通常，函数 mint() 接受 _to_ 和 _amount_ 作为参数。它允许合约部署者将代币铸造到其他账户。为了简单起见，函数 mint() 只是允许部署者将代币铸造到他的账户中。\r\n\r\n为了跟踪现存的代币数量，ERC20 规范要求有一个公共函数或变量叫 **totalSupply**，用来告诉我们已有多少代币被创造出来。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public {\r\n        require(balanceOf[msg.sender] >= amount, \"you aint rich enough\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[msg.sender] -= amount;\r\n        balanceOf[to] += amount;\r\n    }\r\n}\r\n```\r\n\r\n如果你在钱包中使用过 ERC20 代币，毫无疑问，你见过你拥有一定数量的代币的情况。即使无符号整数没有小数，这种情况是如何发生的呢？\r\n\r\n一个 uint256 能表示的最大数字是\r\n\r\n115792089237316195423570985008687907853269984665640564039457584007913129639935\r\n\r\n让我们稍微简化这个数字，以便更清楚。\r\n\r\n10000000000000000000000000000000000000000000000000000000000000000000000000000\r\n\r\n为了能够描述 “decimals”，我们说右侧的 18 个零是代币的分数部分。\r\n\r\n10000000000000000000000000000000000000000000000000000000000.000000000000000000\r\n\r\n因此，如果我们的 ERC20 具有 18 个小数，我们最多可以拥有\r\n\r\n10000000000000000000000000000000000000000000000000000000000\r\n\r\n完整代币，右侧的零是小数。这是 10 八十亿（octodecillion）代币，或对于那些不熟悉如此庞大的数字的人，这相当于 1 千兆 x 1 千兆 x 1 千兆 x 1 万亿。\r\n\r\n10 八十亿应该足够大多数应用程序使用，包括那些经历恶性通货膨胀的国家。\r\n\r\n货币的 “单位” 仍然是整数，但单位现在是非常小的数值。\r\n\r\n18 个小数位是相当标准的，但一些代币使用 6 个小数位。\r\n\r\n代币的小数位不应改变，它只是一个返回代币有多少小数位的函数。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    constructor(string memory _name, string memory _symbol, uint8 decimals) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n}\r\n```\r\n\r\n如果你在注意，我在这里给你抛出了一个难题。数字类型是 uint8，而不是 uint256。uint8 最多只能表示到 255。然而，uint256 有 77 个零（如果你想数一数上面的零，你可以验证这一点）。因此，如果你想要拥有一个完整的代币，最多不能有超过 77 位小数。因此，标准规定我们使用 uint8，因为小数的数量永远不会非常大。\r\n\r\n**转账**\r\n\r\n现在让我们把我们的转账函数加回来。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public {\r\n        require(balanceOf[msg.sender] >= amount, \"you aint rich enough\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[msg.sender] -= amount;\r\n        balanceOf[to] += amount;\r\n    }\r\n}\r\n```\r\n\r\n啊，我们偷偷加了一行代码： **require(to != address(0), \"cannot send to address(0)\")**\r\n\r\n这是为什么呢？因为没有人“拥有”零地址，所以发到零地址的代币是不可花费的。按照约定，将代币发送到零地址应该减少 **totalSupply**，所以我们希望有一个单独的函数来处理这个问题。\r\n\r\n现在我们引入一个 **allowance** 的概念。\r\n\r\n**Allowance**\r\n\r\nAllowance 允许一个地址支出其他人的代币，最多到他们指定的限制。\r\n\r\n为什么你要允许某人代替你花费代币？这是一个很长的故事，不过简单来说，想想你是如何“知道”某人转账给你 ERC20 代币的。发生的只是一个函数被执行，映射的值被改变。你并没有“收到”这些代币，它们只是与你的地址相关联。\r\n\r\n然而，智能合约无法这样做。\r\n\r\n智能合约作为接收转账的既定模式是允许智能合约拥有一定的 allowance，然后告知该智能合约从你的账户中提取余额。\r\n\r\n当你想将代币转移到智能合约时，通常的方法是首先批准智能合约从你的帐户中提取一定数量的代币。然后，你指示智能合约从你的帐户中提取已批准数量的代币。这是在智能合约中用于启用代币转移到合约的常见模式。\r\n\r\n让我们添加 allowance 跟踪器，以及给其他用户授权的方式。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    // owner -> spender -> allowance\r\n    // 这使得一个拥有者可以给予多个地址的授权\r\n    mapping(address => mapping(address => uint256)) public allowance;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public {\r\n        require(balanceOf[msg.sender] >= amount, \"you aint rich enough\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[msg.sender] -= amount;\r\n        balanceOf[to] += amount;\r\n    }\r\n\r\n    // 刚刚添加的\r\n    function approve(address spender, uint256 amount) public {\r\n        allowance[msg.sender][spender] = amount;\r\n    }\r\n}\r\n```\r\n\r\n在这一行 allowance\\[msg.sender\\]\\[spender\\] = amount; 中，spender 指的是正被 msg.sender 授权的账户地址。msg.sender 是在其账户中给 spender 花费一定数量代币的权限。\r\n\r\n因此，msg.sender 是代币的拥有者，而 spender 是被拥有者批准可以代表他们消费一定数量代币的帐户。\r\n\r\n但是，我们没有办法实际使用我们所给予的授权，授权就停在那里！这就是 transferFrom 的作用。\r\n\r\n**transferFrom**\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n    uint256 public totalSupply;\r\n\r\n    // owner -> spender -> allowance\r\n    // 这使得一个拥有者可以给予多个地址的授权\r\n    mapping(address => mapping(address => uint256)) public allowance;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public {\r\n        require(balanceOf[msg.sender] >= amount, \"you aint rich enough\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[msg.sender] -= amount;\r\n        balanceOf[to] += amount;\r\n    }\r\n\r\n    function approve(address spender, uint256 amount) public {\r\n        allowance[msg.sender][spender] = amount;\r\n    }\r\n\r\n    // 刚刚添加的\r\n    function transferFrom(address from, address to, uint256 amount) public {\r\n        require(balanceOf[from] >= amount, \"not enough money\");\r\n\r\n        if (msg.sender != from) {\r\n            require(allowance[from][msg.sender] >= amount, \"not enough allowance\");\r\n            allowance[from][msg.sender] -= amount;\r\n        }\r\n\r\n        balanceOf[from] -= amount;\r\n        balanceOf[to] += amount;\r\n    }\r\n}\r\n```\r\n\r\n让我们详细拆解一下我们刚才做的事情。\r\n\r\n首先，代币的拥有者是可以调用 transferFrom 的。在这种情况下，allowance 是没有意义的，因此我们不必检查 allowance 映射，而是相应地更新余额。\r\n\r\n否则，我们需要检查花费者是否获得足够的 allowance，然后减少他们的支出。如果我们不去减去他们的支出，我们将拥有无限的支出能力。\r\n\r\n还有最后一个清理工作。如果我们查看 [EIP 20](https://eips.ethereum.org/EIPS/eip-20) 的原始规范，它说明 approve、transfer 和 transferFrom 必须在成功后返回 true。所以我们来加上这一点。\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    // owner -> spender -> allowance\r\n    // 这使得一个拥有者可以给予多个地址的授权\r\n    mapping(address => mapping(address => uint256)) public allowance;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public returns (bool) {\r\n        require(balanceOf[msg.sender] >= amount, \"you aint rich enough\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[msg.sender] -= amount;\r\n        balanceOf[to] += amount;\r\n\r\n        return true;\r\n    }\r\n\r\n    function approve(address spender, uint256 amount) public returns (bool) {\r\n        allowance[msg.sender][spender] = amount;\r\n        return true;\r\n    }\r\n\r\n    function transferFrom(address from, address to, uint256 amount) public returns (bool) {\r\n        require(balanceOf[from] >= amount, \"not enough money\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n\r\n        if (msg.sender != from) {\r\n            require(allowance[from][msg.sender] >= amount, \"not enough allowance\");\r\n            allowance[from][msg.sender] -= amount;\r\n        }\r\n\r\n        balanceOf[from] -= amount;\r\n        balanceOf[to] += amount;\r\n\r\n        return true;\r\n    }\r\n}\r\n```\r\n\r\n冒着过多信息给你的风险，有一个小的清理工作可以完成。注意到 **transferFrom** 和 **transfer** 中有重复的代码。我们该怎么解决呢？我们可以将余额更新的代码提取到一个单独的函数中，但我们需要确保该函数不是 public 的以免有人盗取代币！\r\n\r\n```solidity\r\ncontract ERC20 {\r\n    string public name;\r\n    string public symbol;\r\n\r\n    mapping(address => uint256) public balanceOf;\r\n    address public owner;\r\n    uint8 public decimals;\r\n\r\n    uint256 public totalSupply;\r\n\r\n    // owner -> spender -> allowance\r\n    // 这使得一个拥有者可以给予多个地址的授权\r\n    mapping(address => mapping(address => uint256)) public allowance;\r\n\r\n    constructor(string memory _name, string memory _symbol) {\r\n        name = _name;\r\n        symbol = _symbol;\r\n        decimals = 18;\r\n\r\n        owner = msg.sender;\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public {\r\n        require(msg.sender == owner, \"only owner can create tokens\");\r\n        totalSupply += amount;\r\n        balanceOf[owner] += amount;\r\n    }\r\n\r\n    function transfer(address to, uint256 amount) public returns (bool) {\r\n        return helperTransfer(msg.sender, to, amount);\r\n    }\r\n\r\n    function approve(address spender, uint256 amount) public returns (bool) {\r\n        allowance[msg.sender][spender] = amount;\r\n        return true;\r\n    }\r\n\r\n    function transferFrom(address from, address to, uint256 amount) public returns (bool) {\r\n        if (msg.sender != from) {\r\n            require(allowance[from][msg.sender] >= amount, \"not enough allowance\");\r\n            allowance[from][msg.sender] -= amount;\r\n        }\r\n\r\n        return helperTransfer(from, to, amount);\r\n    }\r\n\r\n    function helperTransfer(address from, address to, uint256 amount) internal returns (bool) {\r\n        require(balanceOf[from] >= amount, \"not enough money\");\r\n        require(to != address(0), \"cannot send to address(0)\");\r\n        balanceOf[from] -= amount;\r\n        balanceOf[to] += amount;\r\n\r\n        return true;\r\n    }\r\n}\r\n```\r\n\r\n干得更漂亮了！\r\n\r\n练习题\r\n\r\n- 修改上述代码，使其不允许超过 100 万代币流通，即使拥有者尝试铸造更多。\r\n\r\n###  学习更多\r\n\r\n请查看 [区块链训练营](https://learnblockchain.cn/openspace/1)，学习更多关于智能合约开发和代币标准的信息。\r\n\r\n \r\n\r\n>- 原文链接： [rareskills.io/learn-soli...](https://www.rareskills.io/learn-solidity/erc20-tuotrial)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/20722","address":null},"history":null,"timestamp":1740576581,"version":1}