// SPDX-License-Identifier: MIT pragma solidity ^0.8.15; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Context.sol"; import "../IERC1155Events.sol"; import "../IERC1155Receiver.sol"; import "./ERC1155BaseStorage.sol"; /** * @title Base ERC1155 internal functions * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license) */ abstract contract ERC1155BaseInternal is Context, IERC1155Events { using Address for address; /** * @notice query the balance of given token held by given address * @param account address to query * @param id token to query * @return token balance */ function _balanceOf(address account, uint256 id) internal view virtual returns (uint256) { require(account != address(0), "ERC1155: balance query for the zero address"); return ERC1155BaseStorage.layout().balances[id][account]; } /** * @notice mint given quantity of tokens for given address * @dev ERC1155Receiver implementation is not checked * @param account beneficiary of minting * @param id token ID * @param amount quantity of tokens to mint * @param data data payload */ function _mint( address account, uint256 id, uint256 amount, bytes calldata data ) internal virtual { address operator = _msgSender(); require(account != address(0), "ERC1155: mint to the zero address"); _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); ERC1155BaseStorage.layout().balances[id][account] += amount; emit TransferSingle(operator, address(0), account, id, amount); } /** * @notice mint given quantity of tokens for given address * @param account beneficiary of minting * @param id token ID * @param amount quantity of tokens to mint * @param data data payload */ function _safeMint( address account, uint256 id, uint256 amount, bytes calldata data ) internal virtual { _mint(account, id, amount, data); _doSafeTransferAcceptanceCheck(_msgSender(), address(0), account, id, amount, data); } /** * @notice mint batch of tokens for given address * @dev ERC1155Receiver implementation is not checked * @param account beneficiary of minting * @param ids list of token IDs * @param amounts list of quantities of tokens to mint * @param data data payload */ function _mintBatch( address account, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) internal virtual { require(account != address(0), "ERC1155: mint to the zero address"); require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); address sender = _msgSender(); _beforeTokenTransfer(sender, address(0), account, ids, amounts, data); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; for (uint256 i; i < ids.length; ) { balances[ids[i]][account] += amounts[i]; unchecked { i++; } } emit TransferBatch(sender, address(0), account, ids, amounts); } function _mintBatch( address[] calldata accounts, uint256[] calldata ids, uint256[] calldata amounts, bytes[] calldata datas ) internal virtual { require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); require(accounts.length == amounts.length, "ERC1155: accounts and amounts length mismatch"); address operator = _msgSender(); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; for (uint256 i; i < ids.length; ) { _beforeTokenTransfer( operator, address(0), accounts[i], _asSingletonArray(ids[i]), _asSingletonArray(amounts[i]), datas[i] ); balances[ids[i]][accounts[i]] += amounts[i]; emit TransferSingle(operator, address(0), accounts[i], ids[i], amounts[i]); unchecked { i++; } } } /** * @notice mint batch of tokens for given address * @param account beneficiary of minting * @param ids list of token IDs * @param amounts list of quantities of tokens to mint * @param data data payload */ function _safeMintBatch( address account, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) internal virtual { _mintBatch(account, ids, amounts, data); _doSafeBatchTransferAcceptanceCheck(_msgSender(), address(0), account, ids, amounts, data); } /** * @notice burn given quantity of tokens held by given address * @param account holder of tokens to burn * @param id token ID * @param amount quantity of tokens to burn */ function _burn( address account, uint256 id, uint256 amount ) internal virtual { require(account != address(0), "ERC1155: burn from the zero address"); address sender = _msgSender(); _beforeTokenTransfer(sender, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), ""); mapping(address => uint256) storage balances = ERC1155BaseStorage.layout().balances[id]; unchecked { require(balances[account] >= amount, "ERC1155: burn amount exceeds balance"); balances[account] -= amount; } emit TransferSingle(sender, account, address(0), id, amount); } /** * @notice burn given batch of tokens held by given address * @param account holder of tokens to burn * @param ids token IDs * @param amounts quantities of tokens to burn */ function _burnBatch( address account, uint256[] calldata ids, uint256[] calldata amounts ) internal virtual { require(account != address(0), "ERC1155: burn from the zero address"); require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); address sender = _msgSender(); _beforeTokenTransfer(sender, account, address(0), ids, amounts, ""); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; unchecked { for (uint256 i; i < ids.length; i++) { uint256 id = ids[i]; require(balances[id][account] >= amounts[i], "ERC1155: burn amount exceeds balance"); balances[id][account] -= amounts[i]; } } emit TransferBatch(sender, account, address(0), ids, amounts); } function _setApprovalForAll(address operator, bool status) internal virtual { address sender = _msgSender(); require(sender != operator, "ERC1155: setting approval status for self"); ERC1155BaseStorage.layout().operatorApprovals[sender][operator] = status; emit ApprovalForAll(sender, operator, status); } /** * @notice transfer tokens between given addresses * @dev ERC1155Receiver implementation is not checked * @param operator executor of transfer * @param sender sender of tokens * @param recipient receiver of tokens * @param id token ID * @param amount quantity of tokens to transfer * @param data data payload */ function _transfer( address operator, address sender, address recipient, uint256 id, uint256 amount, bytes calldata data ) internal virtual { require(recipient != address(0), "ERC1155: transfer to the zero address"); _beforeTokenTransfer(operator, sender, recipient, _asSingletonArray(id), _asSingletonArray(amount), data); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; unchecked { uint256 senderBalance = balances[id][sender]; require(senderBalance >= amount, "ERC1155: insufficient balances for transfer"); balances[id][sender] = senderBalance - amount; } balances[id][recipient] += amount; emit TransferSingle(operator, sender, recipient, id, amount); } /** * @notice transfer tokens between given addresses * @param operator executor of transfer * @param sender sender of tokens * @param recipient receiver of tokens * @param id token ID * @param amount quantity of tokens to transfer * @param data data payload */ function _safeTransfer( address operator, address sender, address recipient, uint256 id, uint256 amount, bytes calldata data ) internal virtual { _transfer(operator, sender, recipient, id, amount, data); _doSafeTransferAcceptanceCheck(operator, sender, recipient, id, amount, data); } /** * @notice transfer batch of tokens between given addresses * @dev ERC1155Receiver implementation is not checked * @param operator executor of transfer * @param sender sender of tokens * @param recipient receiver of tokens * @param ids token IDs * @param amounts quantities of tokens to transfer * @param data data payload */ function _transferBatch( address operator, address sender, address recipient, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual { require(recipient != address(0), "ERC1155: transfer to the zero address"); require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); _beforeTokenTransfer(operator, sender, recipient, ids, amounts, data); mapping(uint256 => mapping(address => uint256)) storage balances = ERC1155BaseStorage.layout().balances; for (uint256 i; i < ids.length; ) { uint256 token = ids[i]; uint256 amount = amounts[i]; unchecked { uint256 senderBalance = balances[token][sender]; require(senderBalance >= amount, "ERC1155: insufficient balances for transfer"); balances[token][sender] = senderBalance - amount; i++; } // balance increase cannot be unchecked because ERC1155Base neither tracks nor validates a totalSupply balances[token][recipient] += amount; } emit TransferBatch(operator, sender, recipient, ids, amounts); } /** * @notice transfer batch of tokens between given addresses * @param operator executor of transfer * @param sender sender of tokens * @param recipient receiver of tokens * @param ids token IDs * @param amounts quantities of tokens to transfer * @param data data payload */ function _safeTransferBatch( address operator, address sender, address recipient, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual { _transferBatch(operator, sender, recipient, ids, amounts, data); _doSafeBatchTransferAcceptanceCheck(operator, sender, recipient, ids, amounts, data); } /** * @notice wrap given element in array of length 1 * @param element element to wrap * @return singleton array */ function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { uint256[] memory array = new uint256[](1); array[0] = element; return array; } /** * @notice revert if applicable transfer recipient is not valid ERC1155Receiver * @param operator executor of transfer * @param from sender of tokens * @param to receiver of tokens * @param id token ID * @param amount quantity of tokens to transfer * @param data data payload */ function _doSafeTransferAcceptanceCheck( address operator, address from, address to, uint256 id, uint256 amount, bytes memory data ) private { if (to.isContract()) { try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { require( response == IERC1155Receiver.onERC1155Received.selector, "ERC1155: ERC1155Receiver rejected tokens" ); } catch Error(string memory reason) { revert(reason); } catch { revert("ERC1155: transfer to non ERC1155Receiver implementer"); } } } /** * @notice revert if applicable transfer recipient is not valid ERC1155Receiver * @param operator executor of transfer * @param from sender of tokens * @param to receiver of tokens * @param ids token IDs * @param amounts quantities of tokens to transfer * @param data data payload */ function _doSafeBatchTransferAcceptanceCheck( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) private { if (to.isContract()) { try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( bytes4 response ) { require( response == IERC1155Receiver.onERC1155BatchReceived.selector, "ERC1155: ERC1155Receiver rejected tokens" ); } catch Error(string memory reason) { revert(reason); } catch { revert("ERC1155: transfer to non ERC1155Receiver implementer"); } } } /** * @notice ERC1155 hook, called before all transfers including mint and burn * @dev function should be overridden and new implementation must call super * @dev called for both single and batch transfers * @param operator executor of transfer * @param from sender of tokens * @param to receiver of tokens * @param ids token IDs * @param amounts quantities of tokens to transfer * @param data data payload */ function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual {} }