194 lines
6.5 KiB
Solidity
194 lines
6.5 KiB
Solidity
// 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);
|
|
}
|
|
} |