285 lines
12 KiB
Solidity
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);
|
|
}
|
|
}
|