// SPDX-License-Identifier: Proprietary pragma solidity ^0.7.2; pragma experimental ABIEncoderV2; import "./Address.sol"; import "./SafeMath.sol"; import "./Strings.sol"; import "./LibControl.sol"; import "./LibPause.sol"; import "./LibERC1155.sol"; import "./IERC1155.sol"; import "./IERC1155Receiver.sol"; import "./IBurnable.sol"; import "./IMintable.sol"; import "./IPriceChange.sol"; import "./IERC1155MetadataURI.sol"; import "./IERC1155Extra.sol"; contract ERC1155Facet is IERC1155, IERC1155MetadataURI, IERC1155Extra, IBurnable, IMintable, IPriceChange { using Address for address; using Strings for string; using SafeMath for uint256; using SafeMath for uint16; modifier onlyCreator { LibControl.enforceIsCreator(); _; } modifier onlyManager { LibControl.enforceIsManager(); _; } modifier whenNotPaused { require(!LibPause.paused()); _; } /** Generates URI in form of {uri prefix}{ipfshash} IDs between stores should never clash because of embedded store id */ function uri(uint256 _id) external virtual override view returns (string memory) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return Strings.strConcat( store._uri, store.nftData[_id].metadataIpfsHash ); } /** Should really only ever be "ipfs://" */ function setURI(string calldata _newURI) external virtual override onlyManager { LibERC1155.setURI(_newURI); } function name() external override view returns(string memory) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.name; } function symbol() external override view returns(string memory) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.symbol; } function proxyRegistryAddress() external override view returns(address) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.proxyRegistryAddress; } function setProxyRegistryAddress(address _newAddress) external override onlyManager { LibERC1155.setProxyRegistryAddress(_newAddress); } function nftData(uint256 _id) external override view returns(LibERC1155.Nft memory) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.nftData[_id]; } function storeId(uint256 _id) external override pure returns(uint24) { return LibERC1155.getStoreId(_id); } function nonce() external override view returns(uint64) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.nonce; } function metaNonce() external override view returns(uint64) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.metaNonce; } function metaId(uint256 _id) external override pure returns(uint64) { return LibERC1155.getMetaId(_id); } function subId(uint256 _id) external override pure returns(uint64) { return LibERC1155.getSubId(_id); } function quantityForId(uint256 _id) external override pure returns (uint8) { return LibERC1155.getQuantity(_id); } function balanceOf(address account, uint256 id) external view override returns (uint256) { require(account != address(0), "0 disallowed"); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); if (LibERC1155.isFungible(id)) { return store._balances[id][account]; } else { return store.nftData[id].owner == account ? 1 : 0; } } function balanceOfBatch( address[] calldata accounts, uint256[] calldata ids ) external view override returns (uint256[] memory) { require(accounts.length == ids.length, "parm len mismatch"); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); uint256[] memory batchBalances = new uint256[](accounts.length); for (uint256 i = 0; i < accounts.length; ++i) { require(accounts[i] != address(0), "0 disallowed"); if (LibERC1155.isFungible(ids[i])) { batchBalances[i] = store._balances[ids[i]][accounts[i]]; } else { batchBalances[i] = store.nftData[ids[i]].owner == accounts[i] ? 1 : 0; } } return batchBalances; } function isDataLocked(uint256 _id) override external view returns (bool) { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); return store.nftData[_id].dataLocked; } function lockData(uint256 _id) override external { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); require(!store.nftData[_id].dataLocked, "Already locked"); bool isCreatorControlled = msg.sender == LibControl.creator() && store.nftData[_id].owner == msg.sender; require(isCreatorControlled || msg.sender == LibControl.manager() || msg.sender == LibControl.bridge(), "Not permitted to lock"); store.nftData[_id].dataLocked = true; } /** Caution, remember there is a prefix */ function changeMetadataUri(uint256 _id, string memory _uri) override external { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); require(!store.nftData[_id].dataLocked, "Data is locked"); require(msg.sender == LibControl.bridge(), "Only bridge can change URI"); store.nftData[_id].metadataIpfsHash = _uri; emit URI(_uri, _id); } /** Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-free listings. */ function isApprovedForAll(address _account, address _operator) public view override returns (bool) { return LibERC1155.isApprovedForAll(_account, _operator); } function setApprovalForAll(address operator, bool approved) external virtual override whenNotPaused { LibERC1155.setApprovalForAll(operator, approved); } function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes memory data ) public virtual override whenNotPaused { require(to != address(0), "To 0 disallowed"); LibERC1155.safeTransferFrom(from, to, id, amount, data); } function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override whenNotPaused { require(to != address(0), "To 0 disallowed"); LibERC1155.safeBatchTransferFrom(from, to, ids, amounts, data); } /** * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`. * * Emits a {TransferSingle} event. * * Requirements: * * - `account` 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 account, uint256 id, uint256 amount, bytes memory data) internal virtual { require(account != address(0), "Mint to 0"); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); address operator = msg.sender; LibERC1155._beforeTokenTransfer(operator, address(0), account, LibERC1155._asSingletonArray(id), LibERC1155._asSingletonArray(amount), data); store._balances[id][account] = store._balances[id][account].add(amount); emit TransferSingle(operator, address(0), account, id, amount); LibERC1155._doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data); } /** You can never mint more of the same meta id. */ function mintNonFungible( bool sameMetaId, LibERC1155.Nft[] memory nfts ) external virtual override whenNotPaused onlyCreator returns(uint256[] memory) { require (nfts.length > 0, "0 quantity"); uint8 quantity = uint8(nfts.length); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); uint256[] memory ids = new uint256[](quantity); uint256[] memory values = new uint256[](quantity); // just an array of 1's for the event address to = nfts[0].owner; // only used if every nft has same owner bool sameOwner = true; uint24 _storeId = LibERC1155.getERC1155Store().storeId; uint64 _metaId = LibERC1155.getERC1155Store().metaNonce; uint8 _subId = 0; // reset for every meta id for (uint8 i = 0; i < quantity; ++i) { LibERC1155.Nft memory nft = nfts[i]; require(nft.price == 0 || nft.price >= 100, "price must be 0 or above 100 wei"); require(nft.tokenPrice == 0 || nft.tokenPrice >= 100, "token price must be 0 or above 100 wei"); require(nft.owner != address(0), "Cannot mint to 0 address"); values[i] = 1; sameOwner = sameOwner && ( nft.owner == to ); ids[i] = (uint256(_storeId) << LibERC1155.STORE_ID_OFFSET) | LibERC1155.NFT_FLAG | (uint256(_metaId) << LibERC1155.META_ID_OFFSET) | (uint256(quantity) << LibERC1155.QUANTITY_OFFSET) | uint256(_subId) ; store.nftData[ids[i]] = nft; if (nft.forSale == LibERC1155.NFTLucreState.ForSale || nft.tokenForSale == LibERC1155.NFTLucreState.ForSale) { emit PriceChange( ids[i], nft.owner, nft.price, nft.tokenPrice, nft.forSale == LibERC1155.NFTLucreState.ForSale, nft.tokenForSale == LibERC1155.NFTLucreState.ForSale ); } if (sameMetaId) { _subId++; } else { _metaId++; } } LibERC1155.incrementMetaNonce( sameMetaId ? 1 : quantity ); if (sameOwner) { emit TransferBatch(msg.sender, address(0), to, ids, values); } else { for (uint8 i = 0; i < quantity; ++i) { emit TransferSingle(msg.sender, address(0), nfts[i].owner, ids[i], 1); } } return ids; } function mintFungible(address _to, uint256 _id, uint256 _quantity) external virtual override whenNotPaused onlyCreator { uint24 _storeId = LibERC1155.getERC1155Store().storeId; uint256 _nonce = LibERC1155.getERC1155Store().nonce; uint256 _nextFungibleId = (uint256(_storeId) << LibERC1155.STORE_ID_OFFSET) | _nonce; // can only mint more of an existing, or next id if (_id == _nextFungibleId) { LibERC1155.incrementNonce(1); _nextFungibleId += 1; } require(_id < _nextFungibleId, "Invalid fungible ID"); _mint(_to, _id, _quantity, ""); } function mintFungible(address _to, uint256 _quantity) external virtual override whenNotPaused onlyCreator returns(uint256) { uint24 _storeId = LibERC1155.getERC1155Store().storeId; uint256 _nonce = LibERC1155.getERC1155Store().nonce; uint256 _id = (uint256(_storeId) << LibERC1155.STORE_ID_OFFSET) | _nonce; require(_id < (uint256(_storeId) << LibERC1155.STORE_ID_OFFSET) | LibERC1155.NFT_FLAG , "Out of fungible IDs"); LibERC1155.incrementNonce(1); _mint(_to, _id, _quantity, ""); return _id; } function isNonFungibleItem(uint256 _id) external pure virtual override returns(bool) { return LibERC1155.isNonFungible(_id); } function getSetForId(uint256 _id) external pure virtual override returns (uint256[] memory) { return LibERC1155.getSetForId(_id); } /** * @dev Destroys `amount` tokens of token type `id` from `account` * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens of token type `id`. */ function _burn(address account, uint256 id, uint256 amount) internal virtual { require(account != address(0), "Burn from 0"); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); address operator = msg.sender; LibERC1155._beforeTokenTransfer(operator, account, address(0), LibERC1155._asSingletonArray(id), LibERC1155._asSingletonArray(amount), ""); store._balances[id][account] = store._balances[id][account].sub( amount, "Burn > bal" ); emit TransferSingle(operator, account, address(0), id, amount); } /** Anyone can burn their fungible tokens but only someone both owner and creator can burn an NFT. */ function burn(address account, uint256 id, uint256 amount) external whenNotPaused override virtual { LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); if (LibERC1155.isNonFungible(id)) { require(amount == 1, "Burn 1 only"); LibERC1155.Nft storage nft = store.nftData[id]; require(account == nft.owner, "Account doesn't own NFT"); require( msg.sender == nft.owner || msg.sender == LibControl.controlStorage().mothership.bridge() || isApprovedForAll(account, msg.sender ), "Disallowed"); address operator = msg.sender; LibERC1155._beforeTokenTransfer(operator, account, address(0), LibERC1155._asSingletonArray(id), LibERC1155._asSingletonArray(amount), ""); nft.owner = address(0); LibERC1155.resetNftPrice(nft); // not bothering to emit a price change emit TransferSingle(operator, account, address(0), id, 1); } else { require(account == msg.sender || isApprovedForAll(account, msg.sender), "Disallowed"); _burn(account, id, amount); } } function burnBatch(address account, uint256[] calldata ids, uint256[] calldata amounts) external whenNotPaused override virtual { require(account != address(0), "Burn 0 disallowed"); require(ids.length == amounts.length, "Len mismatch"); LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store(); address operator = msg.sender; LibERC1155._beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); for (uint i = 0; i < ids.length; i++) { uint256 id = ids[i]; uint256 amount = amounts[i]; if (LibERC1155.isFungible(id)) { require( account == msg.sender || isApprovedForAll(account, msg.sender), "Disallowed" ); store._balances[id][account] = store._balances[id][account].sub( amount, "Burn exceeds bal" ); emit TransferSingle(operator, account, address(0), id, 1); } else { require(amount == 1, "Burn 1 only"); LibERC1155.Nft storage nft = store.nftData[id]; require(account == nft.owner, "Account doesn't own NFT"); require( ( msg.sender == nft.owner ) || isApprovedForAll(account, msg.sender) || msg.sender == LibControl.controlStorage().mothership.bridge(), "Disallowed" ); nft.owner = address(0); LibERC1155.resetNftPrice(nft); // not bothering to emit a price change emit TransferSingle(operator, account, address(0), id, 1); } } } }