一 什么是ERC-1155, 为什么需要ERC-1155?
1.1 ERC-20或者ERC-721存在什么问题
1.1.1 部署效率低
无论是ERC-20同质化代币还是ERC-721非同质化代币,只允许部署一个Token合约,如果想部署多个Token合约,那么必须进行多次部署
1.1.2 操作效率低
有些时候,想批量转移多个Token给其他地址,只能不同的Token一个一个转,操作效率低下
1.1.3 交易成本高
有些时候,想转移多个Token给其他地址,只能不同的Token一个一个转,那就需要调用多次交易函数,因此交易成本也高
1.2 什么是ERC-1155
ERC-1155允许在单个合约中创建多个同质化代币或者非同质化代币,允许批量转账多个代币,并且可以保证操作的原子性,提升了操作效率,减少了Gas费用,丰富了使用场景
二 ERC-1155提案标准内容
我们知道,ERC-1155其实创建一个智能合约接口,可以代表和控制任何数量的同质化和非同质化代币类型。 这样一来,ERC-1155 代币就具有与 ERC-20 和 ERC-721 代币相同的功能,甚至可以同时使用这两者的功能
2.1 函数
2.1.1 balanceOf
function balanceOf(address account, uint256 id) external view returns (uint256)
根据用户地址和token id查询余额
2.1.2 balanceOfBatch
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
批量查询多个地址和多个代币ID的余额,并且account数组和id数组是一一对应的
2.1.3 setApprovalForAll
function setApprovalForAll(address operator, bool approved) external;
设置或撤销对某个操作员的授权,允许其代表用户管理所有代币
2.1.4 isApprovedForAll
function isApprovedForAll(address account, address operator) external view returns (bool);
检查某个操作员是否被授权管理用户的所有代币
2.1.5 safeTransferFrom
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
安全转移特定数量的代币到目标地址
2.1.6 safeBatchTransferFrom
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external;
安全批量转移特定数量的多个代币到目标地址,id数组和value数组也是一一对应的
2.2 事件
2.2.1 TransferSingle
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
2.2.2 TransferBatch
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
2.2.3 ApprovalForAll
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
三 ERC-1155有什么特点
3.1 单一合约部署
因为ERC-1155 允许在同一个合约中创建多种代币类型,减少了要部署的合约数量,增加了部署效率,降低了部署的复杂性和成本。
3.2 支持部署多个币种
之前ERC-20和ERC-721只是支持部署单个代币,现在可以一下子部署多个代币类型
3.3 高效的批量转移
ERC-1155支持批量转移,因此提升了转移的效率
3.4 交易原子性
ERC-1155 支持原子交换,允许在单个交易中同时交换多个代币,确保所有交换要么全部成功,要么全部失败
3.5 降低Gas费用,节省成本
通过允许批量转移和在同一个合约中创建多种代币类型,ERC-1155 相较于为每种代币类型使用单独合约可以帮助降低 Gas 成本
3.6 向后兼容
ERC-1155 设计为向后兼容 ERC-20 和 ERC-721 标准,便于与现有的代币标准和基础设施集成
3.7互操作性
ERC-1155 代币可以在支持该标准的不同平台和应用程序中使用,增加了以太坊生态系统中代币的互操作性
四 手动实现ERC-1155
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
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);
/*
* Queries the balance of tokens by account address and token ID
* @param account The address of the account to query
* @param id The ID of the token to query
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/*
* Batch queries the balance of multiple accounts and multiple token IDs. Each address in the accounts array corresponds to a token ID in the ids array.
* The balanceOfBatch function returns an array where each element represents the balance of the corresponding address and token ID.
* @param accounts The array of account addresses to query
* @param ids The array of token IDs to query
*/
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
/*
* Grants or revokes permission for an operator to manage all tokens of the owner
* @param operator The address of the operator to grant or revoke permission
* @param approved A boolean value, true to grant permission, false to revoke permission
*/
function setApprovalForAll(address operator, bool approved) external;
/*
* Checks if an operator is authorized to manage all tokens of a specific account
* @param account The address of the account to check
* @param operator The address of the operator to check
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/*
* Safely transfers a specified amount of a token to a target address
* @param from The address from which the tokens are transferred
* @param to The address to which the tokens are transferred
* @param id The ID of the token to transfer
* @param amount The amount of tokens to transfer
* @param data Additional data, which may be used to call special operations on the receiving contract
*/
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
/*
* Safely batch transfers specified amounts of specified token IDs to a target address. The ids array and values array correspond one-to-one.
* @param from The address from which the tokens are transferred
* @param to The address to which the tokens are transferred
* @param ids The array of token IDs to transfer
* @param values The array of amounts of tokens to transfer, each corresponding to the respective ID in the ids array
* @param data Additional data, which may be used to call special operations on the receiving contract
*/
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external;
}
interface IERC1155Errors {
error ERC1155InsufficientBalance(address from, uint256 balance, uint256 needed, uint256 tokenId);
error ERC1155InvalidSender(address from);
error ERC1155InvalidReceiver(address to);
error ERC1155MissingApprovalForAll(address operator, address owner);
error ERC1155InvalidApprover(address approver);
error ERC1155InvalidOperator(address operator);
error ERC1155InvalidArrayLength();
}
interface IERC1155Receiver is IERC165 {
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 amount,
bytes calldata data
) external view returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external returns (bytes4);
}
abstract contract ERC1155 is IERC1155, IERC1155Errors {
string private _uri;
mapping(uint256 id => mapping(address account => uint256)) _balances;
mapping(address account => mapping(address operator => bool)) _approvals;
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
super.supportsInterface(interfaceId);
}
function balanceOf(
address account,
uint256 id
) public view virtual returns (uint256) {
return _balances[id][account];
}
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual returns (uint256[] memory) {
if (accounts.length != ids.length) {
revert ERC1155InvalidArrayLength(ids.length, accounts.length);
}
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(
accounts.unsafeMemoryAccess(i),
ids.unsafeMemoryAccess(i)
);
}
return batchBalances;
}
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual returns (uint256[] memory) {
if (accounts.length != ids.length) revert ERC1155InvalidArrayLength();
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids);
}
return batchBalances;
}
function setApprovalForAll(address operator, bool approved) public virtual {
_setApprovalForAll(msg.sender, operator, approved);
}
function isApprovedForAll(
address account,
address operator
) public view virtual returns (bool) {
return _approvals[account][operator];
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 value,
bytes memory data
) public virtual {
if (from != msg.sender && !isApprovedForAll(from, msg.sender)) {
revert ERC1155MissingApprovalForAll(msg.sender, from);
}
_safeTransferFrom(from, to, id, value, data);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) public virtual {
if (from != sender && !isApprovedForAll(from, sender)) {
revert ERC1155MissingApprovalForAll(sender, from);
}
_safeBatchTransferFrom(from, to, ids, values, data);
}
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
uint256[] memory ids = new uint256[](1);
ids[0] = id;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;
_checkSafeTransfer(msg.sender, from, to, id, amount, "");
_update(from, to, ids, amounts);
}
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
_checkSafeBatchTransfer(msg.sender, from, to, ids, amounts, "");
_update(from, to, ids, amounts);
}
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
if (operator == address(0)) revert ERC1155InvalidOperator(address(0));
_approvals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/*
* mint
* @param to
* @param id
* @param amount
*/
function _mint(address to, uint256 id, uint256 amount) internal {
if (to == address(0)) revert ERC1155InvalidReceiver(address(0));
uint256[] memory ids = new uint256[](1);
ids[0] = id;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;
_checkSafeTransfer(msg.sender, address(0), to, id, amount, "");
_update(address(0), to, ids, amounts);
}
/*
* batch mint
* @param to
* @param ids
* @param amounts
* @param data
*/
function _batchMint(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
if (to == address(0)) revert ERC1155InvalidReceiver(address(0));
_checkSafeBatchTransfer(msg.sender, address(0), to, ids, amounts, "");
_update(address(0), to, ids, amounts);
}
/*
* burn token
* @param from
* @param id
* @param amount
*/
function _burn(address from, uint256 id, uint256 amount) internal {
if (from == address(0)) revert ERC1155InvalidSender(address(0));
uint256[] memory ids = new uint256[](1);
ids[0] = id;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;
_checkSafeTransfer(msg.sender, from, address(0), id, amount, "");
_update(from, address(0), ids, amounts);
}
/*
* batch burn multiple token
* @param from
* @param ids
* @param amounts
* @param data
*/
function _batchBurn(
address from,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
if (from == address(0)) revert ERC1155InvalidSender(address(0));
_checkSafeBatchTransfer(msg.sender, from, address(0), ids, amounts, "");
_update(from, address(0), ids, amounts);
}
/*
* update or batch update transfer two parties' balances
* @param from
* @param to
* @param ids
* @param amounts
*/
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
if (ids.length != amounts.length) revert ERC1155InvalidArrayLength();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
if (from != address(0)) {
uint256 fromBalance = _balances[id][from];
if (fromBalance < amount) {
revert ERC1155InsufficientBalance(
from,
fromBalance,
amount,
id
);
}
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
if (to != address(0)) {
_balances[id][to] += amount;
}
}
// send transfer event
if (ids.length == 1) {
uint256 id = ids[0];
uint256 amount = amounts[0];
emit TransferSingle(msg.sender, from, to, id, amount);
} else {
emit TransferBatch(msg.sender, from, to, ids, amounts);
}
}
/*
* Check if contract is able to transfer while the receiver is contract
* @param operator
* @param from
* @param to
* @param id
* @param amount
* @param data
*/
function _checkSafeTransfer(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal {
if (!(to.code.length > 0)) return;
try
IERC1155Receiver(to).onERC1155Received(
operator,
from,
id,
amount,
data
)
returns (bytes4 selector) {
if (selector != IERC1155Receiver.onERC1155Received.selector) {
revert ERC1155InvalidReceiver(to);
}
} catch (bytes memory reason) {
// bytes is a dynamic array type, first 32 bit is array length
// mload(reason): read the reason first 32 byte length that means the actual error data
// add(32, reason): reason point to start location in the memory, So here means message offset which should be revert
assembly {
revert(add(32, reason), mload(reason))
}
}
}
/*
* Check if contract is able to batch transfer while the receiver is contract
* @param operator
* @param from
* @param to
* @param ids
* @param amounts
* @param data
*/
function _checkSafeBatchTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
if (!(to.code.length > 0)) return;
try
IERC1155Receiver(to).onERC1155BatchReceived(
operator,
from,
ids,
amounts,
data
)
returns (bytes4 selector) {
if (selector != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert ERC1155InvalidReceiver(to);
}
} catch (bytes memory reason) {
// bytes is a dynamic array type, first 32 bit is array length
// mload(reason): read the reason first 32 byte length that means the actual error data
// add(32, reason): reason point to start location in the memory, So here means message offset which should be revert
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}