450 lines
16 KiB
Solidity
450 lines
16 KiB
Solidity
// 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);
|
|
}
|
|
}
|
|
}
|
|
} |