ERC-20 合约概览
简介
以太坊最常见的用途之一是由一个团队来打造一种可以交易 的代币,在某种意义上是他们自己的货币。 这些代币通常遵循一个标准, ERC-20。 此标准使得人们能够以此来开发可以用于所有 ERC-20 代币的工具,如流动资金池和钱包。 在这篇文章中,我们将带领大家分析 OpenZeppelin Solidity ERC20 实现(opens in a new tab)以及 ERC20 接口定义(opens in a new tab)。
这里使用的是附加说明的源代码。 如果想要实现 ERC-20, 请阅读此教程(opens in a new tab)。
接口
像 ERC-20 这样的标准,其目的是允许符合标准的多种代币,都可以在应用程序之间进行互操作,例如钱包和分布式交易所。 为实现这个目的,我们要创建一个 接口(opens in a new tab)。 任何需要使用代币合约的代码 可以在接口中使用相同的定义,并且与使用它的所有代币合约兼容。无论是像 MetaMask 这样的钱包、 诸如 etherscan.io 之类的去中心化应用程序,或一种不同的合约,例如流动资金池。
如果您是一位经验丰富的程序员,您可能记得在 Java(opens in a new tab) 中,甚至在 C 头文件(opens in a new tab) 中看到过类似的构造。
这是来自 OpenZeppelin 的 ERC-20 接口(opens in a new tab) 的定义。 这是将人类可读标准(opens in a new tab)转换为 Solidity 代码。 当然, 接口本身并不定义如何做事。 这一点在下文合约的源代码中作了解释。
1// SPDX-License-Identifier: MIT2复制
Solidity 文件中一般需要标识软件许可证。 您可以在这里看到许可证列表(opens in a new tab)。 如果需要不同的 许可证,只需在注释中加以说明。
1pragma solidity >=0.6.0 <0.8.0;2复制
Solidity 语言仍在迅速地发展,新版本可能不适配旧的代码 (请点击此处查看(opens in a new tab))。 因此,最好不仅指定一个最低的 语言版本,也指定一个最高的版本,即测试过代码的最新版本。
1/**2 * @dev Interface of the ERC20 standard as defined in EIP.3 */4复制
注释中的 @dev
是 NatSpec 格式(opens in a new tab)的一部分,用于 从源代码生成文档。
1interface IERC20 {2复制
根据惯例,接口名称以 I
开头。
1 /**2 * @dev Returns the amount of tokens in existence.3 */4 function totalSupply() external view returns (uint256);5复制
此函数标记为 external
,表示它只能从合约之外调用(opens in a new tab)。 它返回的是合约中代币的总供应量 这个值按以太坊中最常见的类型返回,即无符号的 256 位(256 位是 以太坊虚拟机的原生字长宽度)。 此函数也是视图 view
类型,这意味着它不会改变合约状态,这样它可以在单个节点上执行 ,而不需要在区块链的每个节点上执行。 这类函数不会生成交易,也不会消耗燃料。
注意:理论上讲,合约创建者可能会通过返回比实际数量少的总供应量来做骗局,让每个代币 比实际看起来更有价值。 然而,这种担忧忽视了区块链的真正内涵。 所有在区块链上发生的事情都要通过每个节点 进行验证。 为了实现这一点,每个合约的机器语言代码和存储都可以在每个节点上找到。 虽然无需发布您的合约代码,但这样其它人都不会认真对待您,除非您发布源代码和用于编译的 Solidity 版本,这样人们可以用它来验证您提供的机器语言代码。 例如,请查看此合约(opens in a new tab)。
1 /**2 * @dev Returns the amount of tokens owned by `account`.3 */4 function balanceOf(address account) external view returns (uint256);5复制
顾名思义,balanceOf
返回一个账户的余额。 以太坊帐户在 Solidity 中通过 address
类型识别,该类型有 160 位。 它也是 external
和 view
类型。
1 /**2 * @dev Moves `amount` tokens from the caller's account to `recipient`.3 *4 * Returns a boolean value indicating whether the operation succeeded.5 *6 * Emits a {Transfer} event.7 */8 function transfer(address recipient, uint256 amount) external returns (bool);9复制
transfer
函数将代币从调用者地址转移到另一个地址。 这涉及到状态的更改,所以它不是 view
类型。 当用户调用此函数时,它会创建交易并消耗燃料。 还会触发一个 Transfer
事件,以通知区块链上的所有人。
该函数有两种输出,对应两种不同的调用:
- 直接从用户接口调用函数的用户。 此类用户通常会提交一个交易 并且不会等待响应,因为响应可能需要无限期的时间。 用户可以查看交易收据 (通常通过交易哈希值识别)或者查看
Transfer
事件,以确定发生了什么。 - 将函数作为整个交易一部分调用的其他合约 这些合约可立即获得结果, 由于它们在相同的交易里运行,因此可以使用函数返回值。
更改合约状态的其他函数创建的同类型输出。
限额允许帐户使用属于另一位所有者的代币。 比如,当合约作为卖方时,这个函数就很实用。 合约无法 监听事件,如果买方要将代币直接转给卖方合约, 该合约无法知道已经获得付款。 因此,买方允许 卖方合约支付一定的额度,而让卖方转账相应金额。 这通过卖方合约调用的函数完成,这样卖方合约 可以知道是否成功。
1 /**2 * @dev Returns the remaining number of tokens that `spender` will be3 * allowed to spend on behalf of `owner` through {transferFrom}. This is4 * zero by default.5 *6 * This value changes when {approve} or {transferFrom} are called.7 */8 function allowance(address owner, address spender) external view returns (uint256);9复制
allowance
函数允许任何人查询一个 地址 (owner
) 给另一个地址 (spender
) 的许可额度。
1 /**2 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.3 *4 * Returns a boolean value indicating whether the operation succeeded.5 *6 * IMPORTANT: Beware that changing an allowance with this method brings the risk7 * that someone may use both the old and the new allowance by unfortunate8 * transaction ordering. One possible solution to mitigate this race9 * condition is to first reduce the spender's allowance to 0 and set the10 * desired value afterwards:11 * https://github.com/ethereum/EIPs/issues/20#issuecomment-26352472912 *13 * Emits an {Approval} event.14 */15 function approve(address spender, uint256 amount) external returns (bool);16显示全部复制
approve
函数创建了一个许可额度。 请务必阅读关于 如何避免函数被滥用的信息。 在以太坊中,您可以控制自己交易的顺序, 但无法控制其他方交易的执行顺序, 除非在看到其他方的交易发生之前 不提交您自己的交易。
1 /**2 * @dev Moves `amount` tokens from `sender` to `recipient` using the3 * allowance mechanism. `amount` is then deducted from the caller's4 * allowance.5 *6 * Returns a boolean value indicating whether the operation succeeded.7 *8 * Emits a {Transfer} event.9 */10 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);11显示全部复制
最后,消费者使用 transferFrom
函数用来使用许可额度。
12 /**3 * @dev Emitted when `value` tokens are moved from one account (`from`) to4 * another (`to`).5 *6 * Note that `value` may be zero.7 */8 event Transfer(address indexed from, address indexed to, uint256 value);910 /**11 * @dev Emitted when the allowance of a `spender` for an `owner` is set by12 * a call to {approve}. `value` is the new allowance.13 */14 event Approval(address indexed owner, address indexed spender, uint256 value);15}16显示全部复制
在 ERC-20 合约状态发生变化时就会激发这些事件。
实际合约
这是实现 ERC-20 标准的实际合约, 摘自此处(opens in a new tab)。 不能照原样使用,但可以 通过继承(opens in a new tab)将其扩展,使之可用。
1// SPDX-License-Identifier: MIT2pragma solidity >=0.6.0 <0.8.0;3复制
导入声明
除了上述接口定义外,合约定义还要导入两个其他文件:
12import "../../GSN/Context.sol";3import "./IERC20.sol";4import "../../math/SafeMath.sol";5复制
GSN/Context.sol
是使用 OpenGSN(opens in a new tab) 所需的文件,该系统允许用户在没有以太币的情况下 使用区块链。 请注意,这里的文件是旧版本,如果需要集成 OpenGSN, 请使用此教程(opens in a new tab)。- SafeMath 库(opens in a new tab),用于 完成没有溢出问题的加法和减法。 这非常必要,否则会出现,用户仅有一个代币,花掉 两个代币后,反而有了 2^256-1 个代币。
这里的注释说明了合约的目的。
1/**2 * @dev Implementation of the {IERC20} interface.3 *4 * This implementation is agnostic to the way tokens are created. This means5 * that a supply mechanism has to be added in a derived contract using {_mint}.6 * For a generic mechanism see {ERC20PresetMinterPauser}.7 *8 * TIP: For a detailed writeup see our guide9 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How10 * to implement supply mechanisms].11 *12 * We have followed general OpenZeppelin guidelines: functions revert instead13 * of returning `false` on failure. This behavior is nonetheless conventional14 * and does not conflict with the expectations of ERC20 applications.15 *16 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.17 * This allows applications to reconstruct the allowance for all accounts just18 * by listening to said events. Other implementations of the EIP may not emit19 * these events, as it isn't required by the specification.20 *21 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}22 * functions have been added to mitigate the well-known issues around setting23 * allowances. See {IERC20-approve}.24 */2526显示全部复制
合约定义
1contract ERC20 is Context, IERC20 {2复制
此行为 OpenGSN 指定继承,在本例中来自上面的 IERC20
和 Context
。
12 using SafeMath for uint256;34复制
此行将 SafeMath
库附加到 uint256
类型。 您可以在 此处(opens in a new tab)找到此程序库。
变量的定义
这些定义具体指定了合约的状态变量。 虽然声明这些变量为 private
,但 这只意味着区块链上的其他合约无法读取 它们。 区块链上 没有秘密,所有节点上的软件在每个区块上 都有每个合约的状态。 根据惯例,状态变量名称为 _<something>
。
前两个变量是映射(opens in a new tab), 表示它们的结果与关联数组(opens in a new tab)相同, 不同之处在于关键词为数值。 存储空间仅分配给数值不同于 默认值(零)的条目。
1 mapping (address => uint256) private _balances;2复制
第一个映射,_balances