// SPDX-License-Identifier: Apache-2.0 /** * Authors: Moonstream Engineering (engineering@moonstream.to) * GitHub: https://github.com/bugout-dev/dao * * An ERC1155 implementation which uses the Moonstream DAO common storage structure for proxies. * EIP1155: https://eips.ethereum.org/EIPS/eip-1155 * * The Moonstream contract is used to delegate calls from an EIP2535 Diamond proxy. * * This implementation is adapted from the OpenZeppelin ERC1155 implementation: * https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76d1156e20e45d1016f355d154141c7e5b9/contracts/token/ERC1155 */ pragma solidity ^0.8.9; import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol"; import "@openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; import "@openzeppelin-contracts/contracts/utils/Address.sol"; import "@openzeppelin-contracts/contracts/utils/Context.sol"; import "@openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; import "./LibTerminus.sol"; contract ERC1155WithTerminusStorage is Context, ERC165, IERC1155, IERC1155MetadataURI { using Address for address; constructor() {} /** * @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); } function uri(uint256 poolID) public view virtual override returns (string memory) { return LibTerminus.terminusStorage().poolURI[poolID]; } /** * @dev See {IERC1155-balanceOf}. * * Requirements: * * - `account` cannot be the zero address. */ function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { require( account != address(0), "ERC1155: balance query for the zero address" ); return LibTerminus.terminusStorage().poolBalances[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 override returns (uint256[] memory) { require( accounts.length == ids.length, "ERC1155: accounts and ids length mismatch" ); 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 LibTerminus.terminusStorage().globalOperatorApprovals[account][ operator ]; } /** * @dev See {IERC1155-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes memory data ) public virtual override { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not owner nor approved" ); _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 override { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: transfer caller is not owner nor approved" ); _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. * - `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 { require(to != address(0), "ERC1155: transfer to the zero address"); address operator = _msgSender(); _beforeTokenTransfer( operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, "ERC1155: insufficient balance for transfer" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; } ts.poolBalances[id][to] += amount; emit TransferSingle(operator, from, to, id, amount); _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 { require( ids.length == amounts.length, "ERC1155: ids and amounts length mismatch" ); require(to != address(0), "ERC1155: transfer to the zero address"); address operator = _msgSender(); _beforeTokenTransfer(operator, from, to, ids, amounts, data); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids[i]; uint256 amount = amounts[i]; uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, "ERC1155: insufficient balance for transfer" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; } ts.poolBalances[id][to] += amount; } emit TransferBatch(operator, from, to, ids, amounts); _doSafeBatchTransferAcceptanceCheck( operator, from, to, ids, amounts, data ); } /** * @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 virtual { require(to != address(0), "ERC1155: mint to the zero address"); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); require( ts.poolSupply[id] + amount <= ts.poolCapacity[id], "ERC1155WithTerminusStorage: _mint -- Minted tokens would exceed pool capacity" ); address operator = _msgSender(); _beforeTokenTransfer( operator, address(0), to, _asSingletonArray(id), _asSingletonArray(amount), data ); ts.poolBalances[id][to] += amount; emit TransferSingle(operator, address(0), to, id, amount); _doSafeTransferAcceptanceCheck( operator, address(0), to, id, amount, data ); } /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. * * 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 virtual { require(to != address(0), "ERC1155: mint to the zero address"); require( ids.length == amounts.length, "ERC1155: ids and amounts length mismatch" ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); for (uint256 i = 0; i < ids.length; i++) { require( ts.poolSupply[ids[i]] + amounts[i] <= ts.poolCapacity[ids[i]], "ERC1155WithTerminusStorage: _mintBatch -- Minted tokens would exceed pool capacity" ); } address operator = _msgSender(); _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); for (uint256 i = 0; i < ids.length; i++) { ts.poolBalances[ids[i]][to] += amounts[i]; } emit TransferBatch(operator, address(0), to, ids, amounts); _doSafeBatchTransferAcceptanceCheck( operator, address(0), to, ids, amounts, data ); } /** * @dev Destroys `amount` tokens of token type `id` from `from` * * 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 virtual { require(from != address(0), "ERC1155: burn from the zero address"); address operator = _msgSender(); _beforeTokenTransfer( operator, from, address(0), _asSingletonArray(id), _asSingletonArray(amount), "" ); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); uint256 fromBalance = ts.poolBalances[id][from]; require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); unchecked { ts.poolBalances[id][from] = fromBalance - amount; } emit TransferSingle(operator, from, address(0), id, amount); } /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. * * Requirements: * * - `ids` and `amounts` must have the same length. */ function _burnBatch( address from, uint256[] memory ids, uint256[] memory amounts ) internal virtual { require(from != address(0), "ERC1155: burn from the zero address"); require( ids.length == amounts.length, "ERC1155: ids and amounts length mismatch" ); address operator = _msgSender(); _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); for (uint256 i = 0; i < ids.length; i++) { uint256 id = ids[i]; uint256 amount = amounts[i]; uint256 fromBalance = ts.poolBalances[id][from]; require( fromBalance >= amount, "ERC1155: burn amount exceeds balance" ); unchecked { ts.poolBalances[id][from] = fromBalance - amount; } } emit TransferBatch(operator, from, address(0), ids, amounts); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits a {ApprovalForAll} event. */ function _setApprovalForAll( address owner, address operator, bool approved ) internal virtual { require(owner != operator, "ERC1155: setting approval status for self"); LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage(); ts.globalOperatorApprovals[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 `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 _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual {} 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) { if (response != IERC1155Receiver.onERC1155Received.selector) { revert("ERC1155: ERC1155Receiver rejected tokens"); } } catch Error(string memory reason) { revert(reason); } catch { revert("ERC1155: transfer to non ERC1155Receiver implementer"); } } } 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) { if ( response != IERC1155Receiver.onERC1155BatchReceived.selector ) { revert("ERC1155: ERC1155Receiver rejected tokens"); } } catch Error(string memory reason) { revert(reason); } catch { revert("ERC1155: transfer to non ERC1155Receiver implementer"); } } } function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { uint256[] memory array = new uint256[](1); array[0] = element; return array; } }