-
Notifications
You must be signed in to change notification settings - Fork 0
/
NFT-Marketplace
260 lines (218 loc) · 9.54 KB
/
NFT-Marketplace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./NFT.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract Marketplace is ReentrancyGuard {
using Counters for Counters.Counter;
Counters.Counter private _marketItemIds;
Counters.Counter private _tokensSold;
Counters.Counter private _tokensCanceled;
address payable private owner;
// Challenge: make this price dynamic according to the current currency price
uint256 private listingFee = 0.045 ether;
mapping(uint256 => MarketItem) private marketItemIdToMarketItem;
struct MarketItem {
uint256 marketItemId;
address nftContractAddress;
uint256 tokenId;
address payable creator;
address payable seller;
address payable owner;
uint256 price;
bool sold;
bool canceled;
}
event MarketItemCreated(
uint256 indexed marketItemId,
address indexed nftContract,
uint256 indexed tokenId,
address creator,
address seller,
address owner,
uint256 price,
bool sold,
bool canceled
);
constructor() {
owner = payable(msg.sender);
}
function getListingFee() public view returns (uint256) {
return listingFee;
}
/**
* @dev Creates a market item listing, requiring a listing fee and transfering the NFT token from
* msg.sender to the marketplace contract.
*/
function createMarketItem(
address nftContractAddress,
uint256 tokenId,
uint256 price
) public payable nonReentrant returns (uint256) {
require(price > 0, "Price must be at least 1 wei");
require(msg.value == listingFee, "Price must be equal to listing price");
_marketItemIds.increment();
uint256 marketItemId = _marketItemIds.current();
address creator = NFT(nftContractAddress).getTokenCreatorById(tokenId);
marketItemIdToMarketItem[marketItemId] = MarketItem(
marketItemId,
nftContractAddress,
tokenId,
payable(creator),
payable(msg.sender),
payable(address(0)),
price,
false,
false
);
IERC721(nftContractAddress).transferFrom(msg.sender, address(this), tokenId);
emit MarketItemCreated(
marketItemId,
nftContractAddress,
tokenId,
payable(creator),
payable(msg.sender),
payable(address(0)),
price,
false,
false
);
return marketItemId;
}
/**
* @dev Cancel a market item
*/
function cancelMarketItem(address nftContractAddress, uint256 marketItemId) public payable nonReentrant {
uint256 tokenId = marketItemIdToMarketItem[marketItemId].tokenId;
require(tokenId > 0, "Market item has to exist");
require(marketItemIdToMarketItem[marketItemId].seller == msg.sender, "You are not the seller");
IERC721(nftContractAddress).transferFrom(address(this), msg.sender, tokenId);
marketItemIdToMarketItem[marketItemId].owner = payable(msg.sender);
marketItemIdToMarketItem[marketItemId].canceled = true;
_tokensCanceled.increment();
}
/**
* @dev Get Latest Market Item by the token id
*/
function getLatestMarketItemByTokenId(uint256 tokenId) public view returns (MarketItem memory, bool) {
uint256 itemsCount = _marketItemIds.current();
for (uint256 i = itemsCount; i > 0; i--) {
MarketItem memory item = marketItemIdToMarketItem[i];
if (item.tokenId != tokenId) continue;
return (item, true);
}
// What is the best practice for returning a "null" value in solidity?
// Reverting does't seem to be the best approach as it would throw an error on frontend
MarketItem memory emptyMarketItem;
return (emptyMarketItem, false);
}
/**
* @dev Creates a market sale by transfering msg.sender money to the seller and NFT token from the
* marketplace to the msg.sender. It also sends the listingFee to the marketplace owner.
*/
function createMarketSale(address nftContractAddress, uint256 marketItemId) public payable nonReentrant {
uint256 price = marketItemIdToMarketItem[marketItemId].price;
uint256 tokenId = marketItemIdToMarketItem[marketItemId].tokenId;
require(msg.value == price, "Please submit the asking price in order to continue");
marketItemIdToMarketItem[marketItemId].owner = payable(msg.sender);
marketItemIdToMarketItem[marketItemId].sold = true;
marketItemIdToMarketItem[marketItemId].seller.transfer(msg.value);
IERC721(nftContractAddress).transferFrom(address(this), msg.sender, tokenId);
_tokensSold.increment();
payable(owner).transfer(listingFee);
}
/**
* @dev Fetch non sold and non canceled market items
*/
function fetchAvailableMarketItems() public view returns (MarketItem[] memory) {
uint256 itemsCount = _marketItemIds.current();
uint256 soldItemsCount = _tokensSold.current();
uint256 canceledItemsCount = _tokensCanceled.current();
uint256 availableItemsCount = itemsCount - soldItemsCount - canceledItemsCount;
MarketItem[] memory marketItems = new MarketItem[](availableItemsCount);
uint256 currentIndex = 0;
for (uint256 i = 0; i < itemsCount; i++) {
// Is this refactor better than the original implementation?
// https://github.com/dabit3/polygon-ethereum-nextjs-marketplace/blob/main/contracts/Market.sol#L111
// If so, is it better to use memory or storage here?
MarketItem memory item = marketItemIdToMarketItem[i + 1];
if (item.owner != address(0)) continue;
marketItems[currentIndex] = item;
currentIndex += 1;
}
return marketItems;
}
/**
* @dev This seems to be the best way to compare strings in Solidity
*/
function compareStrings(string memory a, string memory b) private pure returns (bool) {
return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
}
/**
* @dev Since we can't access structs properties dinamically, this function selects the address
* we're looking for between "owner" and "seller"
*/
function getMarketItemAddressByProperty(MarketItem memory item, string memory property)
private
pure
returns (address)
{
require(
compareStrings(property, "seller") || compareStrings(property, "owner"),
"Parameter must be 'seller' or 'owner'"
);
return compareStrings(property, "seller") ? item.seller : item.owner;
}
/**
* @dev Fetch market items that are being listed by the msg.sender
*/
function fetchSellingMarketItems() public view returns (MarketItem[] memory) {
return fetchMarketItemsByAddressProperty("seller");
}
/**
* @dev Fetch market items that are owned by the msg.sender
*/
function fetchOwnedMarketItems() public view returns (MarketItem[] memory) {
return fetchMarketItemsByAddressProperty("owner");
}
/**
* @dev Fetches market items according to the its requested address property that
* can be "owner" or "seller". The original implementations were two functions that were
* almost the same, changing only a property access. This refactored version requires an
* addional auxiliary function, but avoids repeating code.
* See original: https://github.com/dabit3/polygon-ethereum-nextjs-marketplace/blob/main/contracts/Market.sol#L121
*/
function fetchMarketItemsByAddressProperty(string memory _addressProperty)
public
view
returns (MarketItem[] memory)
{
require(
compareStrings(_addressProperty, "seller") || compareStrings(_addressProperty, "owner"),
"Parameter must be 'seller' or 'owner'"
);
uint256 totalItemsCount = _marketItemIds.current();
uint256 itemCount = 0;
uint256 currentIndex = 0;
for (uint256 i = 0; i < totalItemsCount; i++) {
// Is it ok to assign this variable for better code legbility?
// Is it better to use memory or storage in this case?
MarketItem storage item = marketItemIdToMarketItem[i + 1];
address addressPropertyValue = getMarketItemAddressByProperty(item, _addressProperty);
if (addressPropertyValue != msg.sender) continue;
itemCount += 1;
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint256 i = 0; i < totalItemsCount; i++) {
// Is it ok to assign this variable for better code legbility?
// Is it better to use memory or storage in this case?
MarketItem storage item = marketItemIdToMarketItem[i + 1];
address addressPropertyValue = getMarketItemAddressByProperty(item, _addressProperty);
if (addressPropertyValue != msg.sender) continue;
items[currentIndex] = item;
currentIndex += 1;
}
return items;
}
}