// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC1155Delta} from "../interfaces/IERC1155Delta.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; import {Context} from "@openzeppelin/contracts/utils/Context.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {LibBitmap} from "solady/src/utils/LibBitmap.sol"; contract ERC1155Delta is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Delta { using LibBitmap for LibBitmap.Bitmap; // Mapping from accout to owned tokens mapping(address => LibBitmap.Bitmap) internal _owned; // 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; // The next token ID to be minted. uint256 internal _currentIndex; /** * @dev See {_setURI}. */ constructor(string memory uri_) { _setURI(uri_); _currentIndex = _startTokenId(); } /** * @dev Returns the starting token ID. * To change the starting token ID, please override this function. */ function _startTokenId() internal pure virtual returns (uint256) { return 1; } /** * @dev Returns the next token ID to be minted. */ function _nextTokenId() internal view returns (uint256) { return _currentIndex; } /** * @dev Returns true if the account owns the `id` token. */ function isOwnerOf(address account, uint256 id) public view virtual override returns (bool) { return _owned[account].get(id); } /** * @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 || interfaceId == type(IERC1155Delta).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 override 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 override returns (uint256) { if (account == address(0)) { revert BalanceQueryForZeroAddress(); } if (_owned[account].get(id)) { return 1; } else { return 0; } } /** * @dev See {IERC1155-balanceOfBatch}. * * Requirements: * * - `accounts` and `ids` must have the same length. */ function balanceOfBatch( address[] memory accounts, uint256[] memory ids ) public view virtual override returns (uint256[] memory) { if (accounts.length != ids.length) { revert InputLengthMistmatch(); } uint256[] memory batchBalances = new uint256[](accounts.length); for (uint256 i = 0; i < accounts.length; ++i) { batchBalances[i] = balanceOf(accounts[i], ids[i]); } return batchBalances; } /** * @dev See {IERC1155-setApprovalForAll}. */ function setApprovalForAll(address operator, bool approved) public virtual override { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC1155-isApprovedForAll}. */ function isApprovedForAll(address account, address operator) public view virtual override 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 override { if (from == _msgSender() || isApprovedForAll(from, _msgSender())) { _safeTransferFrom(from, to, id, amount, data); } else { revert TransferCallerNotOwnerNorApproved(); } } /** * @dev See {IERC1155-safeBatchTransferFrom}. */ function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { if (!(from == _msgSender() || isApprovedForAll(from, _msgSender()))) { revert TransferCallerNotOwnerNorApproved(); } _safeBatchTransferFrom(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. * - `amount` cannot be zero. * - `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 virtual { if (to == address(0)) { revert TransferToZeroAddress(); } address operator = _msgSender(); uint256[] memory ids = _asSingletonArray(id); _beforeTokenTransfer(operator, from, to, ids); if (amount == 1 && _owned[from].get(id)) { _owned[from].unset(id); _owned[to].set(id); } else { revert TransferFromIncorrectOwnerOrInvalidAmount(); } emit TransferSingle(operator, from, to, id, amount); _afterTokenTransfer(operator, from, to, ids); _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, 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 virtual { if (ids.length != amounts.length) { revert InputLengthMistmatch(); } if (to == address(0)) { revert TransferToZeroAddress(); } address operator = _msgSender(); _beforeTokenTransfer(operator, from, to, ids); for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids[i]; uint256 amount = amounts[i]; if (amount == 1 && _owned[from].get(id)) { _owned[from].unset(id); _owned[to].set(id); } else { revert TransferFromIncorrectOwnerOrInvalidAmount(); } } emit TransferBatch(operator, from, to, ids, amounts); _afterTokenTransfer(operator, from, to, ids); _doSafeBatchTransferAcceptanceCheck(operator, 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; } function _mint(address to, uint256 amount) internal virtual { _mint(to, amount, ""); } /** * @dev Creates `amount` tokens, and assigns them to `to`. * * Emits a {TransferBatch} event. * * Requirements: * * - `to` cannot be the zero address. * - `amount` cannot be zero. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ function _mint(address to, uint256 amount, bytes memory data) internal virtual { (uint256[] memory ids, uint256[] memory amounts) = _mintWithoutCheck(to, amount); uint256 end = _currentIndex; _doSafeBatchTransferAcceptanceCheck(_msgSender(), address(0), to, ids, amounts, data); if (_currentIndex != end) revert(); } function _mintWithoutCheck( address to, uint256 amount ) internal virtual returns (uint256[] memory ids, uint256[] memory amounts) { if (to == address(0)) { revert MintToZeroAddress(); } if (amount == 0) { revert MintZeroQuantity(); } address operator = _msgSender(); ids = new uint256[](amount); amounts = new uint256[](amount); uint256 startTokenId = _nextTokenId(); unchecked { require(type(uint256).max - amount >= startTokenId, "Minting more tokens than the maximum allowed"); for (uint256 i = 0; i < amount; i++) { ids[i] = startTokenId + i; amounts[i] = 1; } } _beforeTokenTransfer(operator, address(0), to, ids); _owned[to].setBatch(startTokenId, amount); _currentIndex += amount; emit TransferBatch(operator, address(0), to, ids, amounts); _afterTokenTransfer(operator, address(0), to, ids); } /** * @dev Destroys token of token type `id` from `from` * * Emits a {TransferSingle} event. * * Requirements: * * - `from` cannot be the zero address. * - `from` must have the token of token type `id`. */ function _burn(address from, uint256 id) internal virtual { if (from == address(0)) { revert BurnFromZeroAddress(); } address operator = _msgSender(); uint256[] memory ids = _asSingletonArray(id); _beforeTokenTransfer(operator, from, address(0), ids); if (!_owned[from].get(id)) { revert BurnFromNonOnwerAddress(); } _owned[from].unset(id); emit TransferSingle(operator, from, address(0), id, 1); _afterTokenTransfer(operator, from, address(0), ids); } /** * @dev Destroys tokens of token types in `ids` from `from` * * Emits a {TransferBatch} event. * * Requirements: * * - `from` cannot be the zero address. * - `from` must have the token of token types in `ids`. */ function _burnBatch(address from, uint256[] memory ids) internal virtual { if (from == address(0)) { revert BurnFromZeroAddress(); } address operator = _msgSender(); uint256[] memory amounts = new uint256[](ids.length); _beforeTokenTransfer(operator, from, address(0), ids); unchecked { for (uint256 i = 0; i < ids.length; i++) { amounts[i] = 1; uint256 id = ids[i]; if (!_owned[from].get(id)) { revert BurnFromNonOnwerAddress(); } _owned[from].unset(id); } } emit TransferBatch(operator, from, address(0), ids, amounts); _afterTokenTransfer(operator, from, address(0), ids); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. */ 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); } /** * @dev Hook that is called before any token transfer. This includes minting * and burning, as well as batched variants. * * The same hook is called on both single and batched variants. For single * transfers, the length of the `ids` and `amounts` arrays will be 1. * * Calling conditions (for each `id` and `amount` pair): * * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens * of token type `id` will be transferred to `to`. * - When `from` is zero, `amount` tokens of token type `id` will be minted * for `to`. * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` * will be burned. * - `from` and `to` are never both zero. * - `ids` and `amounts` have the same, non-zero length. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids) internal virtual {} /** * @dev Hook that is called after any token transfer. This includes minting * and burning, as well as batched variants. * * The same hook is called on both single and batched variants. For single * transfers, the length of the `id` and `amount` arrays will be 1. * * Calling conditions (for each `id` and `amount` pair): * * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens * of token type `id` will be transferred to `to`. * - When `from` is zero, `amount` tokens of token type `id` will be minted * for `to`. * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` * will be burned. * - `from` and `to` are never both zero. * - `ids` and `amounts` have the same, non-zero length. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address operator, address from, address to, uint256[] memory ids) internal virtual {} 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) { revert TransferToNonERC1155ReceiverImplementer(); } } catch Error(string memory reason) { revert(reason); } catch { revert TransferToNonERC1155ReceiverImplementer(); } } } 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) { revert TransferToNonERC1155ReceiverImplementer(); } } catch Error(string memory reason) { revert(reason); } catch { revert TransferToNonERC1155ReceiverImplementer(); } } } function _asSingletonArray(uint256 element) private pure returns (uint256[] memory array) { array = new uint256[](1); array[0] = element; } }