From c08707047d8eed12e6045e1e09e53b0965434b2e Mon Sep 17 00:00:00 2001 From: zhl Date: Fri, 7 Jan 2022 15:04:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96contracts=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/BEChip.sol | 33 ++++ contracts/BECoin.sol | 12 ++ contracts/BEHero.sol | 152 ++++++++++++++++ contracts/CryptoHero.sol | 302 -------------------------------- contracts/HasSignature.sol | 63 +++++++ contracts/IMarketplace.sol | 83 --------- contracts/MarketPlace.sol | 333 +++++++++++++++++++----------------- contracts/MinterFactory.sol | 41 +++++ migrations/2_deploy_hero.js | 6 +- 9 files changed, 479 insertions(+), 546 deletions(-) create mode 100644 contracts/BEChip.sol create mode 100644 contracts/BECoin.sol create mode 100644 contracts/BEHero.sol delete mode 100644 contracts/CryptoHero.sol create mode 100644 contracts/HasSignature.sol delete mode 100644 contracts/IMarketplace.sol create mode 100644 contracts/MinterFactory.sol diff --git a/contracts/BEChip.sol b/contracts/BEChip.sol new file mode 100644 index 0000000..da609b9 --- /dev/null +++ b/contracts/BEChip.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; + +contract BEChip is ERC721, AccessControlEnumerable, ERC721Enumerable, Ownable { + /** + * @dev See {IERC165-_beforeTokenTransfer}. + * TODO:: + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual override(ERC721, ERC721Enumerable) { + super._beforeTokenTransfer(from, to, tokenId); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(AccessControlEnumerable, ERC721, ERC721Enumerable) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/BECoin.sol b/contracts/BECoin.sol new file mode 100644 index 0000000..cf6f878 --- /dev/null +++ b/contracts/BECoin.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; + +contract BECoin is ERC20PresetMinterPauser, Ownable { + uint256 public initializedCap = 20000000 * 1e18; + + constructor() ERC20PresetMinterPauser("Blissful Elites Coin", "BEC") { + _mint(_msgSender(), initializedCap); + } +} \ No newline at end of file diff --git a/contracts/BEHero.sol b/contracts/BEHero.sol new file mode 100644 index 0000000..6020000 --- /dev/null +++ b/contracts/BEHero.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; + +contract BEHero is ERC721, AccessControlEnumerable, ERC721Enumerable, Ownable { + mapping(address => bool) public approvalWhitelists; + mapping(uint256 => bool) public lockedTokens; + string private _baseTokenURI; + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor() ERC721("Blissful Elites Hero", "BEH") {} + + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + + /** + * @dev Creates a new token for `to`. Its token ID will be automatically + * assigned (and available on the emitted {IERC721-Transfer} event), and the token + * URI autogenerated based on the base URI passed at construction. + * + * See {ERC721-_mint}. + * + * Requirements: + * + * - the caller must have the `MINTER_ROLE`. + */ + function mint(address to, uint256 tokenId) public virtual { + require( + hasRole(MINTER_ROLE, _msgSender()), + "Must have minter role to mint" + ); + require(!_exists(tokenId), "Must have unique tokenId"); + // We cannot just use balanceOf to create the new tokenId because tokens + // can be burned (destroyed), so we need a separate counter. + _mint(to, tokenId); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) + public + view + override + returns (bool) + { + if (approvalWhitelists[operator] == true) { + return true; + } + + return super.isApprovedForAll(owner, operator); + } + + /** + * @dev Allow operation to reduce gas fee. + */ + function addApprovalWhitelist(address proxy) public onlyOwner { + require(approvalWhitelists[proxy] == false, "Invalid proxy address"); + + approvalWhitelists[proxy] = true; + } + + /** + * @dev Remove operation from approval list. + */ + function removeApprovalWhitelist(address proxy) public onlyOwner { + approvalWhitelists[proxy] = false; + } + + /** + * @dev Add factory to mint item + */ + function setMintFactory(address factory) public onlyOwner { + _setupRole(MINTER_ROLE, factory); + } + + /** + * @dev Remove factory + */ + function removeMintFactory(address factory) public onlyOwner { + revokeRole(MINTER_ROLE, factory); + } + + /** + * @dev Lock token to use in game or for rental + */ + function lock(uint256 tokenId) public { + require( + approvalWhitelists[_msgSender()], + "Must be valid approval whitelist" + ); + require(_exists(tokenId), "Must be valid tokenId"); + require(!lockedTokens[tokenId], "Token has already locked"); + lockedTokens[tokenId] = true; + } + + /** + * @dev Unlock token to use blockchain or sale on marketplace + */ + function unlock(uint256 tokenId) public { + require( + approvalWhitelists[_msgSender()], + "Must be valid approval whitelist" + ); + require(_exists(tokenId), "Must be valid tokenId"); + require(lockedTokens[tokenId], "Token has already unlocked"); + lockedTokens[tokenId] = false; + } + + /** + * @dev Get lock status + */ + function isLocked(uint256 tokenId) public view returns (bool) { + return lockedTokens[tokenId]; + } + + /** + * @dev Set token URI + */ + function updateBaseURI(string calldata baseTokenURI) public onlyOwner { + _baseTokenURI = baseTokenURI; + } + + /** + * @dev See {IERC165-_beforeTokenTransfer}. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual override(ERC721, ERC721Enumerable) { + require(!lockedTokens[tokenId], "Can not transfer locked token"); + super._beforeTokenTransfer(from, to, tokenId); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(AccessControlEnumerable, ERC721, ERC721Enumerable) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/CryptoHero.sol b/contracts/CryptoHero.sol deleted file mode 100644 index b53fa5b..0000000 --- a/contracts/CryptoHero.sol +++ /dev/null @@ -1,302 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -contract CryptoHero is Ownable, IERC721 { - using SafeMath for uint256; - - uint256 public constant maxGen0Hero = 10; - - uint256 public gen0Counter = 0; - - uint256 initHeroPrice = 0.01 ether; - - bytes4 internal constant _ERC721Checksum = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); - - bytes4 private constant _InterfaceIdERC721 = 0x80ac58cd; - bytes4 private constant _InterfaceIdERC165 = 0x01ffc9a7; - - string private _name; - string private _symbol; - - struct Hero { - uint256 genes; - uint64 birthTime; - uint32 mumId; - uint32 dadId; - uint16 generation; - } - Hero[] heros; - // map of hero and owner - mapping(uint256 => address) public heroOwner; - // hero count of owner - mapping(address => uint256) ownsNumberOfTokens; - // hero in trading - mapping(uint256 => address) public approvalOneHero; - - mapping(address => mapping (address => bool)) private _operatorApprovals; - - event HeroBirth(address owner, uint256 _heroId, uint256 mumId, uint256 dadId, uint256 genes); - event HeroEvolved(uint256 indexed _heroId, uint256 _oldGenes, uint256 _newGenes); - event HeroRetired(uint256 indexed _heroId); - - constructor(string memory dname, string memory dsymbol) { - _name = dname; - _symbol = dsymbol; - _createHero(0, 0, 0, uint256(0), address(0)); - } - - function getContractOwner() external view returns (address contractowner) { - return owner(); - } - - function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { - return (_interfaceId == _InterfaceIdERC721 || _interfaceId == _InterfaceIdERC165); - } - - /** - * for player buy init heros - */ - function buyInitHero(uint256 genes) public payable returns (uint256) { - require(gen0Counter < maxGen0Hero, "Maximum number of Heros is reached. No new hero allowed!"); - require(msg.value >= initHeroPrice, "not enough money!"); - gen0Counter = SafeMath.add(gen0Counter, 1); - return _createHero(0, 0, 0, genes, msg.sender); - } - - function setInitHeroPrice(uint _price) external onlyOwner { - initHeroPrice = _price; - } - - function _createHero( - uint256 _mumId, - uint256 _dadId, - uint256 _generation, - uint256 _genes, - address _owner - ) internal returns (uint256) { - Hero memory _hero = Hero({ - genes: _genes, - birthTime: uint64(block.timestamp), - mumId: uint32(_mumId), //easier to input 256 and later convert to 32. - dadId: uint32(_dadId), - generation: uint16(_generation) - }); - heros.push(_hero); - uint256 newHeroId = SafeMath.sub(heros.length, 1);//want to start with zero. - _transfer(address(0), _owner, newHeroId);//transfer from nowhere. Creation event. - emit HeroBirth(_owner, newHeroId, _mumId, _dadId, _genes); - return newHeroId; - } - - function retireHero(uint256 heroId) internal { - delete heros[heroId]; - emit HeroRetired(heroId); - } - - function evolveHero(uint256 _heroId, uint256 _newGenes) internal { - uint256 _oldGenes = heros[_heroId].genes; - heros[_heroId].genes = _newGenes; - emit HeroEvolved(_heroId, _oldGenes, _newGenes); - } - - function getHero(uint256 tokenId) public view returns ( - uint256 genes, - uint256 birthTime, - uint256 mumId, - uint256 dadId, - uint256 generation) //code looks cleaner when the params appear here vs. in the return statement. - { - require(tokenId < heros.length, "Token ID doesn't exist."); - Hero storage hero = heros[tokenId];//saves space over using memory, which would make a copy - - genes = hero.genes; - birthTime = uint256(hero.birthTime); - mumId = uint256(hero.mumId); - dadId = uint256(hero.dadId); - generation = uint256(hero.generation); - } - - function getAllHerosOfOwner(address owner) external view returns(uint256[] memory) { - uint256[] memory allHerosOfOwner = new uint[](ownsNumberOfTokens[owner]); - uint256 j = 0; - for (uint256 i = 0; i < heros.length; i++) { - if (heroOwner[i] == owner) { - allHerosOfOwner[j] = i; - j = SafeMath.add(j, 1); - } - } - return allHerosOfOwner; - } - - function balanceOf(address owner) external view returns (uint256 balance) { - return ownsNumberOfTokens[owner]; - } - - function totalSupply() external view returns (uint256 total) { - return heros.length; - } - - function name() external view returns (string memory tokenName){ - return _name; - } - - function symbol() external view returns (string memory tokenSymbol){ - return _symbol; - } - - function ownerOf(uint256 tokenId) external view returns (address owner) { - require(tokenId < heros.length, "Token ID doesn't exist."); - return heroOwner[tokenId]; - } - - function transfer(address to, uint256 tokenId) external { - require(to != address(0), "Use the burn function to burn tokens!"); - require(to != address(this), "Wrong address, try again!"); - require(heroOwner[tokenId] == msg.sender); - _transfer(msg.sender, to, tokenId); - } - - function _transfer(address _from, address _to, uint256 _tokenId) internal { - require(_to != address(this)); - ownsNumberOfTokens[_to] = SafeMath.add(ownsNumberOfTokens[_to], 1); - heroOwner[_tokenId] = _to; - - if (_from != address(0)) { - ownsNumberOfTokens[_from] = SafeMath.sub(ownsNumberOfTokens[_from], 1); - delete approvalOneHero[_tokenId];//when owner changes, approval must be removed. - } - - emit Transfer(_from, _to, _tokenId); - } - - function approve(address _approved, uint256 _tokenId) external { - require(heroOwner[_tokenId] == msg.sender || _operatorApprovals[heroOwner[_tokenId]][msg.sender] == true, - "You are not authorized to access this function."); - approvalOneHero[_tokenId] = _approved; - emit Approval(msg.sender, _approved, _tokenId); - } - - function setApprovalForAll(address _operator, bool _approved) external { - require(_operator != msg.sender); - _operatorApprovals[msg.sender][_operator] = _approved; - emit ApprovalForAll(msg.sender, _operator, _approved); - } - - function getApproved(uint256 _tokenId) external view returns (address) { - require(_tokenId < heros.length, "Token doesn't exist"); - return approvalOneHero[_tokenId]; - } - - function isApprovedForAll(address _owner, address _operator) external view returns (bool) { - return _operatorApprovals[_owner][_operator]; - } - - - function _safeTransfer(address _from, address _to, uint256 _tokenId, bytes memory _data) internal { - require(_checkERC721Support(_from, _to, _tokenId, _data)); - _transfer(_from, _to, _tokenId); - } - - function _checkERC721Support(address _from, address _to, uint256 _tokenId, bytes memory _data) - internal returns(bool) { - if(!_isContract(_to)) { - return true; - } - bytes4 returnData = IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data); - //Call onERC721Received in the _to contract - return returnData == _ERC721Checksum; - //Check return value - } - - function _isContract(address _to) internal view returns (bool) { - uint32 size; - assembly{ - size := extcodesize(_to) - } - return size > 0; - //check if code size > 0; wallets have 0 size. - } - - function _isOwnerOrApproved(address _from, address _to, uint256 _tokenId) internal view returns (bool) { - require(_from == msg.sender || - approvalOneHero[_tokenId] == msg.sender || - _operatorApprovals[_from][msg.sender], - "You are not authorized to use this function"); - require(heroOwner[_tokenId] == _from, "Owner incorrect"); - require(_to != address(0), "Error: Operation would delete this token permanently"); - require(_tokenId < heros.length, "Token doesn't exist"); - return true; - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external { - _isOwnerOrApproved(_from, _to, _tokenId); - _safeTransfer(_from, _to, _tokenId, data); - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external { - _isOwnerOrApproved(_from, _to, _tokenId); - _safeTransfer(_from, _to, _tokenId, ""); - } - - function transferFrom(address _from, address _to, uint256 _tokenId) external { - _isOwnerOrApproved(_from, _to, _tokenId); - _transfer(_from, _to, _tokenId); - } - - function _mixDna( - uint256 _dadDna, - uint256 _mumDna, - uint8 random, - uint8 randomSeventeenthDigit, - uint8 randomPair, - uint8 randomNumberForRandomPair - ) internal pure returns (uint256){ - - uint256[9] memory geneArray; - uint256 i; - uint256 counter = 7; // start on the right end - - //DNA example: 11 22 33 44 55 66 77 88 9 - - if(randomSeventeenthDigit == 0){ - geneArray[8] = uint8(_mumDna % 10); //this takes the 17th gene from mum. - } else { - geneArray[8] = uint8(_dadDna % 10); //this takes the 17th gene from dad. - } - - _mumDna = SafeMath.div(_mumDna, 10); // division by 10 removes the last digit - _dadDna = SafeMath.div(_dadDna, 10); // division by 10 removes the last digit - - for (i = 1; i <= 128; i=i*2) { //1, 2 , 4, 8, 16, 32, 64 ,128 - if(random & i == 0){ //00000001 - geneArray[counter] = uint8(_mumDna % 100); //00000010 etc. - } else { //11001011 & - geneArray[counter] = uint8(_dadDna % 100); //00000001 will go through random number bitwise - } //if(1) - dad gene - _mumDna = SafeMath.div(_mumDna, 100); //if(0) - mum gene - _dadDna = SafeMath.div(_dadDna, 100); //division by 100 removes last two digits from genes - if(counter > 0) { - counter = SafeMath.sub(counter, 1); - } - } - - geneArray[randomPair] = randomNumberForRandomPair; //extra randomness for random pair. - - uint256 newGene = 0; - - //geneArray example: [11, 22, 33, 44, 55, 66, 77, 88, 9] - - for (i = 0; i < 8; i++) { //8 is number of pairs in array - newGene = SafeMath.mul(newGene, 100); //adds two digits to newGene; no digits the first time - newGene = SafeMath.add(newGene, geneArray[i]); //adds a pair of genes - } - newGene = SafeMath.mul(newGene, 10); //add seventeenth digit - newGene = SafeMath.add(newGene, geneArray[8]); - return newGene; - } -} diff --git a/contracts/HasSignature.sol b/contracts/HasSignature.sol new file mode 100644 index 0000000..018b236 --- /dev/null +++ b/contracts/HasSignature.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract HasSignature { + function getEthSignedMessageHash(bytes32 _messageHash) + public + pure + returns (bytes32) + { + /* + Signature is produced by signing a keccak256 hash with the following format: + "\x19Ethereum Signed Message\n" + len(msg) + msg + */ + return + keccak256( + abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + _messageHash + ) + ); + } + + function recoverSigner( + bytes32 _ethSignedMessageHash, + bytes memory _signature + ) public pure returns (address) { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); + + return ecrecover(_ethSignedMessageHash, v, r, s); + } + + function splitSignature(bytes memory sig) + public + pure + returns ( + bytes32 r, + bytes32 s, + uint8 v + ) + { + require(sig.length == 65, "invalid signature length"); + + assembly { + /* + First 32 bytes stores the length of the signature + + add(sig, 32) = pointer of sig + 32 + effectively, skips first 32 bytes of signature + + mload(p) loads next 32 bytes starting at the memory address p into memory + */ + + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + + // implicitly return (r, s, v) + } +} \ No newline at end of file diff --git a/contracts/IMarketplace.sol b/contracts/IMarketplace.sol deleted file mode 100644 index 45ed5be..0000000 --- a/contracts/IMarketplace.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./CryptoHero.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/* - * Market place to trade heros (should **in theory** be used for any ERC721 token) - * It needs an existing hero contract to interact with - * Note: it does not inherit from the contract - * Note: It takes ownership of the hero for the duration that is is on the marketplace. - */ -interface IMarketPlace { - - event MarketTransaction(string TxType, address owner, uint256 tokenId); - event MonetaryTransaction(string message, address recipient, uint256 amount); - - /** - * Set the current contract address and initialize the instance of the contract. - * Requirement: Only the contract owner can call. - */ - function setContract(address _contractAddress) external; - - /** - * Sets status of _paused to true which affects all functions that have whenNotPaused modifiers. - */ - function pause() external; - - /** - * Sets status of _paused to false which affects all functions that have whenNotPaused modifiers. - */ - function resume() external; - - /** - * Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId. - */ - function getOffer(uint256 _tokenId) external view returns (address seller, uint256 price, uint256 index, uint256 tokenId, bool active); - - /** - * Get all tokenId's that are currently for sale. Returns an empty array if no offer exists. - */ - function getAllTokensOnSale() external view returns (uint256[] memory listOfOffers); - - /** - * Creates a new offer for _tokenId for the price _price. - * Emits the MarketTransaction event with txType "Create offer" - * Requirement: Only the owner of _tokenId can create an offer. - * Requirement: There can only be one active offer for a token at a time. - * Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created. - */ - function setOffer(uint256 _price, uint256 _tokenId) external; - - /** - * Removes an existing offer. - * Emits the MarketTransaction event with txType "Remove offer" - * Requirement: Only the seller of _tokenId can remove an offer. - */ - function removeOffer(uint256 _tokenId) external; - - /** - * Executes the purchase of _tokenId. - * Transfers the token using transferFrom in CryptoHero. - * Transfers funds to the _fundsToBeCollected mapping. - * Removes the offer from the mapping. - * Sets the offer in the array to inactive. - * Emits the MarketTransaction event with txType "Buy". - * Requirement: The msg.value needs to equal the price of _tokenId - * Requirement: There must be an active offer for _tokenId - */ - function buyHero(uint256 _tokenId) external payable; - - /** - * Returns current balance of msg.sender - */ - function getBalance() external view returns (uint256); - - /** - * Send funds to msg.sender. - * Emits a MonetaryTransaction event "Successful Transfer". - * Requirement: msg.sender must have funds in the mapping. - */ - function withdrawFunds() external payable; -} \ No newline at end of file diff --git a/contracts/MarketPlace.sol b/contracts/MarketPlace.sol index 44e8e70..d8fc16c 100644 --- a/contracts/MarketPlace.sol +++ b/contracts/MarketPlace.sol @@ -1,194 +1,211 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "./CryptoHero.sol"; -import "./IMarketplace.sol"; - -/* - * Market place to trade heros (should **in theory** be used for any ERC721 token) - * It needs an existing hero contract to interact with - * Note: it does not inherit from the contract - * Note: It takes ownership of the hero for the duration that is is on the marketplace. - */ - -contract MarketPlace is Ownable, IMarketPlace { - CryptoHero private _cryptoHero; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./HasSignature.sol"; +contract Marketplace is Ownable, HasSignature { + using SafeERC20 for IERC20; using SafeMath for uint256; - struct Offer { - address payable seller; - uint256 price; - uint256 index; - uint256 tokenId; - bool active; + // Supported payment token WETH & list of authorized ERC20 + mapping(address => bool) public paymentTokens; + mapping(bytes => bool) public usedSignatures; + + // Address to receive transaction fee + address public feeToAddress; + uint256 public transactionFee; + + // Events + event MatchTransaction( + uint256 indexed tokenId, + address contractAddress, + uint256 price, + address paymentToken, + address seller, + address buyer, + uint256 fee + ); + + function setFeeToAddress(address _feeToAddress) public onlyOwner { + feeToAddress = _feeToAddress; } - bool internal _paused; - - Offer[] offers; - - mapping(uint256 => Offer) tokenIdToOffer; - mapping(address => uint256) internal _fundsToBeCollected; - - //Contract has one event that is already declared in the interface. - - //Contract can be paused by owner to ensure bugs can be fixed after deployment - modifier whenNotPaused() { - require(!_paused); - _; + function setTransactionFee(uint256 _transactionFee) public onlyOwner { + transactionFee = _transactionFee; } - modifier whenPaused() { - require(_paused); - _; - } - - function setContract(address _contractAddress) onlyOwner public { - _cryptoHero = CryptoHero(_contractAddress); - } - - constructor(address _contractAddress) { - setContract(_contractAddress); - _paused = false; - } - - function pause() public onlyOwner whenNotPaused { - _paused = true; - } - - function resume() public onlyOwner whenPaused { - _paused = false; - } - - function isPaused() public view returns (bool) { - return _paused; - } - - function getOffer(uint256 _tokenId) public view returns ( - address seller, - uint256 price, - uint256 index, - uint256 tokenId, - bool active) { - - require(tokenIdToOffer[_tokenId].active == true, "No active offer at this time"); - - return (tokenIdToOffer[_tokenId].seller, - tokenIdToOffer[_tokenId].price, - tokenIdToOffer[_tokenId].index, - tokenIdToOffer[_tokenId].tokenId, - tokenIdToOffer[_tokenId].active); - } - - function getAllTokensOnSale() public view returns (uint256[] memory listOfOffers) { - uint256 resultId = 0;//index for all heros with active offer status (true) - - for (uint256 index = 0; index < offers.length; index++) { - if (offers[index].active == true) { - resultId = SafeMath.add(resultId, 1);//determine length of array to return + function setPaymentTokens(address[] calldata _paymentTokens) + public + onlyOwner + { + for (uint256 i = 0; i < _paymentTokens.length; i++) { + if (paymentTokens[_paymentTokens[i]] == true) { + continue; } - } - - if (offers.length == 0) { - return new uint256[](0);//returns empty array - } else { - uint256[] memory allTokensOnSale = new uint256[](resultId); - //initialize new array with correct length - resultId = 0;//reset index of new array - for (uint256 index = 0; index < offers.length; index++) {//iterate through entire offers array - if (offers[index].active == true) { - allTokensOnSale[resultId] = offers[index].tokenId; - resultId = SafeMath.add(resultId, 1); - } - } - return allTokensOnSale; + + paymentTokens[_paymentTokens[i]] = true; } } - function _ownsHero(address _address, uint256 _tokenId) internal view returns (bool) { - return (_cryptoHero.ownerOf(_tokenId) == _address); + function removePaymentTokens(address[] calldata _removedPaymentTokens) + public + onlyOwner + { + for (uint256 i = 0; i < _removedPaymentTokens.length; i++) { + paymentTokens[_removedPaymentTokens[i]] = false; + } } - function setOffer(uint256 _price, uint256 _tokenId) public { - require(_ownsHero(msg.sender, _tokenId), - "Only the owner of the hero can initialize an offer"); - require(tokenIdToOffer[_tokenId].active == false, - "You already created an offer for this hero. Please remove it first before creating a new one."); - require(_cryptoHero.isApprovedForAll(msg.sender, address(this)), - "MarketPlace contract must first be an approved operator for your heros"); + function ignoreSignature( + address[2] calldata addresses, + uint256[3] calldata values, + bytes calldata signature + ) external { + require( + !usedSignatures[signature], + "Marketplace: this signature has added to ignore list already" + ); + bytes32 criteriaMessageHash = getMessageHash( + addresses[0], + values[0], + addresses[1], + values[1], + values[2] + ); - Offer memory _currentOffer = Offer({//set offer - seller: payable(msg.sender), - price: _price, - index: offers.length, - tokenId: _tokenId, - active: true - }); + bytes32 ethSignedMessageHash = getEthSignedMessageHash( + criteriaMessageHash + ); - tokenIdToOffer[_tokenId] = _currentOffer;//update mapping - offers.push(_currentOffer);//update array + require( + recoverSigner(ethSignedMessageHash, signature) == _msgSender(), + "Marketplace: invalid signature" + ); - emit MarketTransaction("Offer created", msg.sender, _tokenId); + usedSignatures[signature] = true; } - function removeOffer(uint256 _tokenId) public { - require(tokenIdToOffer[_tokenId].seller == msg.sender, - "Only the owner of the hero can withdraw the offer."); + /** + * @dev Function matched transaction with user signatures + */ + function matchTransaction( + address[3] calldata addresses, + uint256[3] calldata values, + bytes calldata signature + ) external returns (bool) { + // address[3] [seller_address,nft_address,payment_token_address] + // uint256[3] [token_id,price,salt_nonce] + // bytes seller_signature + require( + paymentTokens[addresses[2]] == true, + "Marketplace: invalid payment method" + ); - offers[tokenIdToOffer[_tokenId].index].active = false; - //don't iterate through array, simply set active to false. - delete tokenIdToOffer[_tokenId];//delete entry in mapping + require( + !usedSignatures[signature], + "Marketplace: signature used. please send another transaction with new signature" + ); - emit MarketTransaction("Offer removed", msg.sender, _tokenId); - } + bytes32 criteriaMessageHash = getMessageHash( + addresses[1], + values[0], + addresses[2], + values[1], + values[2] + ); - function buyHero(uint256 _tokenId) public payable whenNotPaused{ - Offer memory _currentOffer = tokenIdToOffer[_tokenId]; + bytes32 ethSignedMessageHash = getEthSignedMessageHash( + criteriaMessageHash + ); - //checks - require(_currentOffer.active, "There is no active offer for this hero"); - require(msg.value == _currentOffer.price, "The amount offered is not equal to the requested amount"); + require( + recoverSigner(ethSignedMessageHash, signature) == addresses[0], + "Marketplace: invalid seller signature" + ); - //effects - delete tokenIdToOffer[_tokenId];//delete entry in mapping - offers[_currentOffer.index].active = false;//don't iterate through array, but simply set active to false. + // check current ownership + IERC721 nft = IERC721(addresses[1]); + require( + nft.ownerOf(values[0]) == addresses[0], + "Marketplace: seller is not owner of this item now" + ); - //interactions - if (_currentOffer.price > 0) { - _fundsToBeCollected[_currentOffer.seller] = - SafeMath.add(_fundsToBeCollected[_currentOffer.seller], _currentOffer.price); - //instead of sending money to seller it is deposited in a mapping waiting for seller to pull. + // Check payment approval and buyer balance + IERC20 paymentContract = IERC20(addresses[2]); + require( + paymentContract.balanceOf(_msgSender()) >= values[1], + "Marketplace: buyer doesn't have enough token to buy this item" + ); + require( + paymentContract.allowance(_msgSender(), address(this)) >= values[1], + "Marketplace: buyer doesn't approve marketplace to spend payment amount" + ); + + // We divide by 10000 to support decimal value such as 4.25% => 425 / 10000 + uint256 fee = transactionFee.mul(values[1]).div(10000); + uint256 payToSellerAmount = values[1].sub(fee); + + // transfer money to seller + paymentContract.safeTransferFrom( + _msgSender(), + addresses[0], + payToSellerAmount + ); + + // transfer fee to address + if (fee > 0) { + paymentContract.safeTransferFrom(_msgSender(), feeToAddress, fee); } - _cryptoHero.transferFrom(_currentOffer.seller, msg.sender, _tokenId);//ERC721 ownership transferred + // transfer item to buyer + nft.safeTransferFrom(addresses[0], _msgSender(), values[0]); - emit MarketTransaction("Hero successfully purchased", msg.sender, _tokenId); + usedSignatures[signature] = true; + // emit sale event + emitEvent(addresses, values); + return true; } - function getBalance() public view returns (uint256) { - return _fundsToBeCollected[msg.sender]; + /** + * @dev Function to emit transaction matched event + */ + function emitEvent( + address[3] calldata addresses, + uint256[3] calldata values + ) internal { + emit MatchTransaction( + values[0], + addresses[1], + values[1], + addresses[2], + addresses[0], + _msgSender(), + transactionFee + ); } - function withdrawFunds() public payable whenNotPaused{ - - //check - require(_fundsToBeCollected[msg.sender] > 0, "No funds available at this time"); - - uint256 toWithdraw = _fundsToBeCollected[msg.sender]; - - //effect - _fundsToBeCollected[msg.sender] = 0; - - //interaction - payable(msg.sender).transfer(toWithdraw); - - //making sure transfer executed correctly - assert(_fundsToBeCollected[msg.sender] == 0); - - //emit event - emit MonetaryTransaction("Funds successfully received", msg.sender, toWithdraw); + function getMessageHash( + address _nftAddress, + uint256 _tokenId, + address _paymentErc20, + uint256 _price, + uint256 _saltNonce + ) public pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + _nftAddress, + _tokenId, + _paymentErc20, + _price, + _saltNonce + ) + ); } -} \ No newline at end of file +} diff --git a/contracts/MinterFactory.sol b/contracts/MinterFactory.sol new file mode 100644 index 0000000..9f06db5 --- /dev/null +++ b/contracts/MinterFactory.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +interface IMintableERC721 is IERC721 { + function mint(address to, uint256 tokenId) external; +} + +contract MinterFactory is Ownable, Initializable { + // NFT contract + IMintableERC721 public erc721; + bool public publicMintAllowed; + + event TokenMinted( + address contractAddress, + address to, + uint256 indexed tokenId + ); + + function init(address _erc721) external initializer onlyOwner { + erc721 = IMintableERC721(_erc721); + } + + /** + * @dev mint function to distribute Blissful Elites NFT to user + */ + function mintTo(address to, uint256 tokenId) external { + require(publicMintAllowed || _msgSender() == owner()); + erc721.mint(to, tokenId); + emit TokenMinted(address(erc721), to, tokenId); + } + + /** + * @dev function to allow user mint items + */ + function allowPublicMint() public onlyOwner { + publicMintAllowed = true; + } +} diff --git a/migrations/2_deploy_hero.js b/migrations/2_deploy_hero.js index 65be87b..847ea74 100644 --- a/migrations/2_deploy_hero.js +++ b/migrations/2_deploy_hero.js @@ -1,9 +1,9 @@ -const Hero = artifacts.require('CryptoHero'); +const Hero = artifacts.require('BEHero'); module.exports = async function (deployer) { - await deployer.deploy(Hero, "CryptoHero", "JC"); + await deployer.deploy(Hero, "BEHero", "JC"); const instance = await Hero.deployed(); if(instance) { - console.log("CryptoHero successfully deployed.") + console.log("BEHero successfully deployed.") } }; \ No newline at end of file