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。例如数量为 , 则 除以 得到的值才是真正的代币数量
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
对应的 tokenURI
, tokenURI
是一个描述了该 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);
}
授权 tokenId
的 token
给 to
地址, 并且触发 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
上面说的 ERC20
和 ERC721
标准,每个合约都对应着一个独立的代币。但 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
,类似 ERC721
的 tokenURI
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
事件