Implement Kitty iMarketplace
Hereās my code:
Marketplace:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "./Kittycontract.sol";
import "./IKittyMarketplace.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
contract KittyMarketplace is IKittyMarketPlace, Ownable {
address payable kittyContractAddress;
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
mapping(uint256 => Offer) tokenIdToOffer;
uint256[] tokenOffers; // array of tokenId
constructor(address tokenContractAddress) {
kittyContractAddress = payable(tokenContractAddress);
}
function setKittyContract(address _kittyContractAddress)
external
override
onlyOwner
{
kittyContractAddress = payable(_kittyContractAddress);
}
function getTokenContractAddress() external view returns (address) {
return kittyContractAddress;
}
function getOffer(uint256 _tokenId)
external
view
override
returns (
address seller,
uint256 price,
uint256 index,
uint256 tokenId,
bool active
)
{
// do checks
require(
tokenIdToOffer[_tokenId].active == true,
"Token offer doesn't exist"
);
// set return variables
seller = tokenIdToOffer[_tokenId].seller;
price = tokenIdToOffer[_tokenId].price;
index = tokenIdToOffer[_tokenId].index;
tokenId = tokenIdToOffer[_tokenId].tokenId;
active = tokenIdToOffer[_tokenId].active;
}
function getAllTokenOnSale()
external
view
override
returns (uint256[] memory listOfOffers)
{
return tokenOffers;
}
function setOffer(uint256 _price, uint256 _tokenId) external override {
// create instance of token contract
Kittycontract TokenContract = Kittycontract(kittyContractAddress);
// Only owner of _tokenId can create offer
require(
TokenContract.ownerOf(_tokenId) == msg.sender,
"Must be owner to set offer"
);
// Can't create offer it there is already one present for this _tokenId
require(
!tokenIdToOffer[_tokenId].active,
"Can't have more than one offer for token"
);
// Marketplace needs to be an approved operator when the offer is created
require(
TokenContract.getApproved(_tokenId) == address(this) ||
TokenContract.isApprovedForAll(msg.sender, address(this)),
"Marketplace has to be approved before making offer"
);
// Add to offer list
tokenOffers.push(_tokenId);
// Create offer in mapping
Offer memory _offer =
Offer({
seller: payable(msg.sender),
price: _price,
index: tokenOffers.length - 1,
tokenId: _tokenId,
active: true
});
tokenIdToOffer[_tokenId] = _offer;
// emit
emit MarketTransaction("Create offer", msg.sender, _tokenId);
}
/**
* Removes an existing offer.
*/
function removeOffer(uint256 _tokenId) external override {
//checks
require(
tokenIdToOffer[_tokenId].seller == msg.sender,
"You can only remove your own offer"
);
_removeOffer(_tokenId);
//emit
emit MarketTransaction("Remove offer", msg.sender, _tokenId);
}
function _removeOffer(uint256 _tokenId) internal {
//remove from tokenOffers
uint256 indexToRemove = tokenIdToOffer[_tokenId].index;
//copy last element to index of _tokenId
tokenOffers[indexToRemove] = tokenOffers[tokenOffers.length];
//update index of the previous last element in array to the index of the deleted
tokenIdToOffer[tokenOffers[tokenOffers.length]].index = indexToRemove;
//pop of the last element
tokenOffers.pop();
//remove from tokenIdToOffer
delete tokenIdToOffer[_tokenId];
}
/**
* Executes the purchase of _tokenId.
* Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
*/
function buyKitty(uint256 _tokenId) public payable override {
//Requirement: There must be an active offer for _tokenId
require(
tokenIdToOffer[_tokenId].active == true,
"No offer for this token"
);
//Requirement: The msg.value needs to equal the price of _tokenId
require(
tokenIdToOffer[_tokenId].price == msg.value,
"Sent value doesn't equal offer price"
);
// Take Kitty of market and prevent reentry
tokenIdToOffer[_tokenId].active = false;
//transfer funds
Offer memory offer = tokenIdToOffer[_tokenId];
offer.seller.transfer(msg.value);
//transfer NFT
Kittycontract TokenContract = Kittycontract(kittyContractAddress);
TokenContract.transferFrom(
tokenIdToOffer[_tokenId].seller,
msg.sender,
_tokenId
);
//remove offer
_removeOffer(_tokenId);
emit MarketTransaction("Buy", msg.sender, _tokenId);
}
}
Kittycontract:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "./IERC721.sol";
import "./IERC721Receiver.sol";
contract Kittycontract is IERC721, Ownable {
uint256 public constant CREATION_LIMIT_GEN0 = 10;
string public constant override name = "VivekKitties";
string public constant override symbol = "VK";
bytes4 internal constant MAGIC_ERC721_RECEIVED =
bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
bytes4 internal constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
bytes4 internal constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
//event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Birth(
address indexed owner,
uint256 kittenId,
uint256 mumId,
uint256 dadId,
uint256 genes
);
struct Kitty {
uint256 genes;
uint64 birthTime;
uint32 mumId;
uint32 dadId;
uint16 generation;
}
Kitty[] kitties;
mapping(uint256 => address) public kittyIndexToOwner;
mapping(address => uint256) ownershipTokenCount;
mapping(uint256 => address) kittyIndexToApproved;
// MYADDR => OPERATORADDR => TRUE/FALSE
// _operaterApprovals[MYADDR][OPERATORADDR] = false;
mapping(address => mapping(address => bool)) private _operatorApprovals;
mapping(address => uint256[]) ownerToCats;
function supportsInterface(bytes4 _interfaceId)
external
pure
returns (bool)
{
return (_interfaceId == _INTERFACE_ID_ERC721 ||
_interfaceId == _INTERFACE_ID_ERC165);
}
function balanceOf(address owner)
external
view
override
returns (uint256 balance)
{
return ownershipTokenCount[owner];
}
function totalSupply() public view override returns (uint256) {
return kitties.length;
}
function ownerOf(uint256 _tokenId)
external
view
override
returns (address)
{
return kittyIndexToOwner[_tokenId];
}
function transfer(address _to, uint256 _tokenId) external override {
require(_to != address(0));
require(_to != address(this));
require(_owns(msg.sender, _tokenId));
_transfer(msg.sender, _to, _tokenId);
}
function getAllCatsFor(address _owner)
external
view
returns (uint256[] memory cats)
{
return ownerToCats[_owner];
}
uint256 public gen0Counter;
function createKittyGen0(uint256 _genes)
public
onlyOwner
returns (uint256)
{
require(
gen0Counter < CREATION_LIMIT_GEN0,
"can't create more gen0 cats"
);
gen0Counter++;
return _createKitty(0, 0, 0, _genes, msg.sender);
}
function _createKitty(
uint256 _mumId,
uint256 _dadId,
uint256 _generation,
uint256 _genes,
address _owner
) private returns (uint256) {
Kitty memory _kitty =
Kitty({
genes: _genes,
birthTime: uint64(block.timestamp),
mumId: uint32(_mumId),
dadId: uint32(_dadId),
generation: uint16(_generation)
});
kitties.push(_kitty);
uint256 newKittenId = kitties.length - 1;
emit Birth(_owner, newKittenId, _mumId, _dadId, _genes);
_transfer(address(0), _owner, newKittenId);
return newKittenId;
}
function breed(uint256 _dadId, uint256 _mumId) public returns (uint256) {
// Check Ownership
require(
_owns(msg.sender, _dadId) && _owns(msg.sender, _mumId),
"Must own parents to breed"
);
Kitty storage dad = kitties[_dadId];
Kitty storage mum = kitties[_mumId];
// Figure out generation
uint16 newGen = 0;
if ((dad.generation + mum.generation) > 0) {
newGen = (dad.generation + mum.generation) / 2 + 1;
} else {
newGen = 1;
}
// Create a new cat with new properties and give it to msg.sender
uint256 newDna = _mixDna(dad.genes, mum.genes);
return _createKitty(_mumId, _dadId, newGen, newDna, msg.sender);
}
function _mixDna(uint256 _dadDna, uint256 _mumDna)
internal
view
returns (uint256)
{
uint256 dadPart = _dadDna / 100000000;
uint256 mumPart = _mumDna % 100000000;
uint256 newDna = (dadPart * 100000000) + mumPart;
// Make animation completely Random I have implemented 1-7 types so I'll limit to that
uint8 random = uint8(block.timestamp % 7) + 1;
uint256 removeBit = newDna % 100;
newDna = (newDna - removeBit) + (random * 10) + 1;
return newDna;
}
function _transfer(
address _from,
address _to,
uint256 _tokenId
) internal {
ownershipTokenCount[_to]++;
kittyIndexToOwner[_tokenId] = _to;
ownerToCats[_to].push(_tokenId);
if (_from != address(0)) {
ownershipTokenCount[_from]--;
_removeTokenIdFromOwner(_from, _tokenId);
delete kittyIndexToApproved[_tokenId];
}
// Emit the transfer event.
emit Transfer(_from, _to, _tokenId);
}
function _removeTokenIdFromOwner(address _owner, uint256 _tokenId)
internal
{
uint256 lastId = ownerToCats[_owner][ownerToCats[_owner].length - 1];
for (uint256 i = 0; i < ownerToCats[_owner].length; i++) {
if (ownerToCats[_owner][i] == _tokenId) {
ownerToCats[_owner][i] = lastId;
ownerToCats[_owner].pop();
}
}
}
function _owns(address _claimant, uint256 _tokenId)
internal
view
returns (bool)
{
return kittyIndexToOwner[_tokenId] == _claimant;
}
// function getKitty(uint256 _kittyID) public view returns (Kitty memory){
// return kitties[_kittyID];
// }
function getKitty(uint256 _kittyID)
public
view
returns (
uint256 genes,
uint256 birthTime,
uint256 mumId,
uint256 dadId,
uint256 generation
)
{
Kitty storage kitty = kitties[_kittyID];
genes = uint256(kitty.genes);
birthTime = uint256(kitty.birthTime);
mumId = uint256(kitty.mumId);
dadId = uint256(kitty.dadId);
generation = uint256(kitty.generation);
// set values will automatically be returned without return statement
}
function _isApprovedForAll(address _owner, address _operator)
private
view
returns (bool status)
{
return _operatorApprovals[_owner][_operator];
}
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external override {
require(
_owns(msg.sender, _tokenId) ||
_isApprovedForAll(kittyIndexToOwner[_tokenId], msg.sender),
"Must own or operate to approve"
);
kittyIndexToApproved[_tokenId] = _approved;
}
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved)
external
override
{
require(_operator != msg.sender, "can't approve yourself for all");
_operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId)
external
view
override
returns (address)
{
//require(kittyIndexToOwner[_tokenId] != address(0), "Kitty does not exist");
require(_tokenId < kitties.length, "Kitty does not exist");
return kittyIndexToApproved[_tokenId];
}
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator)
external
view
override
returns (bool)
{
return _isApprovedForAll(_owner, _operator);
}
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(
address _from,
address _to,
uint256 _tokenId
) external override {
require(_owns(_from, _tokenId));
require(
_owns(msg.sender, _tokenId) ||
_isApprovedForAll(kittyIndexToOwner[_tokenId], msg.sender) ||
kittyIndexToApproved[_tokenId] == msg.sender,
"Must own or operate or be approved to transfer"
);
require(_to != address(0), "can't transfer to zero address");
require(_tokenId < kitties.length, "Kitty does not exist");
_transfer(_from, _to, _tokenId);
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes calldata data
) external override {
require(_owns(_from, _tokenId));
require(
_owns(msg.sender, _tokenId) ||
_isApprovedForAll(kittyIndexToOwner[_tokenId], msg.sender) ||
kittyIndexToApproved[_tokenId] == msg.sender,
"Must own or operate or be approved to transfer"
);
require(_to != address(0), "can't transfer to zero address");
require(_tokenId < kitties.length, "Kitty does not exist");
_safeTransfer(_from, _to, _tokenId, data);
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId
) external override {
require(_owns(_from, _tokenId));
require(
_owns(msg.sender, _tokenId) ||
_isApprovedForAll(kittyIndexToOwner[_tokenId], msg.sender) ||
kittyIndexToApproved[_tokenId] == msg.sender,
"Must own or operate or be approved to transfer"
);
require(_to != address(0), "can't transfer to zero address");
require(_tokenId < kitties.length, "Kitty does not exist");
_safeTransfer(_from, _to, _tokenId, "");
}
function _checkERC721Support(
address _from,
address _to,
uint256 _tokenId,
bytes memory _data
) internal returns (bool) {
if (!_isContract(_to)) {
return true;
}
// Call onERC721Received in the _to contract
bytes4 returnData =
IERC721Receiver(_to).onERC721Received(
msg.sender,
_from,
_tokenId,
_data
);
return returnData == MAGIC_ERC721_RECEIVED;
}
function _isContract(address _to) internal view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_to)
}
return size > 0;
}
function _safeTransfer(
address _from,
address _to,
uint256 _tokenId,
bytes memory _data
) internal {
_transfer(_from, _to, _tokenId);
// Call
require(
_checkERC721Support(_from, _to, _tokenId, _data),
"ERC721 not support"
);
}
}
Marketplace Test:
const Kittycontract = artifacts.require("Kittycontract");
const KittyMarketplace = artifacts.require("KittyMarketplace");
const truffleAssert = require('truffle-assertions');
contract("KittyMarketplace", accounts => {
it("Should be able to set token contract address", async () => {
let kittycontract = await Kittycontract.deployed();
const tokenaddress = kittycontract.address;
//console.log("tokenaddress: " + tokenaddress);
let marketplace = await KittyMarketplace.deployed();
const marketaddress = marketplace.address;
//console.log("marketaddress: " + marketaddress);
//Test set functionality by setting token contract address to market address even if this is terribly wrong :)
await marketplace.setKittyContract(marketaddress, {from: accounts[0]});
const addressafter = await marketplace.getTokenContractAddress();
//console.log("After setting address : " + addressafter);
assert(addressafter == marketaddress, "Address not set to correctaddress");
});
});
// Start fresh
contract("KittyMarketplace", accounts => {
it("Should be able to buy Kitty at right price", async () => {
let kittycontract = await Kittycontract.deployed();
let marketplace = await KittyMarketplace.deployed();
// Create kitty by first account
await kittycontract.createKittyGen0(7333684321281031, {from: accounts[0]});
// Authorize Marketplace
await kittycontract.setApprovalForAll(marketplace.address, true, {from: accounts[0]});
// Set kitty for sale
await marketplace.setOffer(50000, 0, {from: accounts[0]});
// Buy kitty bij second account
await marketplace.buyKitty(0,{from: accounts[1], value: 50000, gas:100000});
});
});
@kenn.eth or @AdamFortuna can you see why my buyKitty function reverts Iāve tried all kinds of things even copied everything to remix to see if I could find something. The code compiles but I get a runtime error. In truffle test it gives:
- Contract: KittyMarketplace
Should be able to buy Kitty at right price:
Error: Returned error: VM Exception while processing transaction: revert
at Context. (test\MarketplaceTest.js:44:27)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
in Remix it gave the following error:
transact to KittyMarketplace.buyKitty errored: VM error: revert. revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information.
What am I missing here?
Thanks!
Hey can you send a Big picture of remix, contract and error showing? Maybe you are not sending value.
Thanks @kenn.eth, As you can see in the debug 50000 was sent. It is very strange. I havenāt done assembly since my studies many years back and not familiar with EVM so canāt make much sense of the debugger. By process of elimination I have found that itās the transfer function that causes the error.
@kenn.eth just a small update, I also made the constructor payable which I found as a possible solution online. Unfortunately that didnāt have a different result. I also created the contract with some value so it would have eth itself in case that was a problem. Also that didnāt give a different result. I keep looking for something along those lines.
Hi @kenn.eth some more information. It turns out my removeOffer function gives same error. I still canāt see anything wrong with it but a fresh pair of eyes might. Here it is:
function removeOffer(uint256 _tokenId) external override {
//checks
Offer memory offer = tokenIdToOffer[_tokenId];
require(
offer.seller == msg.sender,
"You can only remove your own offer"
);
_removeOffer(_tokenId);
//emit
emit MarketTransaction("Remove offer", msg.sender, _tokenId);
}
function _removeOffer(uint256 _tokenId) internal {
//remove from tokenOffers
uint256 indexToRemove = tokenIdToOffer[_tokenId].index;
//copy last element to index of _tokenId
tokenOffers[indexToRemove] = tokenOffers[tokenOffers.length];
//update index of the previous last element in array to the index of the deleted
tokenIdToOffer[tokenOffers[tokenOffers.length]].index = indexToRemove;
//pop of the last element
tokenOffers.pop();
//remove from tokenIdToOffer
delete tokenIdToOffer[_tokenId];
}
In this case i see that you are trying to convert a non-payable address into a payable one. Solidity have this types for payable and non-payable, basically is makes you think if you will send money or not to this address in the future. So payable
is a modifier that can be added to a function, cannot use it to convert a address to payable. Is easy to convert a payable into non-payable but upside down is different.
address payable addr = address(uint160(nftAddress));
In your case you dont need to use payable for tokenIdToOffer[_tokenId].seller since you declare like payable address in the Offer struct
Let me know if this solve your issue.
Hi @kenn.eth, thanks for the insights. I was able to make sure all the addresses were typed correctly.
I was able to fix the error in the remove offer function which was the underlying cause of the problem. Iām still not done debugging because Iām having the same error on another function. Turns out the error message had nothing to do with the underlying problem. Thanks Iāll ask for help again if I canāt find what is causing this new problem.
Great! You can also send me picture of the error. So will know whats about this time.
Hi @kenn.eth Iāve fixed the issue it was the same error but this time the buyKitty function call needed more gas. Both Remix and Truffle tests work fine now. Now I can start working on the interface. Is there anyway of determining before hand how much gas is required for a function?
Thanks!
Final version of code posted below:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "./Kittycontract.sol";
import "./IKittyMarketplace.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
contract KittyMarketplace is IKittyMarketPlace, Ownable {
address payable kittyContractAddress;
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
mapping(uint256 => Offer) tokenIdToOffer;
uint256[] tokenOffers; // array of tokenId
constructor(address tokenContractAddress) payable {
kittyContractAddress = payable(tokenContractAddress);
}
function setKittyContract(address _kittyContractAddress)
external
override
onlyOwner
{
kittyContractAddress = payable(_kittyContractAddress);
}
function getTokenContractAddress() external view returns (address) {
return kittyContractAddress;
}
function getOffer(uint256 _tokenId)
external
view
override
returns (
address seller,
uint256 price,
uint256 index,
uint256 tokenId,
bool active
)
{
// do checks
require(
tokenIdToOffer[_tokenId].active == true,
"Token offer doesn't exist"
);
// set return variables
seller = tokenIdToOffer[_tokenId].seller;
price = tokenIdToOffer[_tokenId].price;
index = tokenIdToOffer[_tokenId].index;
tokenId = tokenIdToOffer[_tokenId].tokenId;
active = tokenIdToOffer[_tokenId].active;
}
function getAllTokenOnSale()
external
view
override
returns (uint256[] memory listOfOffers)
{
return tokenOffers;
}
function setOffer(uint256 _price, uint256 _tokenId) external override {
// create instance of token contract
Kittycontract TokenContract = Kittycontract(kittyContractAddress);
// Only owner of _tokenId can create offer
require(
TokenContract.ownerOf(_tokenId) == msg.sender,
"Must be owner to set offer"
);
// Can't create offer it there is already one present for this _tokenId
require(
!tokenIdToOffer[_tokenId].active,
"Can't have more than one offer for token"
);
// Marketplace needs to be an approved operator when the offer is created
require(
TokenContract.getApproved(_tokenId) == address(this) ||
TokenContract.isApprovedForAll(msg.sender, address(this)),
"Marketplace has to be approved before making offer"
);
// Add to offer list
tokenOffers.push(_tokenId);
// Create offer in mapping
Offer memory _offer =
Offer({
seller: payable(msg.sender),
price: _price,
index: tokenOffers.length - 1,
tokenId: _tokenId,
active: true
});
tokenIdToOffer[_tokenId] = _offer;
// emit
emit MarketTransaction("Create offer", msg.sender, _tokenId);
}
/**
* Removes an existing offer.
*/
function removeOffer(uint256 _tokenId) external override {
//checks
Offer memory offer = tokenIdToOffer[_tokenId];
require(
offer.seller == msg.sender,
"You can only remove your own offer"
);
_removeOffer(_tokenId);
//emit
emit MarketTransaction("Remove offer", msg.sender, _tokenId);
}
function _removeOffer(uint256 _tokenId) internal {
//remove from tokenOffers
uint256 indexToRemove = tokenIdToOffer[_tokenId].index;
uint256 lastIndex = tokenOffers.length - 1;
uint256 lastTokenId = tokenOffers[lastIndex];
//update index of the last element in array to the index of the offer to be deleted
tokenIdToOffer[lastTokenId].index = indexToRemove;
//copy last element to index of _tokenId
tokenOffers[indexToRemove] = lastTokenId;
//pop of the last element
tokenOffers.pop();
//remove from tokenIdToOffer
delete tokenIdToOffer[_tokenId];
}
/**
* Executes the purchase of _tokenId.
* Sends the funds to the seller and transfers the token using transferFrom in Kittycontract.
*/
function buyKitty(uint256 _tokenId) public payable override {
//Requirement: There must be an active offer for _tokenId
require(
tokenIdToOffer[_tokenId].active == true,
"No offer for this token"
);
//Requirement: The msg.value needs to equal the price of _tokenId
require(
tokenIdToOffer[_tokenId].price == msg.value,
"Sent value doesn't equal offer price"
);
// Take Kitty of market and prevent reentry
tokenIdToOffer[_tokenId].active = false;
//transfer funds
Offer memory offer = tokenIdToOffer[_tokenId];
offer.seller.transfer(msg.value);
//transfer NFT
Kittycontract TokenContract = Kittycontract(kittyContractAddress);
TokenContract.transferFrom(
tokenIdToOffer[_tokenId].seller,
msg.sender,
_tokenId
);
//remove offer
_removeOffer(_tokenId);
emit MarketTransaction("Buy", msg.sender, _tokenId);
}
}
well this you this you can set it into local network config using ganache CLI. But normally you would like to use same as mainnet.
Took me a few hours but I think I got it:
DoraMarketplace.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Doracontract.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "./IDoraMarketplace.sol";
contract DoraMarketplace is Ownable, IDoraMarketPlace {
Doracontract private _DoraContract;
struct Offer {
address seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
Offer[] offers;
mapping(uint256 => Offer) tokenIdToOffer;
function setDoraContract(address _doraContractAddress) external override onlyOwner {
_DoraContract = Doracontract(_doraContractAddress);
}
function getContractAddress() external view onlyOwner returns(address){
return address(this);
}
function getOffer(uint256 _tokenId) external override view returns ( address seller, uint256 price, uint256 index, uint256 tokenId, bool active){
Offer storage offer = tokenIdToOffer[_tokenId];
require(offer.active == true, "Marketplace: There is no active offer for this token");
return (
seller = offer.seller,
price = offer.price,
index = offer.index,
tokenId = offer.tokenId,
active = offer.active
);
}
function getAllTokenOnSale() external override view returns(uint256[] memory){
uint256 numOfOffers = offers.length;
uint256[] memory listOfOffers = new uint256[](numOfOffers);
for(uint i = 0; i < numOfOffers; i++){
listOfOffers[i] = offers[i].tokenId;
}
return listOfOffers;
}
function setOffer(uint256 _price, uint256 _tokenId) external override {
require(msg.sender == _DoraContract.ownerOf(_tokenId), "Marketplace: You are not the owner of this token");
require(tokenIdToOffer[_tokenId].active == false, "Marketplace: The token is already on sale");
require(address(this) == _DoraContract.getApproved(_tokenId), "Marketplace: This contract isn't an operator for this token");
_setOffer(_price, _tokenId, msg.sender);
}
function removeOffer(uint256 _tokenId) external override {
require(msg.sender == tokenIdToOffer[_tokenId].seller, "Marketplace: Only the seller of this token can remove offer");
require(tokenIdToOffer[_tokenId].active == true, "Marketplace: The token is already not on sale");
_removeOffer(_tokenId, msg.sender);
}
function buyDoraemon(uint256 _tokenId) external override payable {
Offer memory targetToken = tokenIdToOffer[_tokenId];
require(targetToken.price == msg.value, "Marketplace: Incorrect fund" );
require(targetToken.seller != msg.sender, "Marketplace: Cannot by your own token!");
require(targetToken.active == true, "Marketplace: The token is not for sell");
_buyDoraemon(_tokenId, msg.sender);
}
function _setOffer(uint256 _price, uint256 _tokenId, address _seller) internal {
Offer memory newOffer = Offer({
seller: _seller,
price: _price,
index: offers.length,
tokenId: _tokenId,
active: true
});
offers.push(newOffer);
tokenIdToOffer[_tokenId] = newOffer;
emit MarketTransaction("Create offer", _seller, _tokenId);
}
function _removeOffer(uint256 _tokenId, address _seller) internal {
uint256 targetIndex = tokenIdToOffer[_tokenId].index;
uint256 lastIndex = offers.length - 1;
if(lastIndex > 0){
offers[targetIndex] = offers[lastIndex];
offers[targetIndex].index = targetIndex;
tokenIdToOffer[offers[targetIndex].tokenId] = offers[targetIndex];
}
offers.pop();
delete tokenIdToOffer[_tokenId];
emit MarketTransaction("Remove offer", _seller, _tokenId);
}
function _buyDoraemon(uint256 _tokenId, address _buyer) internal {
Offer memory targetToken = tokenIdToOffer[_tokenId];
address seller = targetToken.seller;
uint256 price = targetToken.price;
(bool success, ) = payable(seller).call{value: price}("");
require(success, "Marketplace: Failed to send funds to the seller");
_DoraContract.safeTransferFrom(seller, _buyer, _tokenId);
_removeOffer(_tokenId, seller);
emit MarketTransaction("Buy", _buyer, _tokenId);
}
}
I also did some tests, can be found at test/marketTest.js
.
Full code: https://github.com/REGO350/nftgame
hello this is my code it is working but not completely ok. I have two questions
Marketplace.sol
pragma solidity ^0.8.4;
import "./IKittyMarketplace.sol";
contract Marketplace is IKittyMarketPlace {
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
uint indexCount;
uint256[] tokensOnSale;
Offer[] Offers;
mapping (uint256 => Offer) public tokenIdToOffer;
KittyContract addr;
function setKittyContract(address _kittyContractAddress) override external{
addr = KittyContract(_kittyContractAddress);
}
function balanceFromMarket() public view returns(uint256) {
return addr.balanceOf(msg.sender);
}
function totalSupplyFromMarket() public view returns(uint256){
return addr.totalSupply();
}
function setOffer(uint256 _price, uint256 _tokenId) override external {
require(addr.ownerOf(_tokenId)== msg.sender,'Only the owner of _tokenId can create an offer' );
require(tokenIdToOffer[_tokenId].active ==false, 'There can only be one active offer for a token at a time');
Offer memory _Offer = Offer ({ // Creates a new offer for _tokenId for the price _price
seller: payable(msg.sender),
price: _price,
index: indexCount++,
tokenId: _tokenId,
active: true
});
tokenIdToOffer[_tokenId] = _Offer;
Offers.push(_Offer);
tokensOnSale.push(_Offer.tokenId);
//addr.approve(address(this), _tokenId); //Marketplace contract (this) needs to be an approved operator when the offer is created
emit MarketTransaction("Create offer", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Create offer"
}
function removeOffer(uint256 _tokenId) external override {
require(addr.ownerOf(_tokenId)== msg.sender);// Only the seller of _tokenId can remove an offer.
/* for (uint i; i<=Offers.length; i++ ){//Removes an existing offer on the array
if (Offers[i].tokenId == _tokenId){
delete Offers[i];
delete tokensOnSale[i];
}
}*/
Offer memory _Offer = Offer ({ // Removes an existing offer on the map
seller: payable(0x0000000000000000000000000000000000000000),
price: 0,
index: 0,
tokenId: 0,
active: false
});
tokenIdToOffer[_tokenId] = _Offer;
emit MarketTransaction("Remove offer", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Remove offer"
}
function getOffer(uint256 _tokenId) external override view returns ( address seller, uint256 price, uint256 index, uint256 tokenId, bool active){
//Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId.
require (tokenIdToOffer[_tokenId].active == true, "there is no active offer for _tokenId");
return (tokenIdToOffer[_tokenId].seller, tokenIdToOffer[_tokenId].price, tokenIdToOffer[_tokenId].index,
tokenIdToOffer[_tokenId].tokenId, tokenIdToOffer[_tokenId].active);
}
function getAllTokenOnSale() external override view returns(uint256[] memory listOfOffers){
// Get all tokenId's that are currently for sale. Returns an empty arror if none exist.
return tokensOnSale;
}
function buyKitty(uint256 _tokenId) external override payable {
require(tokenIdToOffer[_tokenId].active ==true, "There must be an active offer for _tokenId");
require (tokenIdToOffer[_tokenId].price >= msg.value, "The msg.value needs to equal the price of _tokenId");
uint transferValue = msg.value;
tokenIdToOffer[_tokenId].seller.transfer(transferValue);//Sends the funds to the seller
addr.transferFrom(tokenIdToOffer[_tokenId].seller, msg.sender, _tokenId); //transfers the token using transferFrom in Kittycontract.
emit MarketTransaction("Buy", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Buy".
}
}
my first question is on the function setOffer() in remix works perfectly , but with truffle i got an error here
addr.approve(address(this), _tokenId);
i use some logs from the require on the function _owns() on KittyContract
reason: 'msg.sender is not the owner',
hijackedStack: 'Error: Returned error: VM Exception while processing transaction: revert msg.sender is not the owner -- Reason given: msg.se
My question is when for example I have two contracts A and B, and you call a function from contract B and this calls another function on contract A. the msg.sender in contract A is the contract address in contract B or the msg.sender in contract B?
my second question is on the function removeOffer
for (uint i; i<=Offers.length; i++ ){//Removes an existing offer on the array
if (Offers[i].tokenId == _tokenId){
delete Offers[i];
delete tokensOnSale[i];
}
I know this code works on another languages but in solidity does not and I tried with while and is the same I do not how to erase some data on an array with a for or while loop . Why is the reason is for gas calculation before the execution ?
without this, all the rest works ok.
Hello everyone
i updated my code. Now all is working
1.I created two additional functions one for remove the orders on the map and other for remove on the arrays(no matter the position). Both of them works perfectly
2. when I tested buyKitty() function I figured out that i needed to remove the offer on my arrays and map at the end. I included my two new functions there.
3. according my testing.
on my kittycontract
function approve(address _approved, uint256 _tokenId) external override {
require(_owns(**msg.sender**, _tokenId),"msg.sender is not the owner"); // here msg.sender is the contract address on marketplacecontract when I call setOffer()
on marketplacecontract
function setOffer(uint256 _price, uint256 _tokenId) override external {
addr.approve(address(this), _tokenId); // addr is kittycontract
i have two options remove the require on the function approve or create the same approve function with another argument ( I prefer the second )
my code
Marketplace.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./IKittyMarketplace.sol";
contract Marketplace is IKittyMarketPlace {
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
uint indexCount;
uint256[] tokensOnSale;
Offer[] Offers;
mapping (uint256 => Offer) public tokenIdToOffer;
KittyContract addr;
function setKittyContract(address _kittyContractAddress) override external{
addr = KittyContract(_kittyContractAddress);
}
function balanceFromMarket() public view returns(uint256) {
return addr.balanceOf(msg.sender);
}
function totalSupplyFromMarket() public view returns(uint256){
return addr.totalSupply();
}
function setOffer(uint256 _price, uint256 _tokenId) override external {
require(addr.ownerOf(_tokenId)== msg.sender,'Only the owner of _tokenId can create an offer' );
require(tokenIdToOffer[_tokenId].active ==false, 'There can only be one active offer for a token at a time');
Offer memory _Offer = Offer ({ // Creates a new offer for _tokenId for the price _price
seller: payable(msg.sender),
price: _price,
index: indexCount++,
tokenId: _tokenId,
active: true
});
tokenIdToOffer[_tokenId] = _Offer;
Offers.push(_Offer);
tokensOnSale.push(_Offer.tokenId);
addr.approve(address(this), _tokenId, msg.sender); //Marketplace contract (this) needs to be an approved operator when the offer is created
emit MarketTransaction("Create offer", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Create offer"
}
function removeOffer(uint256 _tokenId) external override {
require(addr.ownerOf(_tokenId)== msg.sender);// Only the seller of _tokenId can remove an offer.
_removeOffer(_tokenId); // Removes an existing offer on the arrays
_removeOffer2(_tokenId);// Removes an existing offer on the map
emit MarketTransaction("Remove offer", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Remove offer"
}
function _removeOffer(uint256 _tokenId) internal returns(uint n) {
uint i;
uint j;
if (tokensOnSale.length-1 == _tokenId){
tokensOnSale.pop();
Offers.pop();
return 0;
}
while (i<= tokensOnSale.length){
if (tokensOnSale[i] == _tokenId){
tokensOnSale[i] = tokensOnSale[i+1];
Offers[i]= Offers[i+1];
j= 1;
}
else{
if (i== tokensOnSale.length-1){
tokensOnSale.pop();
Offers.pop();
return 0;
}
tokensOnSale[i] = tokensOnSale[i+j];
Offers[i]= Offers[i+j];
}
i++;
}
}
function _removeOffer2(uint256 _tokenId) internal {
Offer memory _Offer = Offer ({
seller: payable(0x0000000000000000000000000000000000000000),
price: 0,
index: 0,
tokenId: 0,
active: false
});
tokenIdToOffer[_tokenId] = _Offer;
}
function getOffer(uint256 _tokenId) external override view returns ( address seller, uint256 price, uint256 index, uint256 tokenId, bool active){
//Get the details about a offer for _tokenId. Throws an error if there is no active offer for _tokenId.
require (tokenIdToOffer[_tokenId].active == true, "there is no active offer for _tokenId");
return (tokenIdToOffer[_tokenId].seller, tokenIdToOffer[_tokenId].price, tokenIdToOffer[_tokenId].index,
tokenIdToOffer[_tokenId].tokenId, tokenIdToOffer[_tokenId].active);
}
function getAllTokenOnSale() external override view returns(uint256[] memory listOfOffers){
// Get all tokenId's that are currently for sale. Returns an empty arror if none exist.
return tokensOnSale;
}
function buyKitty(uint256 _tokenId) external override payable {
require(tokenIdToOffer[_tokenId].active ==true, "There must be an active offer for _tokenId");
require (tokenIdToOffer[_tokenId].price >= msg.value, "The msg.value needs to equal the price of _tokenId");
uint transferValue = msg.value;
tokenIdToOffer[_tokenId].seller.transfer(transferValue);//Sends the funds to the seller
addr.transferFrom(tokenIdToOffer[_tokenId].seller, msg.sender, _tokenId); //transfers the token using transferFrom in Kittycontract.
_removeOffer(_tokenId); // Removes offer on the arrays
_removeOffer2(_tokenId);// Removes offer on the map
emit MarketTransaction("Buy", msg.sender, _tokenId); //Emits the MarketTransaction event with txType "Buy".
}
}
and one test
kittyContract_test.js
@kenn.eth can you tell me if this is the correct way to test. this is the first time i test two contracts here at the same time. Thanks.
const KittyContract = artifacts.require("KittyContract");
const Marketplace = artifacts.require("Marketplace");
const assert = require('assert');
const truffleAssert = require('truffle-assertions');
contract(Marketplace, accounts => {
it(" set and offer and finishing buying a kitty id 0 with account[1]", async () => {
let marketplace = await Marketplace.deployed();
let kittyContract = await KittyContract.deployed();
await truffleAssert.passes(
marketplace.setKittyContract('0x8cB77eA82E9aE94f6d965b9fCd827B1a5AE71269')
)
await truffleAssert.passes(
await kittyContract.createKittyGen0(1020304050607080, {from: accounts[0]}),
marketplace.setOffer(10000000,0, {from: accounts[0]}),
marketplace.buyKitty(0, {from: accounts[1]})
)
})
})
truffle(develop)> test
Using network 'develop'.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Contract: function TruffleContract() {
this.constructor = temp;
return Contract.apply(this, arguments);
}
- transferFrom is not possible there is no token 0
- transferFrom msg.sender is the current owner
- transferFrom msg.sender is the approved address for token1
- transferFrom msg.sender is an authorized operator address for token2
Contract: function TruffleContract() {
this.constructor = temp;
return Contract.apply(this, arguments);
}
- create a kitties gen0
Contract: function TruffleContract() {
this.constructor = temp;
return Contract.apply(this, arguments);
}
ā use all functions on marketplace, set and offer and finishing buying a kitty id 0 with account[1] (1687ms)
1 passing (3s)
5 pending
truffle(develop)>
we almost finished I learned a lottttttttttt
Here is my attempt. Iām sure itās not working yet. I couldnāt try it because Iām pending on another issue from a previous assignment. Iām waiting for some help on thatā¦
MarketPlace.sol
/// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "./IKittyMarketplace.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MarketPlace is Ownable, IKittyMarketPlace{
Kittycontract private _kittyContract;
struct Offer{
address payable seller;
uint256 price;
uint index;
uint256 tokenId;
bool active;
}
mapping(uint256=>Offer) idToOffer;
address owner;
uint256[] idsKittiesForSale;
uint index = 1;
constructor(){
owner = msg.sender;
_kittyContract.isApprovedForAll(this, _kittyContract);
}
function setKittyContract(address _kittyContractAddress) external override{
require(msg.sender == owner, "Must be owner");
_kittyContract = _kittyContractAddress;
}
function getOffer(uint256 _tokenId) external view override returns ( address seller, uint256 price, uint256 _index, uint256 tokenId, bool active){
Offer memory offer = idToOffer[_tokenId];
require(offer.active != false, "This offer is not active");
return (offer.seller, offer.price, offer.index, offer.tokenId, offer.active);
}
function getAllTokenOnSale() external view override returns(uint256[] memory listOfOffers){
return idsKittiesForSale;
}
function setOffer(uint256 _price, uint256 _tokenId) external override{
require(msg.sender==_kittyContract.ownerOf(_tokenId), "Must own the token to sell it");
require(idToOffer[_tokenId].active == false, "Only one can be active");
// Requirement: Marketplace contract (this) needs to be an approved operator when the offer is created.
require();
idToOffer[_tokenId] = Offer(msg.sender,_price, index,_tokenId, true);
idsKittiesForSale.push(_tokenId);
emit MarketTransaction("Create offer", msg.sender, _tokenId);
index++;
}
function removeOffer(uint256 _tokenId) external override{
require(msg.sender==idToOffer[_tokenId].seller, "Must own the token to sell it");
idToOffer[_tokenId]=0;
emit MarketTransaction("Remove offer", msg.sender, _tokenId);
}
function buyKitty(uint256 _tokenId) external payable override{
require(msg.value == idToOffer[_tokenId].price, "value must match price");
require(idToOffer[_tokenId].active != false, "Must be an active offer");
idToOffer[_tokenId]=Offer(msg.sender,msg.value, idToOffer[_tokenId].index, _tokenId, false);
_kittyContract.transferFrom(idToOffer[_tokenId].seller, msg.sender, _tokenId);
idToOffer[_tokenId].seller.transfer[msg.value];
}
}
Now letās check Filipās solution ā¦
Codes here:
Marketplace.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import ā./KittyContract.solā;
import āā¦/node_modules/@openzeppelin/contracts/utils/math/SafeMath.solā;
import āā¦/node_modules/@openzeppelin/contracts/access/Ownable.solā;
import ā./IKittyMarketplace.solā;
contract Marketplace is Ownable, IKittyMarketPlace {
Kittycontract private _kittyContract;
struct Offer {
address payable seller;
uint256 price;
uint256 index;
uint256 tokenId;
bool active;
}
Offer[] offers;
mapping(uint256 => Offer) kittyForSale;
function setKittyContract(address _kittyContractAddress)
external
override
onlyOwner
{
_kittyContract = Kittycontract(_kittyContractAddress);
}
function getOffer(uint256 _tokenId)
external
view
override
returns (
address seller,
uint256 price,
uint256 index,
uint256 tokenId,
bool active
)
{
require(
kittyForSale[_tokenId].active == true,
"Kitty is not for Sale!"
);
seller = kittyForSale[_tokenId].seller;
price = kittyForSale[_tokenId].price;
index = kittyForSale[_tokenId].index;
tokenId = kittyForSale[_tokenId].tokenId;
active = kittyForSale[_tokenId].active;
}
function getAllTokenOnSale()
external
view
override
returns (uint256[] memory listOfOffers)
{
uint256 i;
for (i = 0; i < offers.length; i++) {
if (kittyForSale[i].active == true) {
listOfOffers[i] += kittyForSale[i].tokenId;
}
}
return listOfOffers;
}
function createOffer(uint256 _price, uint256 _tokenId) external override {
require(
msg.sender == _kittyContract.ownerOf(_tokenId),
"You do not own this Kitty"
);
require(
kittyForSale[_tokenId].active == false,
"Cannot offer same Kitty twice"
);
require(
address(this) == _kittyContract.getApproved(_tokenId),
"This contract is not an approved operator"
);
_createOffer(msg.sender, _price, _tokenId);
}
function removeOffer(uint256 _tokenId) external override {
require(
msg.sender == offers[_tokenId].seller,
"Only seller can remove offer"
);
require(kittyForSale[_tokenId].active == true, "Offer not found");
_removeOffer(msg.sender, _tokenId);
}
function buyKitty(uint256 _tokenId) external payable override {
require(
msg.sender != kittyForSale[_tokenId].seller,
"You own this Kitty"
);
require(kittyForSale[_tokenId].active == true, "Kitty has no offers");
require(
msg.value == kittyForSale[_tokenId].price,
"Insufficient amount"
);
_kittyContract.safeTransferFrom(
kittyForSale[_tokenId].seller,
msg.sender,
_tokenId,
""
);
_removeOffer(kittyForSale[_tokenId].seller, _tokenId);
emit MarketTransaction("Buy", kittyForSale[_tokenId].seller, _tokenId);
}
function _createOffer(
address _seller,
uint256 _price,
uint256 _tokenId
) internal {
//based from _createKitty in KittyContract.sol
//index is offers.length because _offer is not yet added in the array
Offer memory _offer = Offer({
seller: payable(_seller),
price: _price,
index: offers.length,
tokenId: _tokenId,
active: true
});
offers.push(_offer);
kittyForSale[_tokenId] = _offer;
emit MarketTransaction("Create offer", _seller, _tokenId);
}
function _removeOffer(address _seller, uint256 _tokenId) internal {
uint256 offerToRemove = kittyForSale[_tokenId].index;
if (offers.length - 1 > 0) {
offers[offerToRemove] = offers[offers.length - 1];
offers[offerToRemove].index = offerToRemove;
kittyForSale[offers[offerToRemove].index] = offers[offerToRemove];
}
offers.pop();
delete kittyForSale[_tokenId];
emit MarketTransaction("Remove offer", _seller, _tokenId);
}
}
Itās been too long since I started this course (need to refresh on previous topics)
Quick question: Filip used this code in the solution but when I tried it, it has errors and does not allow me to compile my Kittycontract.sol
file - the error is on uint256(-1)
constructor() public {
_createKitty(0, 0, 0, uint256(-1), address(0));
}