token-gallery-contracts/contracts/SellableFacet.sol

285 lines
12 KiB
Solidity

// SPDX-License-Identifier: Proprietary
pragma solidity ^0.7.2;
import "./SafeMath.sol";
import "./LibPause.sol";
import "./LibControl.sol";
import "./LibSellable.sol";
import "./LibERC1155.sol";
import "./ISellable.sol";
import "./IPriceChange.sol";
import "./IERC20.sol";
import "./IERC1155TransferEvents.sol";
contract SellableFacet is ISellable, IPriceChange, IERC1155TransferEvents {
using SafeMath for uint256;
modifier sellableWhenNotPaused {
require(!LibPause.paused());
_;
}
modifier nonReentrant {
LibControl.markEntered(LibControl.ENTERED);
_;
LibControl.markEntered(LibControl.NOT_ENTERED);
}
function paymentTokenAddress() override external view returns(address) {
LibSellable.SellableStorage storage sellStore = LibSellable.sellableStorage();
return sellStore.paymentTokenAddress;
}
function ownerEthBalances(address _owner) override external view returns(uint256) {
return LibSellable.sellableStorage().ownerEthBalances[_owner];
}
function ownerTokenBalances(address _owner) override external view returns(uint256) {
return LibSellable.sellableStorage().ownerTokenBalances[_owner];
}
function setRoyalty(uint8 _royalty) override external {
LibControl.enforceIsCreator();
LibSellable.setRoyalty(_royalty);
}
function getRoyalty() override external view returns(uint8) {
return LibSellable.sellableStorage().royalty;
}
function withdraw() sellableWhenNotPaused nonReentrant override external {
address payable recipient = msg.sender == LibControl.manager() ? LibControl.payout() : msg.sender;
LibSellable.SellableStorage storage s = LibSellable.sellableStorage();
uint256 ethBalance = s.ownerEthBalances[recipient];
uint256 tokenBalance = s.ownerTokenBalances[recipient];
require(ethBalance > 0 || tokenBalance > 0);
if (ethBalance > 0) {
s.ownerEthBalances[recipient] = 0;
(bool xferSuccess,) = recipient.call{ value: ethBalance }('');
require(xferSuccess, "Ubiq transfer failed");
emit Payout(recipient, ethBalance);
}
if (tokenBalance > 0) {
IERC20 tenGrans = IERC20(s.paymentTokenAddress);
s.ownerTokenBalances[recipient] = 0;
tenGrans.transfer(recipient, tokenBalance);
emit TokenPayout(recipient, tokenBalance);
}
}
/**
DUPLICATED CODE ALERT! UPDATE BATCH FUNCTION BELOW TOO.
*/
function setNftPrice(uint256 id, uint256 price, bool forSale, uint256 tokenPrice, bool tokenForSale) external override virtual sellableWhenNotPaused nonReentrant {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
LibERC1155.Nft storage nft = store.nftData[id];
require(msg.sender == nft.owner, "Owner only");
require(LibERC1155.isNonFungible(id), "Not NFT");
require(nft.owner != address(0), "NFT Not found");
require(nft.forSale != LibERC1155.NFTLucreState.AtAuction && nft.tokenForSale != LibERC1155.NFTLucreState.AtAuction, "NFT at auction");
require(price == 0 || price >= 100, "price must be 0 or above 100 wei");
require(tokenPrice == 0 || tokenPrice >= 100, "token price must be 0 or above 100 wei");
nft.price = price;
nft.forSale = forSale ? LibERC1155.NFTLucreState.ForSale : LibERC1155.NFTLucreState.NotForSale;
nft.tokenPrice = tokenPrice;
nft.tokenForSale = tokenForSale ? LibERC1155.NFTLucreState.ForSale : LibERC1155.NFTLucreState.NotForSale;
emit PriceChange(id, nft.owner, price, tokenPrice, forSale, tokenForSale);
}
/**
DUPLICATED CODE ALERT! UPDATE BATCH FUNCTION ABOVE TOO.
*/
function setNftPriceBatch(uint256[] calldata ids, uint256[] calldata prices, bool[] calldata forSales, uint256[] calldata tokenPrices, bool[] calldata tokenForSales) external override virtual sellableWhenNotPaused nonReentrant {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
for (uint i = 0; i < ids.length; i++) {
LibERC1155.Nft storage nft = store.nftData[ids[i]];
require(msg.sender == nft.owner, "Owner only");
require(LibERC1155.isNonFungible(ids[i]), "Not NFT");
require(nft.owner != address(0), "NFT Not found");
require(nft.forSale != LibERC1155.NFTLucreState.AtAuction && nft.tokenForSale != LibERC1155.NFTLucreState.AtAuction, "NFT at auction");
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");
nft.price = prices[i];
nft.forSale = forSales[i] ? LibERC1155.NFTLucreState.ForSale : LibERC1155.NFTLucreState.NotForSale;
nft.tokenPrice = tokenPrices[i];
nft.tokenForSale = tokenForSales[i] ? LibERC1155.NFTLucreState.ForSale : LibERC1155.NFTLucreState.NotForSale;
emit PriceChange(ids[i], nft.owner, prices[i], tokenPrices[i], forSales[i], tokenForSales[i]);
}
}
/**
Buy NFT with Eth/Ubiq
*/
function buyNft(uint256 _id) override external sellableWhenNotPaused nonReentrant payable {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
LibSellable.SellableStorage storage sellStore = LibSellable.sellableStorage();
require(LibERC1155.isNonFungible(_id), "Not NFT");
LibERC1155.Nft storage nft = store.nftData[_id];
uint256 paid = msg.value;
require(nft.owner != msg.sender, "Already own");
require(nft.forSale == LibERC1155.NFTLucreState.ForSale, "Not for sale");
require(paid == nft.price, "Wrong amt");
LibERC1155._beforeTokenTransfer(msg.sender, nft.owner, msg.sender, LibERC1155._asSingletonArray(_id), LibERC1155._asSingletonArray(1), "");
address seller = nft.owner;
nft.owner = msg.sender;
emit TransferSingle(msg.sender, seller, msg.sender, _id, 1);
emit BuySingleNft(seller, msg.sender, _id, nft.price);
emit PriceChange(_id, address(0), 0, 0, false, false);
LibERC1155.resetNftPrice(nft);
if (paid == 0) {
// Do no math to save gas.
}
else {
(uint256 fee, uint256 royalty, uint256 profit) = LibSellable.getSplit(paid);
address payable payout = LibControl.payout();
address payable creator = LibControl.creator();
if (sellStore.sendRightAway) {
if (fee > 0) {
(bool sent,) = payout.call{ value: fee }("");
require(sent, "failed to send fee");
emit Payout(payout, fee);
}
if (royalty > 0) {
(bool sent,) = creator.call{ value: royalty }("");
require(sent, "failed to send creator royalty");
emit Payout(creator, royalty);
}
(bool sent,) = seller.call{ value: profit }("");
require(sent, "failed to send creator royalty");
emit Payout(seller, profit);
}
else {
if (fee > 0) sellStore.ownerEthBalances[payout] = SafeMath.add(sellStore.ownerEthBalances[payout], fee);
if (royalty > 0) sellStore.ownerEthBalances[creator] = SafeMath.add(sellStore.ownerEthBalances[creator], royalty);
sellStore.ownerEthBalances[seller] = SafeMath.add(sellStore.ownerEthBalances[seller], profit);
}
}
LibERC1155._doSafeTransferAcceptanceCheck(msg.sender, seller, msg.sender, _id, 1, "");
}
function _buyNftWithToken(address caller, uint256 nft_id) internal {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
LibSellable.SellableStorage storage sellStore = LibSellable.sellableStorage();
LibERC1155.Nft storage nft = store.nftData[nft_id];
require(nft.tokenForSale == LibERC1155.NFTLucreState.ForSale, "Not for sale");
require(LibERC1155.isNonFungible(nft_id), "Not NFT");
require(nft.owner != caller, "Already own");
LibERC1155._beforeTokenTransfer(caller, nft.owner, caller, LibERC1155._asSingletonArray(nft_id), LibERC1155._asSingletonArray(1), "");
uint256 price = nft.tokenPrice;
address seller = nft.owner;
nft.owner = caller;
if (price != 0) {
IERC20 remoteToken = IERC20(sellStore.paymentTokenAddress);
// These are here because 10grans just throws on every requirement with no more info.
require(remoteToken.allowance(caller, address(this)) >= price, "Insufficient allowance");
require(remoteToken.balanceOf(caller) >= price, "Insufficient balance");
(uint256 fee, uint256 royalty, uint256 profit) = LibSellable.getSplit(price);
address payable payout = LibControl.payout();
address payable creator = LibControl.creator();
if (sellStore.sendRightAway) {
if (fee > 0) {
bool success = remoteToken.transferFrom(caller, payout, fee);
require(success, "Failed to transfer fee");
emit TokenPayout(payout, fee);
}
if (royalty > 0) {
bool success = remoteToken.transferFrom(caller, creator, royalty);
require(success, "Failed to transfer royalty to creator");
emit TokenPayout(creator, royalty);
}
bool success = remoteToken.transferFrom(caller, seller, profit);
require(success, "Failed to transfer profit to seller");
emit TokenPayout(seller, profit);
}
else {
bool success = remoteToken.transferFrom(caller, address(this), price);
require(success, "Token xfer fail"); // this will never actually fail with 10grans and most erc20, just throw an error
if (fee > 0) sellStore.ownerTokenBalances[payout] = SafeMath.add(sellStore.ownerTokenBalances[payout], fee);
if (royalty > 0) sellStore.ownerTokenBalances[creator] = SafeMath.add(sellStore.ownerTokenBalances[creator], royalty);
sellStore.ownerTokenBalances[seller] = SafeMath.add(sellStore.ownerTokenBalances[seller], profit);
}
}
LibERC1155.resetNftPrice(nft);
emit TransferSingle(caller, seller, caller, nft_id, 1);
emit TokenBuySingleNft(seller, caller, nft_id, price);
emit PriceChange(nft_id, address(0), 0, 0, false, false);
LibERC1155._doSafeTransferAcceptanceCheck(caller, seller, caller, nft_id, 1, "");
}
/**
This is for tokens that don't have approveAndCall, after they've approved the amount.
*/
function buyNftWithToken(uint256 nft_id) override external sellableWhenNotPaused nonReentrant {
address caller = msg.sender;
_buyNftWithToken(caller, nft_id);
}
/**
approveAndCall for 10grans specifically, because it was made before standard finalized.
*/
function receiveApproval(address caller, uint256 value, address token, bytes calldata data) sellableWhenNotPaused nonReentrant override external {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
LibSellable.SellableStorage storage sellStore = LibSellable.sellableStorage();
require(token == sellStore.paymentTokenAddress, "Wrong token");
uint256 nft_id = LibERC1155.sliceUint(data, 0); // NFT to be bought.
LibERC1155.Nft storage nft = store.nftData[nft_id];
uint256 price = nft.tokenPrice;
require(value == price, "Wrong pay amt");
_buyNftWithToken(caller, nft_id);
}
/**
approveAndCall thru ERC-1363 implementing token, to buy NFT with token amount.
*/
function onApprovalReceived(address caller, uint256 value, bytes calldata data) sellableWhenNotPaused nonReentrant override external {
LibERC1155.ERC1155Store storage store = LibERC1155.getERC1155Store();
LibSellable.SellableStorage storage sellStore = LibSellable.sellableStorage();
require(msg.sender == sellStore.paymentTokenAddress, "Wrong token");
uint256 nft_id = LibERC1155.sliceUint(data, 0); // NFT to be bought.
LibERC1155.Nft storage nft = store.nftData[nft_id];
uint256 price = nft.tokenPrice;
require(value == price, "Wrong pay amt");
_buyNftWithToken(caller, nft_id);
}
}