跳到主要内容

ERC代币标准详解

· 阅读需 17 分钟

ERC 全称是 Ethereum Request for Comment,即以太坊征求意见协议,在以太坊网络中一方面为开发者提供了技术指导,另一方面也对以太坊网络的发展提供了建议。

ERC 标准是由以太坊开发人员为以太坊社区编写的。为了给以太坊平台创建标准,提交了以太坊改进建议( EIP )——包括协议规范和合同标准。如果 EIP 被委员会批准并最终确定,它就成为了一个 ERC。

EIP 完整列表可以查看

ERC 中常用的代币标准包括了

  • ERC20: 同质化代币的标准
  • ERC721:非同质化代币的标准
  • ERC1155:多代币标准

ERC20

ERC20 提供了一个同质化代币(Fungible Token, FT)的标准,换句话说,即每一枚代币(在类型和价值上)完全相同。例如,一个 ERC-20 代币就像以太币一样,意味着一个代币会并永远会与其他代币一样。

主要功能包括了

  • 转账:将代币从一个帐户转到另一个帐户
  • 查询余额:获取帐户的当前代币余额
  • 查询总量:获取网络上可用代币的总供应量
  • 代币授权:批准一个帐户中一定的代币金额由第三方帐户使用

标准实现的方法有

以下代码节选自 @openzeppelin/contracts: 4.7.0

name

function name() public view virtual override returns (string memory) {
return _name;
}

获取代币名称

symbol

function symbol() public view virtual override returns (string memory) {
return _symbol;
}

获取代币符号

decimals

function decimals() public view virtual override returns (uint8) {
return 18;
}

获取代币的精度。通常为 18。例如数量为 10001000, 则 10001000 除以 101810^{18}得到的值才是真正的代币数量

totalSupply

function totalSupply() public view virtual override returns (uint256) {
return __totalSupply;
}

获取代币的总供应量

balanceOf

function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}

返回 account 账户的代币余额

transfer

function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
// 获取调用的地址
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}

function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, amount);

// 获取 from 地址的余额
uint256 fromBalance = _balances[from];
// 检查余额数量不小于转账的数量
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");

// 将 from 的余额减少
unchecked {
_balances[from] = fromBalance - amount;
}
// 将 to 的余额增加
_balances[to] += amount;

emit Transfer(from, to, amount);

_afterTokenTransfer(from, to, amount);
}

转账,将调用者指定数量的 token 转到 _to 地址并必须触发 Transfer 事件。如果调用者的账户余额没有足够的代币可以支付 amount 数量的代币时,该函数应该抛出异常并回滚。

transferFrom

function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
// 获取消息的发送者
address spender = _msgSender();
// 获取并更新 spender 可操作 from 的代币数量
_spendAllowance(from, spender, amount);
// 转账
_transfer(from, to, amount);
return true;
}

function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
// 获取 spender 可以从 owner 中操作的代币数量
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
// 要求可操作的数量大于 amount
require(currentAllowance >= amount, "ERC20: insufficient allowance");
// 更新可操作的数量
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}

将指定数量的代币从地址 from 转移到地址 to,并且必须触发Transfer事件。进行此项操作的前提是消息的发送者具有操作 from 代币的权利,才能将代币从 from 转移到 to。即转账操作前需要进行 approve 授权操作,将 from 地址指定数量的代币的操作权交给 消息的发送者。并且 approve 操作需要由 from 发出。具体看下方代码

approve

function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
// 地址校验
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");

// 存储 spender 账户可以对 owner 账户的 amount 数量的代币进行操作
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}

该函数的调用者账户地址将 amount 数量的代币授权给 spender,或者更新授权的代币数量,即 spender 可以多次提取调用者账户地址的代币,总数量不超过 amount,每次提取的代币数量也不能超过调用者账户地址的代币余额,同时提取后会更新 amount 的值。如果在提取过程中,提取的代币数量超过了代币余额不足,该函数会抛出异常并回滚。

allowance

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

返回账户地址 spender 还可以从账户地址 owner 中提取的代币数量。

ERC721

不同于 ERC20 中所有的代币都具有相同的价值和属性。ERC721 为非同质化代币(NFT) 标准,即每一枚代币都是独一无二的。例如收藏品、音乐等。

为了实现这种独一无二。会有一个uint256类型的变量 tokenId 来表示每一枚 代币, 通常 tokenId 是自增的。并随着 mint 的数量增加

以下代码节选自 @openzeppelin/contracts 4.7.0

变量:

// 代币名称
string private _name;

// 代币符号
string private _symbol;

// tokenID 对应的 拥有者地址
mapping(uint256 => address) private _owners;

// tokenID 对应的 metadata json 文件的地址
mapping(uint256 => string) private _tokenURIs;

// 拥有者地址所有的 nft 数量
mapping(address => uint256) private _balances;

// tokenID 对应授权的地址 - 单个授权
mapping(uint256 => address) private _tokenApprovals;

// _operatorApprovals[owner][operator] = true or false
// 存储是否将 owner 所持有的全部 nft 的操作权限交给 operator - 全部授权
mapping(address => mapping(address => bool)) private _operatorApprovals;

方法:

balanceOf

function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}

查询余额,返回地址 owner 拥有的 nft 的数量,若没有,则返回 0。

ownerOf

function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}

获取 tokenId 对应的 nft 的持有者地址

tokenURI

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);

