127 lines
3.3 KiB
Solidity
127 lines
3.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.10;
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
import "../core/HasSignature.sol";
|
|
import "../utils/TimeChecker.sol";
|
|
import "./MallBase.sol";
|
|
|
|
/**
|
|
* @title GameItemMarket
|
|
* @dev GameItemMarket is a contract for users sell item in game.
|
|
*/
|
|
contract GameItemMarket is
|
|
MallBase,
|
|
ReentrancyGuard,
|
|
HasSignature,
|
|
TimeChecker
|
|
{
|
|
using SafeERC20 for IERC20;
|
|
|
|
mapping(uint256 => address) public orderIdUsed;
|
|
|
|
uint256 constant ROUND = 1000000;
|
|
uint256 public transactionFee = (3 * ROUND) / 100; // 3%
|
|
// min transaction fee is: 0
|
|
uint256 public constant MIN_TRANSACTION_FEE = 0;
|
|
// max transaction fee is: 10%
|
|
uint256 public constant MAX_TRANSACTION_FEE = (10 * ROUND) / 100;
|
|
|
|
event ItemSoldOut(
|
|
address indexed buyer,
|
|
address indexed seller,
|
|
uint256 indexed orderId,
|
|
address currency,
|
|
uint256 price
|
|
);
|
|
|
|
function buy(
|
|
uint256 orderId,
|
|
address seller,
|
|
address currency,
|
|
uint256 price,
|
|
uint256 startTime,
|
|
uint256 saltNonce,
|
|
bytes calldata signature
|
|
) external nonReentrant signatureValid(signature) timeValid(startTime) {
|
|
// check if orderId is used
|
|
require(
|
|
orderIdUsed[orderId] == address(0),
|
|
"GameItemMarket: orderId is used"
|
|
);
|
|
// check if currency is supported
|
|
require(
|
|
erc20Supported[currency],
|
|
"GameItemMarket: currency is not supported"
|
|
);
|
|
// check if price is valid
|
|
require(price > 0, "GameItemMarket: price is zero");
|
|
bytes32 criteriaMessageHash = getMessageHash(
|
|
_msgSender(),
|
|
seller,
|
|
orderId,
|
|
currency,
|
|
price,
|
|
startTime,
|
|
saltNonce
|
|
);
|
|
checkSigner(executor, criteriaMessageHash, signature);
|
|
require(
|
|
IERC20(currency).balanceOf(_msgSender()) >= price,
|
|
"GameItemMarket: buyer doesn't have enough token to buy this item"
|
|
);
|
|
require(
|
|
IERC20(currency).allowance(_msgSender(), address(this)) >= price,
|
|
"GameItemMarket: buyer doesn't approve marketplace to spend payment amount"
|
|
);
|
|
uint256 _transactionFee = (price * transactionFee) / ROUND;
|
|
if (_transactionFee > 0) {
|
|
IERC20(currency).safeTransferFrom(
|
|
_msgSender(),
|
|
feeToAddress,
|
|
_transactionFee
|
|
);
|
|
}
|
|
IERC20(currency).safeTransferFrom(
|
|
_msgSender(),
|
|
seller,
|
|
price - _transactionFee
|
|
);
|
|
orderIdUsed[orderId] = _msgSender();
|
|
_useSignature(signature);
|
|
|
|
emit ItemSoldOut(_msgSender(), seller, orderId, currency, price);
|
|
}
|
|
|
|
function setTransactionFee(uint256 _transactionFee) external onlyOwner {
|
|
require(
|
|
_transactionFee >= MIN_TRANSACTION_FEE &&
|
|
_transactionFee <= MAX_TRANSACTION_FEE,
|
|
"GameItemMarket: _transactionFee must >= 0 and <= 10%"
|
|
);
|
|
transactionFee = _transactionFee;
|
|
}
|
|
|
|
function getMessageHash(
|
|
address _buyer,
|
|
address _seller,
|
|
uint256 _orderId,
|
|
address _currency,
|
|
uint256 _price,
|
|
uint256 _startTime,
|
|
uint256 _saltNonce
|
|
) public pure returns (bytes32) {
|
|
bytes memory encoded = abi.encodePacked(
|
|
_buyer,
|
|
_seller,
|
|
_orderId,
|
|
_currency,
|
|
_price,
|
|
_startTime,
|
|
_saltNonce
|
|
);
|
|
return keccak256(encoded);
|
|
}
|
|
}
|