diff --git a/contracts/KittyCore.sol b/contracts/KittyCore.sol index df999eb..f5a6c9b 100644 --- a/contracts/KittyCore.sol +++ b/contracts/KittyCore.sol @@ -1,9 +1,9 @@ import "./utils/Ownable.sol"; -import "./KittyMarketPlace.sol"; +import "./KittyOwnership.sol"; pragma solidity ^0.5.0; -contract KittyCore is Ownable, KittyMarketPlace { +contract KittyCore is Ownable, KittyOwnership { uint256 public constant CREATION_LIMIT_GEN0 = 10; @@ -11,8 +11,8 @@ contract KittyCore is Ownable, KittyMarketPlace { uint256 public gen0Counter; constructor() public { - // We are creating the first kitty at index 0 - _createKitty(0, 0, 0, uint256(-1), address(0)); + // We are creating the first kitty at index 0 + _createKitty(0, 0, 0, uint256(-1), address(msg.sender)); } /* @@ -46,8 +46,7 @@ contract KittyCore is Ownable, KittyMarketPlace { function Breeding(uint256 _dadId, uint256 _mumId) public { require(_owns(msg.sender, _dadId), "The user doesn't own the token"); require(_owns(msg.sender, _mumId), "The user doesn't own the token"); - - require(_mumId != _dadId), "The cat can't reproduce himself"); + require(_mumId != _dadId, "The cat can't reproduce himself"); ( uint256 Dadgenes,,,,uint256 DadGeneration ) = getKitty(_dadId); @@ -105,8 +104,7 @@ contract KittyCore is Ownable, KittyMarketPlace { gen0Counter++; // Gen0 have no owners they are own by the contract - uint256 tokenId = _createKitty(0, 0, 0, _genes, msg.sender); - setOffer(0.2 ether, tokenId); + _createKitty(0, 0, 0, _genes, msg.sender); } function getKitty(uint256 _id) diff --git a/contracts/KittyMarketPlace.sol b/contracts/KittyMarketPlace.sol index 563cf03..020842d 100644 --- a/contracts/KittyMarketPlace.sol +++ b/contracts/KittyMarketPlace.sol @@ -1,86 +1,112 @@ -import "./KittyOwnership.sol"; - pragma solidity ^0.5.0; -contract KittyMarketPlace is KittyOwnership { - - struct Offer { - address payable seller; - uint256 price; - } - - Offer [] offers; - - mapping (uint256 => Offer) tokenIdToOffer; - - event MarketTransaction(string TxType, address owner, uint256 tokenId); - - function getOffer(uint256 _tokenId) - public - view - returns - ( - address seller, - uint256 price - ) { - Offer storage offer = tokenIdToOffer[_tokenId]; - return ( - offer.seller, - offer.price - ); - } - - function setOffer(uint256 _price, uint256 _tokenId) - public - { - /* - * We give the contract the ability to transfer kitties - * As the kitties will be in the market place we need to be able to transfert them - * We are checking if the user is owning the kitty inside the approve function - */ - require(_price > 0.009 ether, "Cat price should be greater than 0.01"); - require(tokenIdToOffer[_tokenId].price == 0, "You can't sell twice the same offers "); +import "./KittyCore.sol"; +import "./utils/Ownable.sol"; + +/* + * Market place to trade kitties (should **in theory** be used for any ERC721 token) + * It needs an existing Kitty contract to interact with + * Note: it does not inherit from the kitty contracts + * Note: It takes ownership of the kitty for the duration that it is on the marketplace + */ +contract KittyMarketPlace is Ownable { + KittyCore public kittyContract; + + struct Offer { + address payable seller; + uint256 price; + } + + event OfferCreated(uint256 kittyId, uint256 price); + event OfferCancelled(uint256 kittyId); + event OfferSuccess(uint256 kittyId, uint256 price, address newOwner); + + mapping(uint256 => Offer) kittyIdToOffer; - approve(address(this), _tokenId); + function setKittyContract(address _kittyContractAddress) public onlyOwner { + kittyContract = KittyCore(_kittyContractAddress); + } - tokenIdToOffer[_tokenId].seller = msg.sender; - tokenIdToOffer[_tokenId].price = _price; - emit MarketTransaction("Create offer", msg.sender, _tokenId); - } + constructor(address _kittyContractAddress) public onlyOwner { + setKittyContract(_kittyContractAddress); + } - function removeOffer(uint256 _tokenId) - public - { - require(_owns(msg.sender, _tokenId), "The user doesn't own the token"); + function _ownsKitty(address _address, uint256 _kittyId) + internal + view + returns (bool) + { + return (kittyContract.ownerOf(_kittyId) == _address); + } - Offer memory offer = tokenIdToOffer[_tokenId]; - require(offer.seller == msg.sender, "You should own the kitty to be able to remove this offer"); + function _addOffer(uint256 _kittyId, Offer memory _offer) internal { + kittyIdToOffer[_kittyId] = _offer; - delete tokenIdToOffer[_tokenId]; - _deleteApproval(_tokenId) + emit OfferCreated(_kittyId, _offer.price); + } - emit MarketTransaction("Remove offer", msg.sender, _tokenId); - } + function _cancelOffer(uint256 _kittyId, address _seller) internal { + delete kittyIdToOffer[_kittyId]; + _returnKitty(_seller, _kittyId); - function buyKitty(uint256 _tokenId) - public - payable - { - Offer memory offer = tokenIdToOffer[_tokenId]; - require(msg.value == offer.price, "The price is not correct"); - - delete tokenIdToOffer[_tokenId]; + emit OfferCancelled(_kittyId); + } - /* TMP REMOVE THIS*/ - _approve(_tokenId, msg.sender); + function _completeOffer(uint256 _kittyId, Offer memory offer) internal { + // Important: delete the kitty from the mapping BEFORE paying out to prevent reentry attacks + delete kittyIdToOffer[_kittyId]; + // Transfer the funds to the seller + // TODO: make this logic pull instead of push? + if (offer.price > 0) { + offer.seller.transfer(offer.price); + } - transferFrom(offer.seller, msg.sender, _tokenId); + // Transfer ownership of the kitty + kittyContract.transfer(msg.sender, _kittyId); + emit OfferSuccess(_kittyId, offer.price, msg.sender); + } - offer.seller.transfer(msg.value); - emit MarketTransaction("Buy", msg.sender, _tokenId); - } - + function _returnKitty(address _owner, uint256 _kittyId) internal { + kittyContract.transfer(_owner, _kittyId); + } -} \ No newline at end of file + /* + * Create a new offer based for the given kittyId and price + */ + function createOffer(uint256 _kittyId, uint256 _price) public { + require( + _ownsKitty(msg.sender, _kittyId), + "You are not the owner of that kitty" + ); + require(_price > 0.009 ether, "Cat price should be greater than 0.01"); + require(kittyContract.getApproved(_kittyId) == address(this), "Contract needs to be approved to transfer the kitty"); + + Offer memory offer = Offer(msg.sender, _price); + _addOffer(_kittyId, offer); + } + + /* + * Remove an existing offer + */ + function removeOffer(uint256 _kittyId) public { + Offer memory offer = kittyIdToOffer[_kittyId]; + require( + offer.seller == msg.sender, + "You are not the seller of that kitty" + ); + + _cancelOffer(_kittyId, offer.seller); + } + + /* + * Accept an offer and buy the kitty + */ + function buyKitty(uint256 _kittyId) public payable { + Offer memory offer = kittyIdToOffer[_kittyId]; + require(msg.value == offer.price, "The price is incorrect"); + + _completeOffer(_kittyId, offer); + } +} diff --git a/contracts/KittyOwnership.sol b/contracts/KittyOwnership.sol index 61f7eb8..1355344 100644 --- a/contracts/KittyOwnership.sol +++ b/contracts/KittyOwnership.sol @@ -76,19 +76,24 @@ contract KittyOwnership is KittyFactory{ address _to, uint256 _tokenId ) - public + external { - require(_owns(msg.sender, _tokenId)); + require(_owns(msg.sender, _tokenId), "You do not own this kitty"); _approve(_tokenId, _to); emit Approval(msg.sender, _to, _tokenId); } + function getApproved(uint256 _tokenId) external view returns (address) { + return kittyIndexToApproved[_tokenId]; + } + + function transfer( address _to, uint256 _tokenId ) - public + external { require(_to != address(0)); require(_owns(msg.sender, _tokenId)); @@ -101,11 +106,10 @@ contract KittyOwnership is KittyFactory{ address _to, uint256 _tokenId ) - public + external { - require(_to != address(0)); - require(_approvedFor(msg.sender, _tokenId)); - require(_owns(_from, _tokenId)); + require(_to != address(0), "Cannot send to addresss 0"); + require(_approvedFor(msg.sender, _tokenId) || _owns(_from, _tokenId), "You need to be approved or owner"); _transfer(_from, _to, _tokenId); } diff --git a/migrations/2_KittyCore_migration.js b/migrations/2_KittyCore_migration.js index 18b6b02..c869414 100644 --- a/migrations/2_KittyCore_migration.js +++ b/migrations/2_KittyCore_migration.js @@ -1,5 +1,20 @@ -const KittyCore = artifacts.require("./KittyCore"); +const KittyCore = artifacts.require("KittyCore"); +const KittyMarketPlace = artifacts.require("KittyMarketPlace"); -module.exports = function(deployer) { - deployer.deploy(KittyCore); +module.exports = async (deployer, network, accounts) => { + const ownerAddress = accounts[0]; + const initialKittyPrice = web3.utils.toWei("0.05", "ether") + + // Deploy contracts + await deployer.deploy(KittyCore, {from :ownerAddress}); + await deployer.deploy(KittyMarketPlace, KittyCore.address, {from :ownerAddress}); + + const kittyCoreInstance = await KittyCore.deployed(); + const kittyMarketPlaceInstance = await KittyMarketPlace.deployed(); + + // Set first initial offer in marketplace + const createdKitties = await kittyCoreInstance.tokensOfOwner(ownerAddress) + const createdKitty = createdKitties[0] + await kittyCoreInstance.approve(KittyMarketPlace.address,createdKitty) + await kittyMarketPlaceInstance.createOffer(createdKitty, initialKittyPrice, {from : ownerAddress}) };