Skip to main content

ERC1155

ERC1155

这一讲,我们将学习ERC1155标准,它支持一个合约包含多种代币。并且,我们会发行一个魔改的无聊猿 - BAYC1155:它包含10,000种代币,且元数据与BAYC一致。

不论是ERC20还是ERC721标准,每个合约都对应一个独立的代币。假设我们要在以太坊上打造一个类似《魔兽世界》的大型游戏,这需要我们对每个装备都部署一个合约。上千种装备就要部署和管理上千个合约,这非常麻烦。因此,以太坊EIP1155提出了一个多代币标准ERC1155,允许一个合约包含多个同质化和非同质化代币。ERC1155在GameFi应用最多,Decentraland、Sandbox等知名链游都使用它。

简单来说,ERC1155与之前介绍的非同质化代币标准ERC721类似:

  • 在ERC721中,每个代币都有一个tokenId作为唯一标识,每个tokenId只对应一个代币; 而在ERC1155中,每一种代币都有一个id作为唯一标识,每个id对应一种代币。这样,代币种类就可以非同质的在同一个合约里管理了,并且每种代币都有一个网址uri来存储它的元数据,类似ERC721的tokenURI。下面是ERC1155的元数据接口合约IERC1155MetadataURI:
/**
* @dev ERC1155的可选接口,加入了uri()函数查询元数据
*/
interface IERC1155MetadataURI is IERC1155 {
/**
* @dev 返回第`id`种类代币的URI
*/
function uri(uint256 id) external view returns (string memory);

1-2.那么怎么区分ERC1155中的某类代币是同质化还是非同质化代币呢?

其实很简单:

  • 如果某个id对应的代币总量为1,那么它就是非同质化代币,类似ERC721;

