这是一个管理多种代币类型的合约标准,该合约可以包括同质化代币和非同质化代币,可以代表任意数量的同质化和非同质化的代币类型,抽象上可以解释为:ERC1155 囊括了 ERC20和ERC777这两种标准。
这是一个管理多种代币类型的合约标准,该合约可以包括同质化代币和非同质化代币,可以代表任意数量的同质化和非同质化的代币类型,抽象上可以解释为:ERC1155 囊括了 ERC20和ERC777这两种标准。ERC1155的用处,举个游戏的例子(王者荣耀)例子:要是使用ERC20来表示游戏的金币、钻石、点券,很明显ERC20无法做到,因为ERC20 token是同质化的,不能明确区分token与token之间的不同;要是使用ERC721,根据ERC721非同质化的特点,确实是可以表示金币、钻石、点券,但是ERC721 token不能细分,从而导致只能表示“1”个金币、钻石、点券,这很显然是不可取的。正是为了解决这些不足,从而发行了ERC1155标准,这可以很完美的解决上述问题。在ERC1155 token中不同的id表示不同的属性,而且还可以给id设置数量,有了这些特性,便可以很好的解决上述痛点。
- 同质化代币的表示方式为:如果某个
id
对应的代币总量为1
,那么它就是非同质化代币,类似ERC721
;- 非同质化代币的表示方式为:如果某个
id
对应的代币总量大于1
,那么他就是同质化代币,因为这些代币都分享同一个id
,类似ERC20
。
代码来自 openzepelin:链接。
协议的官方文档:链接。
IERC1155.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-1155 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1155[ERC].
*/
interface IERC1155 is IERC165 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external;
}
这是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()
接收函数。IERC1155MetadataURI.sol
interface IERC1155MetadataURI is IERC1155 {
function uri(uint256 id) external view returns (string memory);
}
这是一个可选接口,用于查询指定 token ID的 uri
。如果继承了该接口,则需要在 ERC165
的supportsInterface()
函数中返回常量(用来检验是否实现该接口)。注意:该 uri()
函数不得用于检查令牌是否存在,因为即使令牌不存在,实现也可能返回有效的字符串。
IERC1155Receiver.sol
如果ERC1155
TOKEN的接收者receiver
是一个合约地址,那么接收者必须要实现该接口。
该接口有两个函数:(前提是接收者是合约地址)
ERC1155
的 safeTransferFrom()
和 _mint()
时,接收者的该函数会被调用,并按要求返回指定的值 bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))
。ERC1155
的 safeBatchTransferFrom()
时,接收者的该函数会被调用,并按要求返回指定的值 bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))
。ERC1155.sol
mapping(uint256 id => mapping(address account => uint256)) private _balances;
mapping(address account => mapping(address operator => bool)) private _operatorApprovals;
id
对应 账户 account
的余额,即保存 account
拥有多少种类为id
的token
个数。account
对operator
的授权情况,true
表示已经授权,false
表示未授权。 function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
这是库合约中的函数,功能时读取 arr
数组指定索引的值。
解释汇编
mload(add(add(arr, 0x20), mul(pos, 0x20)))
add(arr,0x20) //跳过数组长度,定位到数据段
mul(pos, 0x20) // EVM的存储机制是以32bytes为一个单位,这段操作码的结果是计算长度,比如 pos=2,则表示 2 * 32 bytes
add(add(arr, 0x20), mul(pos, 0x20)) // 计算 32bytes + pos * 32bytes
mload(add(add(arr, 0x20), mul(pos, 0x20))) // 设 x = add(add(arr, 0x20), mul(pos, 0x20)),则表示读取[x, x+32bytes)的数据
其实就是用汇编的语言实现,读取数组指定索引的值。
function _asSingletonArrays(
uint256 element1,
uint256 element2
) private pure returns (uint256[] memory array1, uint256[] memory array2)
这个函数的功能则是将传入的两个参数分别封装成两个 uint256[]
类型的数组。汇编实现的逻辑都有注释,写得很清楚。
function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual {
if (ids.length != values.length) {
revert ERC1155InvalidArrayLength(ids.length, values.length);
}
address operator = _msgSender();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids.unsafeMemoryAccess(i);
uint256 value = values.unsafeMemoryAccess(i);
if (from != address(0)) {
uint256 fromBalance = _balances[id][from];
if (fromBalance < value) {
revert ERC1155InsufficientBalance(from, fromBalance, value, id);
}
unchecked {
// Overflow not possible: value <= fromBalance
_balances[id][from] = fromBalance - value;
}
}
if (to != address(0)) {
_balances[id][to] += value;
}
}
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
emit TransferSingle(operator, from, to, id, value);
} else {
emit TransferBatch(operator, from, to, ids, values);
}
}
这是资产更新的核心函数,参与完成铸币,转账,销币操作。要求参数的两个数组长度相等。
address(0)
,通过for循环为 _balances[id][to] += value
添加余额,达成铸币。这对单次铸币和批量铸币都适用。from
和to
都不为address(0)
,通过for循环完成对 from
和to
的余额修改,这对单次转账和批量转账都适用。to
为address(0)
,通过for循环修改_balances[id][from] = fromBalance - value;
,要求fromBalance >=value
,这对单次销币和批量销币都适用。 function _updateWithAcceptanceCheck(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal virtual {
_update(from, to, ids, values);
if (to != address(0)) {
address operator = _msgSender();
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
ERC1155Utils.checkOnERC1155Received(operator, from, to, id, value, data);
} else {
ERC1155Utils.checkOnERC1155BatchReceived(operator, from, to, ids, values, data);
}
}
}
这个函数负责更新用户资产以及检验合约接受者是否实现了 checkOnERC1155Received
接口,这里采用了 checks-effect-interaction
的方式,将合约的交互放在了_update
函数后面,一定程度上限制了对资金的重入风险,但是这里依旧存在重入的风险。
function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal
实现单笔转账,from
和to
都不能为address(0)
,先通过_asSingletonArrays(id, value)
将id
和value
包装成两个数组,再调用_updateWithAcceptanceCheck(from, to, ids, values, data)
,进行资产的更新和对合约接收者的接口检验。
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual
实现安全单笔转账,要求msg.sender
是 from
或者from
对msg.sender
执行了授权操作,否则revert()
。转账逻辑调用_safeTransferFrom(from, to, id, value, data)
。
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal
实现批量转账,from
和to
都不能为address(0)
,调用_updateWithAcceptanceCheck(from, to, ids, values, data);
进行资产的更新和对合约接收者的接口检验。
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) public virtual
实现安全批量转账,将from
所拥有的 ids
,向to
转移values
,ids和values的索引是一一对应的。要求msg.sender
是 from
或者from
对msg.sender
执行了授权操作,否则revert()
。转账逻辑调用_safeBatchTransferFrom(from, to, ids, values, data);
。
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC1155InvalidOperator(address(0));
}
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
授权操作,owner
对operator
执行授权操作,operator
被授权之后可以操作owner
的资产。同时也可以取消授权,即传入的参数approve
为 false
。
function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
实现铸造ID为id
的代币,且发行量为value
。这里调用了_updateWithAcceptanceCheck()
函数存在重入风险。
function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
实现铸造ID为ids
的代币,且发行量为values
,代币ID号和发行量一一对应。这里调用了_updateWithAcceptanceCheck()
函数存在重入风险。
ERC1155Pausable.sol
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal virtual override whenNotPaused {
super._update(from, to, ids, values);
}
实现了合约暂停功能,重写了ERC1155的_update
函数,使得凡是调用该函数的操作都会受到控制。
ERC1155Burnable.sol
提供了代币注销功能,即间接的将两个内部的销币函数设置为external
函数。当然了,执行销币的前提是msg.sender
是token的owner或者是 operator。
ERC1155Supply.sol
主要提供了一个统计发行量的功能,铸币会使得_totalSupply[id]
发行量增大;销币会使得_totalSupply[id]
发行量减小。同时还可以通过exists(uint256 id)
查询 token id 是否以及存在。
ERC1155URIStorage.sol
通过了设置 token 的 URI
功能,同时还实现了为每一种 token设置 tokenURI。
ERC1155Utils.sol
提供了两个用来验证接收者是否实现了指定接口和函数的功能。
// function checkOnERC1155Received
try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response)
// function checkOnERC1155BatchReceived
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (
bytes4 response
)
ERC1155存在重入风险,有重入风险的函数分别是:
具体攻击事件稍后分析。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!