|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)
|
|
|
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
|
|
import "./IERC1155.sol";
|
|
|
|
import "./IERC1155Receiver.sol";
|
|
|
|
import "./extensions/IERC1155MetadataURI.sol";
|
|
|
|
import "../../utils/Context.sol";
|
|
|
|
import "../../utils/introspection/ERC165.sol";
|
|
|
|
import "../../utils/Arrays.sol";
|
|
|
|
import "../../interfaces/draft-IERC6093.sol";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Implementation of the basic standard multi-token.
|
|
|
|
* See https://eips.ethereum.org/EIPS/eip-1155
|
|
|
|
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
|
|
|
|
*
|
|
|
|
* _Available since v3.1._
|
|
|
|
*/
|
|
|
|
abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors {
|
|
|
|
using Arrays for uint256[];
|
|
|
|
using Arrays for address[];
|
|
|
|
|
|
|
|
// Mapping from token ID to account balances
|
|
|
|
mapping(uint256 => mapping(address => uint256)) private _balances;
|
|
|
|
|
|
|
|
// Mapping from account to operator approvals
|
|
|
|
mapping(address => mapping(address => bool)) private _operatorApprovals;
|
|
|
|
|
|
|
|
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
|
|
|
|
string private _uri;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {_setURI}.
|
|
|
|
*/
|
|
|
|
constructor(string memory uri_) {
|
|
|
|
_setURI(uri_);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC165-supportsInterface}.
|
|
|
|
*/
|
|
|
|
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
|
|
|
|
return
|
|
|
|
interfaceId == type(IERC1155).interfaceId ||
|
|
|
|
interfaceId == type(IERC1155MetadataURI).interfaceId ||
|
|
|
|
super.supportsInterface(interfaceId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155MetadataURI-uri}.
|
|
|
|
*
|
|
|
|
* This implementation returns the same URI for *all* token types. It relies
|
|
|
|
* on the token type ID substitution mechanism
|
|
|
|
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
|
|
|
|
*
|
|
|
|
* Clients calling this function must replace the `\{id\}` substring with the
|
|
|
|
* actual token type ID.
|
|
|
|
*/
|
|
|
|
function uri(uint256) public view virtual returns (string memory) {
|
|
|
|
return _uri;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-balanceOf}.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `account` cannot be the zero address.
|
|
|
|
*/
|
|
|
|
function balanceOf(address account, uint256 id) public view virtual returns (uint256) {
|
|
|
|
return _balances[id][account];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-balanceOfBatch}.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `accounts` and `ids` must have the same length.
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-setApprovalForAll}.
|
|
|
|
*/
|
|
|
|
function setApprovalForAll(address operator, bool approved) public virtual {
|
|
|
|
_setApprovalForAll(_msgSender(), operator, approved);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-isApprovedForAll}.
|
|
|
|
*/
|
|
|
|
function isApprovedForAll(address account, address operator) public view virtual returns (bool) {
|
|
|
|
return _operatorApprovals[account][operator];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-safeTransferFrom}.
|
|
|
|
*/
|
|
|
|
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) public virtual {
|
|
|
|
if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) {
|
|
|
|
revert ERC1155InsufficientApprovalForAll(_msgSender(), from);
|
|
|
|
}
|
|
|
|
_safeTransferFrom(from, to, id, amount, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC1155-safeBatchTransferFrom}.
|
|
|
|
*/
|
|
|
|
function safeBatchTransferFrom(
|
|
|
|
address from,
|
|
|
|
address to,
|
|
|
|
uint256[] memory ids,
|
|
|
|
uint256[] memory amounts,
|
|
|
|
bytes memory data
|
|
|
|
) public virtual {
|
|
|
|
if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) {
|
|
|
|
revert ERC1155InsufficientApprovalForAll(_msgSender(), from);
|
|
|
|
}
|
|
|
|
_safeBatchTransferFrom(from, to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address.
|
|
|
|
*
|
|
|
|
* Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received}
|
|
|
|
* or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value.
|
|
|
|
*/
|
|
|
|
function _update(
|
|
|
|
address from,
|
|
|
|
address to,
|
|
|
|
uint256[] memory ids,
|
|
|
|
uint256[] memory amounts,
|
|
|
|
bytes memory data
|
|
|
|
) internal virtual {
|
|
|
|
if (ids.length != amounts.length) {
|
|
|
|
revert ERC1155InvalidArrayLength(ids.length, amounts.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
address operator = _msgSender();
|
|
|
|
|
|
|
|
for (uint256 i = 0; i < ids.length; ++i) {
|
|
|
|
uint256 id = ids.unsafeMemoryAccess(i);
|
|
|
|
uint256 amount = amounts.unsafeMemoryAccess(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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ids.length == 1) {
|
|
|
|
uint256 id = ids.unsafeMemoryAccess(0);
|
|
|
|
uint256 amount = amounts.unsafeMemoryAccess(0);
|
|
|
|
emit TransferSingle(operator, from, to, id, amount);
|
|
|
|
if (to != address(0)) {
|
|
|
|
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
emit TransferBatch(operator, from, to, ids, amounts);
|
|
|
|
if (to != address(0)) {
|
|
|
|
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
|
|
|
|
*
|
|
|
|
* Emits a {TransferSingle} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `to` cannot be the zero address.
|
|
|
|
* - `from` must have a balance of tokens of type `id` of at least `amount`.
|
|
|
|
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
|
|
|
|
* acceptance magic value.
|
|
|
|
*/
|
|
|
|
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, uint256[] memory amounts) = _asSingletonArrays(id, amount);
|
|
|
|
_update(from, to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
|
|
|
|
*
|
|
|
|
* Emits a {TransferBatch} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
|
|
|
|
* acceptance magic value.
|
|
|
|
*/
|
|
|
|
function _safeBatchTransferFrom(
|
|
|
|
address from,
|
|
|
|
address to,
|
|
|
|
uint256[] memory ids,
|
|
|
|
uint256[] memory amounts,
|
|
|
|
bytes memory data
|
|
|
|
) internal {
|
|
|
|
if (to == address(0)) {
|
|
|
|
revert ERC1155InvalidReceiver(address(0));
|
|
|
|
}
|
|
|
|
if (from == address(0)) {
|
|
|
|
revert ERC1155InvalidSender(address(0));
|
|
|
|
}
|
|
|
|
_update(from, to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Sets a new URI for all token types, by relying on the token type ID
|
|
|
|
* substitution mechanism
|
|
|
|
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
|
|
|
|
*
|
|
|
|
* By this mechanism, any occurrence of the `\{id\}` substring in either the
|
|
|
|
* URI or any of the amounts in the JSON file at said URI will be replaced by
|
|
|
|
* clients with the token type ID.
|
|
|
|
*
|
|
|
|
* For example, the `https://token-cdn-domain/\{id\}.json` URI would be
|
|
|
|
* interpreted by clients as
|
|
|
|
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
|
|
|
|
* for token type ID 0x4cce0.
|
|
|
|
*
|
|
|
|
* See {uri}.
|
|
|
|
*
|
|
|
|
* Because these URIs cannot be meaningfully represented by the {URI} event,
|
|
|
|
* this function emits no events.
|
|
|
|
*/
|
|
|
|
function _setURI(string memory newuri) internal virtual {
|
|
|
|
_uri = newuri;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
|
|
|
|
*
|
|
|
|
* Emits a {TransferSingle} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `to` cannot be the zero address.
|
|
|
|
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
|
|
|
|
* acceptance magic value.
|
|
|
|
*/
|
|
|
|
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal {
|
|
|
|
if (to == address(0)) {
|
|
|
|
revert ERC1155InvalidReceiver(address(0));
|
|
|
|
}
|
|
|
|
(uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount);
|
|
|
|
_update(address(0), to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
|
|
|
|
*
|
|
|
|
* Emits a {TransferBatch} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `ids` and `amounts` must have the same length.
|
|
|
|
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
|
|
|
|
* acceptance magic value.
|
|
|
|
*/
|
|
|
|
function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal {
|
|
|
|
if (to == address(0)) {
|
|
|
|
revert ERC1155InvalidReceiver(address(0));
|
|
|
|
}
|
|
|
|
_update(address(0), to, ids, amounts, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Destroys `amount` tokens of token type `id` from `from`
|
|
|
|
*
|
|
|
|
* Emits a {TransferSingle} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `from` cannot be the zero address.
|
|
|
|
* - `from` must have at least `amount` tokens of token type `id`.
|
|
|
|
*/
|
|
|
|
function _burn(address from, uint256 id, uint256 amount) internal {
|
|
|
|
if (from == address(0)) {
|
|
|
|
revert ERC1155InvalidSender(address(0));
|
|
|
|
}
|
|
|
|
(uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount);
|
|
|
|
_update(from, address(0), ids, amounts, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
|
|
|
|
*
|
|
|
|
* Emits a {TransferBatch} event.
|
|
|
|
*
|
|
|
|
* Requirements:
|
|
|
|
*
|
|
|
|
* - `ids` and `amounts` must have the same length.
|
|
|
|
*/
|
|
|
|
function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal {
|
|
|
|
if (from == address(0)) {
|
|
|
|
revert ERC1155InvalidSender(address(0));
|
|
|
|
}
|
|
|
|
_update(from, address(0), ids, amounts, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Approve `operator` to operate on all of `owner` tokens
|
|
|
|
*
|
|
|
|
* Emits an {ApprovalForAll} event.
|
|
|
|
*/
|
|
|
|
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
|
|
|
|
if (owner == operator) {
|
|
|
|
revert ERC1155InvalidOperator(operator);
|
|
|
|
}
|
|
|
|
_operatorApprovals[owner][operator] = approved;
|
|
|
|
emit ApprovalForAll(owner, operator, approved);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _doSafeTransferAcceptanceCheck(
|
|
|
|
address operator,
|
|
|
|
address from,
|
|
|
|
address to,
|
|
|
|
uint256 id,
|
|
|
|
uint256 amount,
|
|
|
|
bytes memory data
|
|
|
|
) private {
|
|
|
|
if (to.code.length > 0) {
|
|
|
|
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
|
|
|
|
if (response != IERC1155Receiver.onERC1155Received.selector) {
|
|
|
|
// Tokens rejected
|
|
|
|
revert ERC1155InvalidReceiver(to);
|
|
|
|
}
|
|
|
|
} catch Error(string memory reason) {
|
|
|
|
revert(reason);
|
|
|
|
} catch {
|
|
|
|
// non-ERC1155Receiver implementer
|
|
|
|
revert ERC1155InvalidReceiver(to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _doSafeBatchTransferAcceptanceCheck(
|
|
|
|
address operator,
|
|
|
|
address from,
|
|
|
|
address to,
|
|
|
|
uint256[] memory ids,
|
|
|
|
uint256[] memory amounts,
|
|
|
|
bytes memory data
|
|
|
|
) private {
|
|
|
|
if (to.code.length > 0) {
|
|
|
|
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
|
|
|
|
bytes4 response
|
|
|
|
) {
|
|
|
|
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
|
|
|
|
// Tokens rejected
|
|
|
|
revert ERC1155InvalidReceiver(to);
|
|
|
|
}
|
|
|
|
} catch Error(string memory reason) {
|
|
|
|
revert(reason);
|
|
|
|
} catch {
|
|
|
|
// non-ERC1155Receiver implementer
|
|
|
|
revert ERC1155InvalidReceiver(to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _asSingletonArrays(
|
|
|
|
uint256 element1,
|
|
|
|
uint256 element2
|
|
|
|
) private pure returns (uint256[] memory array1, uint256[] memory array2) {
|
|
|
|
/// @solidity memory-safe-assembly
|
|
|
|
assembly {
|
|
|
|
array1 := mload(0x40)
|
|
|
|
mstore(array1, 1)
|
|
|
|
mstore(add(array1, 0x20), element1)
|
|
|
|
|
|
|
|
array2 := add(array1, 0x40)
|
|
|
|
mstore(array2, 1)
|
|
|
|
mstore(add(array2, 0x20), element2)
|
|
|
|
|
|
|
|
mstore(0x40, add(array2, 0x40))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|