  • 如果某个id对应的代币总量大于1,那么他就是同质化代币,因为这些代币都分享同一个id,类似ERC20。

2.IERC1155接口合约

IERC1155接口合约抽象了EIP1155需要实现的功能,其中包含4个事件和6个函数。

与ERC721不同,因为ERC1155包含多类代币,它实现了批量转账和批量余额查询,一次操作多种代币。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/IERC165.sol";

/**
* @dev ERC1155标准的接口合约,实现了EIP1155的功能
* 详见:https://eips.ethereum.org/EIPS/eip-1155[EIP].
*/
interface IERC1155 is IERC165 {
/**
* @dev 单类代币转账事件
* 当`value`个`id`种类的代币被`operator`从`from`转账到`to`时释放.
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

/**
* @dev 批量代币转账事件
* ids和values为转账的代币种类和数量数组
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);

/**
* @dev 批量授权事件
* 当`account`将所有代币授权给`operator`时释放
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);

/**
* @dev 当`id`种类的代币的URI发生变化时释放,`value`为新的URI
*/
event URI(string value, uint256 indexed id);

/**
* @dev 持仓查询,返回`account`拥有的`id`种类的代币的持仓量
*/
function balanceOf(address account, uint256 id) external view returns (uint256);

/**
* @dev 批量持仓查询,`accounts`和`ids`数组的长度要想等。
*/
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external
view
returns (uint256[] memory);

/**
* @dev 批量授权,将调用者的代币授权给`operator`地址。
* 释放{ApprovalForAll}事件.
*/
function setApprovalForAll(address operator, bool approved) external;

/**
* @dev 批量授权查询,如果授权地址`operator`被`account`授权,则返回`true`
* 见 {setApprovalForAll}函数.
*/
function isApprovedForAll(address account, address operator) external view returns (bool);

/**
* @dev 安全转账,将`amount`单位`id`种类的代币从`from`转账给`to`.
* 释放{TransferSingle}事件.
* 要求:
* - 如果调用者不是`from`地址而是授权地址,则需要得到`from`的授权
* - `from`地址必须有足够的持仓
* - 如果接收方是合约,需要实现`IERC1155Receiver`的`onERC1155Received`方法,并返回相应的值
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;

/**
* @dev 批量安全转账
* 释放{TransferBatch}事件
* 要求:
* - `ids`和`amounts`长度相等
* - 如果接收方是合约,需要实现`IERC1155Receiver`的`onERC1155BatchReceived`方法,并返回相应的值
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}

2-1.IERC1155事件

  • TransferSingle事件:单类代币转账事件,在单币种转账时释放。
  • TransferBatch事件:批量代币转账事件,在多币种转账时释放。
  • ApprovalForAll事件:批量授权事件,在批量授权时释放。
  • URI事件:元数据地址变更事件,在uri变化时释放。

2-2.IERC1155函数

  • balanceOf():单币种余额查询,返回account拥有的id种类的代币的持仓量。
  • balanceOfBatch():多币种余额查询,查询的地址accounts数组和代币种类ids数组的长度要相等。
  • setApprovalForAll():批量授权,将调用者的代币授权给operator地址。。
  • isApprovedForAll():查询批量授权信息,如果授权地址operator被account授权,则返回true。
  • safeTransferFrom():安全单币转账,将amount单位id种类的代币从from地址转账给to地址。如果to地址是合约,则会验证是否实现了onERC1155Received()接收函数。
  • safeBatchTransferFrom():安全多币转账,与单币转账类似,只不过转账数量amounts和代币种类ids变为数组,且长度相等。如果to地址是合约,则会验证是否实现了onERC1155BatchReceived()接收函数。

3.ERC1155接收合约

与ERC721标准类似,为了避免代币被转入黑洞合约,ERC1155要求代币接收合约继承IERC1155Receiver并实现两个接收函数:

  • onERC1155Received():单币转账接收函数,接受ERC1155安全转账safeTransferFrom 需要实现并返回自己的选择器0xf23a6e61。
  • onERC1155BatchReceived():多币转账接收函数,接受ERC1155安全多币转账safeBatchTransferFrom 需要实现并返回自己的选择器0xbc197c81。

4.ERC1155主合约

ERC1155主合约实现了IERC1155接口合约规定的函数,还有单币/多币的铸造和销毁函数。

4-1.ERC1155变量

ERC1155主合约包含4个状态变量:

  • name:代币名称
  • symbol:代币代号
  • _balances:代币持仓映射,记录代币种类id下某地址account的持仓量balances。
  • _operatorApprovals:批量授权映射,记录持有地址给另一个地址的授权情况。

4-2.ERC1155函数

ERC1155主合约包含16个函数:

  1. 构造函数:初始化状态变量name和symbol。
  2. supportsInterface():实现ERC165标准,声明它支持的接口,供其他合约检查。
  3. balanceOf():实现IERC1155的balanceOf(),查询持仓量。与ERC721标准不同,这里需要输入查询的持仓地址account以及币种id。
  4. balanceOfBatch():实现IERC1155的balanceOfBatch(),批量查询持仓量。
  5. setApprovalForAll():实现IERC1155的setApprovalForAll(),批量授权,释放ApprovalForAll事件。
  6. isApprovedForAll():实现IERC1155的isApprovedForAll(),查询批量授权信息。
  7. safeTransferFrom():实现IERC1155的safeTransferFrom(),单币种安全转账,释放TransferSingle事件。与ERC721不同,这里不仅需要填发出方from,接收方to,代币种类id,还需要填转账数额amount。
  8. safeBatchTransferFrom():实现IERC1155的safeBatchTransferFrom(),多币种安全转账,释放TransferBatch事件。
  9. _mint():单币种铸造函数。
  10. _mintBatch():多币种铸造函数。
  11. _burn():单币种销毁函数。
  12. _burnBatch():多币种销毁函数。
  13. _doSafeTransferAcceptanceCheck:单币种转账的安全检查,被safeTransferFrom()调用,确保接收方为合约的情况下,实现了onERC1155Received()函数。
  14. _doSafeBatchTransferAcceptanceCheck:多币种转账的安全检查,,被safeBatchTransferFrom调用,确保接收方为合约的情况下,实现了onERC1155BatchReceived()函数。
  15. uri():返回ERC1155的第id种代币存储元数据的网址,类似ERC721的tokenURI。
  16. baseURI():返回baseURI,uri就是把baseURI和id拼接在一起,需要开发重写。

BAYC,但是ERC1155

我们魔改下ERC721标准的无聊猿BAYC,创建一个免费铸造的BAYC1155。我们修改_baseURI()函数,使得BAYC1155的uri和BAYC的tokenURI一样。这样,BAYC1155元数据会与无聊猿的相同:

// SPDX-License-Identifier: MIT
// by 0xAA
pragma solidity ^0.8.4;

import "./ERC1155.sol";

contract BAYC1155 is ERC1155{
uint256 constant MAX_ID = 10000;
// 构造函数
constructor() ERC1155("BAYC1155", "BAYC1155"){
}

//BAYC的baseURI为ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
function _baseURI() internal pure override returns (string memory) {
return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
}

// 铸造函数
function mint(address to, uint256 id, uint256 amount) external {
// id 不能超过10,000
require(id < MAX_ID, "id overflow");
_mint(to, id, amount, "");
}

// 批量铸造函数
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external {
// id 不能超过10,000
for (uint256 i = 0; i < ids.length; i++) {
require(ids[i] < MAX_ID, "id overflow");
}
_mintBatch(to, ids, amounts, "");
}
}