becrypto/contracts/market/BENftMall.sol
2023-02-01 16:46:02 +08:00

220 lines
6.8 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/governance/TimelockController.sol";
import "../core/HasSignature.sol";
import "../interfaces/IBEERC721.sol";
import "../interfaces/IBEERC1155.sol";
import "../utils/TimeChecker.sol";
contract BENftMall is Ownable, HasSignature, TimeChecker {
using SafeERC20 for IERC20;
constructor() HasSignature("NftMall", "1") {}
// Supported payment token WETH & list of authorized ERC20
mapping(address => bool) public paymentTokens;
mapping(bytes => bool) public usedSignatures;
mapping(address => bool) public nftTokenSupported;
// Address to receive transaction fee
address public feeToAddress;
// Events
event BuyTransactionBatch(
address indexed buyer,
uint256 indexed nonce,
address[3] addresses,
uint256 price,
uint256[] ids,
uint256[] amounts
);
event BuyTransaction(
address indexed buyer,
uint256 indexed nonce,
uint256 tokenId,
address[3] addresses,
uint256 price
);
function setFeeToAddress(address _feeToAddress) external onlyOwner {
require(
_feeToAddress != address(0),
"fee received address can not be zero"
);
feeToAddress = _feeToAddress;
}
function setPaymentTokens(address[] calldata _paymentTokens)
external
onlyOwner
{
for (uint256 i = 0; i < _paymentTokens.length; i++) {
if (paymentTokens[_paymentTokens[i]]) {
continue;
}
paymentTokens[_paymentTokens[i]] = true;
}
}
function removePaymentTokens(address[] calldata _removedPaymentTokens)
external
onlyOwner
{
for (uint256 i = 0; i < _removedPaymentTokens.length; i++) {
paymentTokens[_removedPaymentTokens[i]] = false;
}
}
function addNFTTokenSupport(address nftToken) external onlyOwner {
nftTokenSupported[nftToken] = true;
}
function removeNFTTokenSupport(address nftToken) external onlyOwner {
nftTokenSupported[nftToken] = false;
}
function ignoreSignature(
address[4] calldata addresses,
uint256[] calldata signArray,
bytes calldata signature
) external signatureValid(signature) {
// address[4] [seller_address,nft_address,payment_token_address, buyer_address]
// uint256[4] [token_id,price,salt_nonce,startTime]
bytes32 criteriaMessageHash = getMessageHash(
addresses[1],
addresses[2],
addresses[3],
signArray
);
checkSigner(_msgSender(), criteriaMessageHash, signature);
_useSignature(signature);
}
/**
* @dev Function matched transaction with user signatures
*/
function buy721NFT(
address[3] calldata addresses,
uint256[4] calldata values,
bytes calldata signature
) external signatureValid(signature) timeValid(values[3]) {
// address[3] [seller_address,nft_address,payment_token_address]
// uint256[4] [token_id,price,salt_nonce,startTime]
// bytes seller_signature
require(nftTokenSupported[addresses[1]], "BENftMall: Unsupported NFT");
require(paymentTokens[addresses[2]], "BENftMall: invalid payment method");
address to = _msgSender();
uint256[] memory signArray = new uint256[](values.length);
for (uint256 i = 0; i < values.length; ++i) {
signArray[i] = values[i];
}
bytes32 criteriaMessageHash = getMessageHash(
addresses[1],
addresses[2],
to,
signArray
);
checkSigner(addresses[0], criteriaMessageHash, signature);
// Check payment approval and buyer balance
IERC20 paymentContract = IERC20(addresses[2]);
require(
paymentContract.balanceOf(to) >= values[1],
"BENftMall: buyer doesn't have enough token to buy this item"
);
require(
paymentContract.allowance(to, address(this)) >= values[1],
"BENftMall: buyer doesn't approve marketplace to spend payment amount"
);
paymentContract.safeTransferFrom(to, feeToAddress, values[1]);
// mint item to user
IBEERC721 nft = IBEERC721(addresses[1]);
nft.mint(to, values[0]);
_useSignature(signature);
// emit sale event
emit BuyTransaction(to, values[2], values[0], addresses, values[1]);
}
function buy1155NFT(
address[3] calldata addresses,
uint256[3] calldata values,
uint256[] memory ids,
uint256[] memory amounts,
bytes calldata signature
) external signatureValid(signature) timeValid(values[2]) {
// address[3] [seller_address,nft_address,payment_token_address]
// uint256[3] [price,salt_nonce,startTime]
require(nftTokenSupported[addresses[1]], "BENftMall: Unsupported NFT");
require(paymentTokens[addresses[2]], "BENftMall: invalid payment method");
require(ids.length > 0, "BENftMall: ids cannot be empty");
require(
ids.length == amounts.length,
"BENftMall: ids and amounts length mismatch"
);
IBEERC1155 nft = IBEERC1155(addresses[1]);
uint256[] memory signArray = new uint256[](ids.length * 2 + values.length);
for (uint256 i = 0; i < ids.length; ++i) {
require(
nft.canMint(ids[i]),
"BENftMall: can not mint for current nft rule setting"
);
signArray[i * 2] = ids[i];
signArray[i * 2 + 1] = amounts[i];
}
for (uint256 i = 0; i < values.length; ++i) {
signArray[ids.length * 2 + i] = values[i];
}
bytes32 criteriaMessageHash = getMessageHash(
addresses[1],
addresses[2],
_msgSender(),
signArray
);
checkSigner(addresses[0], criteriaMessageHash, signature);
// Check payment approval and buyer balance
IERC20 paymentContract = IERC20(addresses[2]);
require(
paymentContract.balanceOf(_msgSender()) >= values[0],
"BENftMall: buyer doesn't have enough token to buy this item"
);
require(
paymentContract.allowance(_msgSender(), address(this)) >= values[0],
"BENftMall: buyer doesn't approve marketplace to spend payment amount"
);
paymentContract.safeTransferFrom(_msgSender(), feeToAddress, values[0]);
nft.mintBatch(_msgSender(), ids, amounts, "");
_useSignature(signature);
emit BuyTransactionBatch(_msgSender(), values[1], addresses, values[0], ids, amounts);
}
function getMessageHash(
address _nftAddress,
address _tokenAddress,
address _buyerAddress,
uint256[] memory _datas
) public pure returns (bytes32) {
bytes memory encoded = abi.encodePacked(
_nftAddress,
_tokenAddress,
_buyerAddress
);
uint256 len = _datas.length;
for (uint256 i = 0; i < len; ++i) {
encoded = bytes.concat(encoded, abi.encodePacked(_datas[i]));
}
return keccak256(encoded);
}
}