add contract of hero and marketplace

This commit is contained in:
zhl 2021-12-27 15:03:18 +08:00
parent 2ec37cbc0c
commit 6b77ef79d7
6 changed files with 585 additions and 0 deletions

279
contracts/CryptoHero.sol Normal file
View File

@ -0,0 +1,279 @@
// 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;
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 Birth(address owner, uint256 heroId, uint256 mumId, uint256 dadId, uint256 genes);
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);
}
function createHeroGen0(uint256 genes) public onlyOwner returns (uint256) {
require(gen0Counter < maxGen0Hero, "Maximum number of Heros is reached. No new hero allowed!");
gen0Counter = SafeMath.add(gen0Counter, 1);
return _createHero(0, 0, 0, genes, msg.sender);
}
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 Birth(_owner, newHeroId, _mumId, _dadId, _genes);
return newHeroId;
}
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;
}
}

View File

@ -0,0 +1,83 @@
// 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;
}

194
contracts/MarketPlace.sol Normal file
View File

@ -0,0 +1,194 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.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;
using SafeMath for uint256;
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
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);
_;
}
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
}
}
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;
}
}
function _ownsHero(address _address, uint256 _tokenId) internal view returns (bool) {
return (_cryptoHero.ownerOf(_tokenId) == _address);
}
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");
Offer memory _currentOffer = Offer({//set offer
seller: payable(msg.sender),
price: _price,
index: offers.length,
tokenId: _tokenId,
active: true
});
tokenIdToOffer[_tokenId] = _currentOffer;//update mapping
offers.push(_currentOffer);//update array
emit MarketTransaction("Offer created", msg.sender, _tokenId);
}
function removeOffer(uint256 _tokenId) public {
require(tokenIdToOffer[_tokenId].seller == msg.sender,
"Only the owner of the hero can withdraw the offer.");
offers[tokenIdToOffer[_tokenId].index].active = false;
//don't iterate through array, simply set active to false.
delete tokenIdToOffer[_tokenId];//delete entry in mapping
emit MarketTransaction("Offer removed", msg.sender, _tokenId);
}
function buyHero(uint256 _tokenId) public payable whenNotPaused{
Offer memory _currentOffer = tokenIdToOffer[_tokenId];
//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");
//effects
delete tokenIdToOffer[_tokenId];//delete entry in mapping
offers[_currentOffer.index].active = false;//don't iterate through array, but simply set active to false.
//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.
}
_cryptoHero.transferFrom(_currentOffer.seller, msg.sender, _tokenId);//ERC721 ownership transferred
emit MarketTransaction("Hero successfully purchased", msg.sender, _tokenId);
}
function getBalance() public view returns (uint256) {
return _fundsToBeCollected[msg.sender];
}
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);
}
}

View File

@ -0,0 +1,9 @@
const Hero = artifacts.require('CryptoHero');
module.exports = async function (deployer) {
await deployer.deploy(Hero, "CryptoHero", "JC");
const instance = await Hero.deployed();
if(instance) {
console.log("CryptoHero successfully deployed.")
}
};

View File

@ -0,0 +1,10 @@
const Hero = artifacts.require('CryptoHero');
const MarketPlace = artifacts.require('MarketPlace');
module.exports = async function (deployer) {
await deployer.deploy(MarketPlace, Hero.address);
const instance = await MarketPlace.deployed();
if(instance) {
console.log("MarketPlace successfully deployed.")
}
};

View File

@ -71,6 +71,16 @@ module.exports = {
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
development: {
host: "192.168.100.22", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
local: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
}
},
// Set default mocha options here, use special reporters etc.