string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
// 要求 tokenId 对应的 nft 存在拥有者 即该 nft 已经被铸造了
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
// 查询 tokenId 是否存在拥有者
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
}

获取 tokenId 对应的 tokenURItokenURI 是一个描述了该 NFT metadata的地址。

setTokenURI

function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
_tokenURIs[tokenId] = _tokenURI;
}

设置 tokenId 对应的 tokenURI

approve

function approve(address to, uint256 tokenId) public virtual override {
// 获取 tokenId 对应的持有者
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");

require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner nor approved for all"
);

_approve(to, tokenId);
}

function _approve(address to, uint256 tokenId) internal virtual {
// 存储授权信息
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}

授权 tokenIdtokento 地址, 并且触发 Approve事件

getApproved

function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);

return _tokenApprovals[tokenId];
}

查询单个 NFT 的授权地址

setApprovalForAll

function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}

function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}

将调用者的所有 NFT 资产授权给 operator 地址来管理,或者取消授权,并且触发 ApprovalForAll 事件

isApprovedForAll

function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}

查询 operator 是否具有 owner 所持有的所有 NFT 的操作权限

transferFrom

function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");

_transfer(from, to, tokenId);
}

// 判断是否具有操作权限
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}

tokenId 的 NFT 的所有权由地址 from 转移给地址 to,并触发Transfer事件

safeTransferFrom

function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}

function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual override {
// 确保消息发送者 具有 tokenId 的操作权限 - 拥有者 或者 已授权。
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
// 调用转账方法
_safeTransfer(from, to, tokenId, data);
}

function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}

function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
// to 地址是合约地址
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}

// 转账方法
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");

_beforeTokenTransfer(from, to, tokenId);

// Clear approvals from the previous owner
_approve(address(0), tokenId);

_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;

emit Transfer(from, to, tokenId);

_afterTokenTransfer(from, to, tokenId);
}

安全地转移 NFT 所有权,即将 tokenId 对应的 NFT 的所有权由地址 from 转移给地址 to,并触发 Transfer事件

mint

function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");

_beforeTokenTransfer(address(0), to, tokenId);

// 持有人 to 的持有数量 + 1
_balances[to] += 1;
// 添加 tokenId 对应的地址 to
_owners[tokenId] = to;

emit Transfer(address(0), to, tokenId);

_afterTokenTransfer(address(0), to, tokenId);
}


铸造 NFT

ERC1155

上面说的 ERC20ERC721标准,每个合约都对应着一个独立的代币。但 ERC1155 却不同,一个合约可以对应多种代币,包括同质化代币和非同质化代币。例如 阿迪达斯 NFT

为了实现单个合约中包含多种代币,会用 id 来标识每一种代币。并且每种代币都有一个 uri来存储其 metadata

以下代码节选自 @openzeppelin/contracts 4.7.0

变量

// _balances[id][owner]
// 存储 id 对应的 token, 其持有者拥有的数量
mapping(uint256 => mapping(address => uint256)) private _balances;

// _operatorApprovals[owner][operator] = true or false
// 存储 owner 将代币操作权限授权给 operator 或 取消授权
mapping(address => mapping(address => bool)) private _operatorApprovals;

mint

function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");

address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);

_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);

_afterTokenTransfer(operator, address(0), to, ids, amounts, data);

_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}

// 铸造 id 为 1, 持有者为 消息发送者, 数量为 10**18 的代币
_mint(msg.sender, 1, 10**18, '');

单币种铸造

mintBatch

function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

address operator = _msgSender();

_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}

emit TransferBatch(operator, address(0), to, ids, amounts);

_afterTokenTransfer(operator, address(0), to, ids, amounts, data);

_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}

多币种铸造

uri

function uri(uint256 id) public view virtual override returns (string memory) {
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : "";
}

返回 id 对应的 token 存储的 uri,类似 ERC721tokenURI

balanceOf

function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: address zero is not a valid owner");
return _balances[id][account];
}

查询余额

balanceOfBatch

function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
public
view
virtual
override
returns (uint256[] memory)
{
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

uint256[] memory batchBalances = new uint256[](accounts.length);

for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}

return batchBalances;
}

查询多账户持有的多 token 的余额

setApprovalForAll

function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}

设置授权

isApprovedForAll

function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator];
}

授权查询

safeTransferFrom

function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
// 要求 from 拥有代币所有权
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner nor approved"
);
_safeTransferFrom(from, to, id, amount, data);
}

function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: transfer to the zero address");

address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);

_beforeTokenTransfer(operator, from, to, ids, amounts, data);

// 获取 from 地址中 id 对应 token 的余额
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
// 减少 from 地址中的余额
unchecked {
_balances[id][from] = fromBalance - amount;
}
// 增加 to 地址余额
_balances[id][to] += amount;

emit TransferSingle(operator, from, to, id, amount);

_afterTokenTransfer(operator, from, to, ids, amounts, data);

_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}

单币种安全转账,触发TransferSingle事件

safeBatchTransferFrom

function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner nor approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}

function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");

address operator = _msgSender();

_beforeTokenTransfer(operator, from, to, ids, amounts, data);

// 循环转账
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];

uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}

emit TransferBatch(operator, from, to, ids, amounts);

_afterTokenTransfer(operator, from, to, ids, amounts, data);

_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}

多币种安全转账,触发TransferBatch事件