forked from Dexaran/CallistoNFT
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCallistoNFT.sol
428 lines (335 loc) · 15.2 KB
/
CallistoNFT.sol
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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
// SPDX-License-Identifier: GPL
pragma solidity ^0.8.16;
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* This test is non-exhaustive, and there may be false-negatives: during the
* execution of a contract's constructor, its address will be reported as
* not containing a contract.
*
* > It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*/
function isContract(address account) internal view returns (bool) {
// This method relies in extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
interface ICallistoNFT {
event NewBid (uint256 indexed tokenID, uint256 indexed bidAmount, bytes data);
event NewPrice (uint256 indexed tokenID, uint256 indexed priceValue);
event TokenTrade (uint256 indexed tokenID, address indexed new_owner, address indexed previous_owner, uint256 priceInWEI);
event Transfer (address indexed from, address indexed to, uint256 indexed tokenId);
event TransferData (bytes data);
struct Properties {
// In this example properties of the given NFT are stored
// in a dynamically sized array of strings
// properties can be re-defined for any specific info
// that a particular NFT is intended to store.
/* Properties could look like this:
bytes property1;
bytes property2;
address property3;
*/
string[] properties;
}
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function standard() external view returns (string memory);
function balanceOf(address _who) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function transfer(address _to, uint256 _tokenId, bytes calldata _data) external returns (bool);
function silentTransfer(address _to, uint256 _tokenId) external returns (bool);
function priceOf(uint256 _tokenId) external view returns (uint256);
function bidOf(uint256 _tokenId) external view returns (uint256 price, address payable bidder, uint256 timestamp);
function getTokenProperties(uint256 _tokenId) external view returns (Properties memory);
function getTokenProperty(uint256 _tokenId, uint256 _propertyId) external view returns (string memory);
function setBid(uint256 _tokenId, bytes calldata _data) payable external; // bid amount is defined by msg.value
function setPrice(uint256 _tokenId, uint256 _amountInWEI) external;
function withdrawBid(uint256 _tokenId) external returns (bool);
function getUserContent(uint256 _tokenId) external view returns (string memory _content, bool _all);
function setUserContent(uint256 _tokenId, string calldata _content) external returns (bool);
}
abstract contract NFTReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external virtual returns(bytes4);
}
abstract contract CallistoNFT is ICallistoNFT {
using Address for address;
event TokenPropertyUpdated(uint tokenID, uint propertyID);
mapping (uint256 => Properties) internal _tokenProperties;
mapping (uint32 => Fee) public feeLevels; // level # => (fee receiver, fee percentage)
uint256 public bidLock = 1 days; // Time required for a bid to become withdrawable.
struct Bid {
address payable bidder;
uint256 amountInWEI;
uint256 timestamp;
}
struct Fee {
address payable feeReceiver;
uint256 feePercentage; // Will be divided by 100000 during calculations
// feePercentage of 100 means 0.1% fee
// feePercentage of 2500 means 2.5% fee
}
mapping (uint256 => uint256) internal _asks; // tokenID => price of this token (in WEI)
mapping (uint256 => Bid) internal _bids; // tokenID => price of this token (in WEI)
mapping (uint256 => uint32) internal _tokenFeeLevels; // tokenID => level ID / 0 by default
// Token name
string internal _name;
// Token symbol
string internal _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) internal _owners;
// Mapping owner address to token count
mapping(address => uint256) internal _balances;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_, uint256 _defaultFee) {
_name = name_;
_symbol = symbol_;
feeLevels[0].feeReceiver = payable(msg.sender);
feeLevels[0].feePercentage = _defaultFee;
}
// Reward is always paid based on BID
modifier checkTrade(uint256 _tokenId)
{
_;
(uint256 _bid, address payable _bidder,) = bidOf(_tokenId);
if(priceOf(_tokenId) > 0 && priceOf(_tokenId) <= _bid)
{
uint256 _reward = _bid - _claimFee(_bid, _tokenId);
emit TokenTrade(_tokenId, _bidder, ownerOf(_tokenId), _reward);
payable(ownerOf(_tokenId)).transfer(_reward);
bytes memory _empty;
delete _bids[_tokenId];
delete _asks[_tokenId];
_transfer(ownerOf(_tokenId), _bidder, _tokenId, _empty );
}
}
function standard() public pure override returns (string memory)
{
return "CallistoNFT";
}
function priceOf(uint256 _tokenId) public view override returns (uint256)
{
address owner = _owners[_tokenId];
require(owner != address(0), "NFT: owner query for nonexistent token");
return _asks[_tokenId];
}
function bidOf(uint256 _tokenId) public view override returns (uint256 price, address payable bidder, uint256 timestamp)
{
address owner = _owners[_tokenId];
require(owner != address(0), "NFT: owner query for nonexistent token");
return (_bids[_tokenId].amountInWEI, _bids[_tokenId].bidder, _bids[_tokenId].timestamp);
}
function getTokenProperties(uint256 _tokenId) public view override returns (Properties memory)
{
return _tokenProperties[_tokenId];
}
function getTokenProperty(uint256 _tokenId, uint256 _propertyId) public view override returns (string memory)
{
return _tokenProperties[_tokenId].properties[_propertyId];
}
function getUserContent(uint256 _tokenId) public view override returns (string memory _content, bool _all)
{
return (_tokenProperties[_tokenId].properties[0], true);
}
function setUserContent(uint256 _tokenId, string calldata _content) public override returns (bool success)
{
require(msg.sender == ownerOf(_tokenId), "NFT: only owner can change NFT content");
_tokenProperties[_tokenId].properties[0] = _content;
emit TokenPropertyUpdated(_tokenId, 0) ;
return true;
}
function balanceOf(address owner) public view override returns (uint256) {
require(owner != address(0), "NFT: balance query for the zero address");
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "NFT: owner query for nonexistent token");
return owner;
}
function setPrice(uint256 _tokenId, uint256 _amountInWEI) checkTrade(_tokenId) public override {
require(ownerOf(_tokenId) == msg.sender, "Setting asks is only allowed for owned NFTs!");
_asks[_tokenId] = _amountInWEI;
emit NewPrice(_tokenId, _amountInWEI);
}
function setBid(uint256 _tokenId, bytes calldata _data) payable checkTrade(_tokenId) public override
{
(uint256 _previousBid, address payable _previousBidder, ) = bidOf(_tokenId);
require(msg.value > _previousBid, "New bid must exceed the existing one");
uint256 _bid;
// Return previous bid if the current one exceeds it.
if(_previousBid != 0)
{
_previousBidder.transfer(_previousBid);
}
// Refund overpaid amount.
if (priceOf(_tokenId) < msg.value)
{
_bid = priceOf(_tokenId);
}
else
{
_bid = msg.value;
}
_bids[_tokenId].amountInWEI = _bid;
_bids[_tokenId].bidder = payable(msg.sender);
_bids[_tokenId].timestamp = block.timestamp;
emit NewBid(_tokenId, _bid, _data);
// Send back overpaid amount.
// WARHNING: Creates possibility for reentrancy.
if (priceOf(_tokenId) < msg.value)
{
payable(msg.sender).transfer(msg.value - priceOf(_tokenId));
}
}
function withdrawBid(uint256 _tokenId) public override returns (bool)
{
(uint256 _bid, address payable _bidder, uint256 _timestamp) = bidOf(_tokenId);
require(msg.sender == _bidder, "Can not withdraw someone elses bid");
require(block.timestamp > _timestamp + bidLock, "Bid is time-locked");
_bidder.transfer(_bid);
delete _bids[_tokenId];
emit NewBid(_tokenId, 0, "0x7769746864726177426964");
return true;
}
function name() public view override returns (string memory) {
return _name;
}
function symbol() public view override returns (string memory) {
return _symbol;
}
function transfer(address _to, uint256 _tokenId, bytes memory _data) public override returns (bool)
{
_transfer(msg.sender, _to, _tokenId, _data);
emit TransferData(_data);
return true;
}
function silentTransfer(address _to, uint256 _tokenId) public override returns (bool)
{
require(CallistoNFT.ownerOf(_tokenId) == msg.sender, "NFT: transfer of token that is not own");
require(_to != address(0), "NFT: transfer to the zero address");
_asks[_tokenId] = 0; // Zero out price on transfer
// When a user transfers the NFT to another user
// it does not automatically mean that the new owner
// would like to sell this NFT at a price
// specified by the previous owner.
// However bids persist regardless of token transfers
// because we assume that the bidder still wants to buy the NFT
// no matter from whom.
_beforeTokenTransfer(msg.sender, _to, _tokenId);
_balances[msg.sender] -= 1;
_balances[_to] += 1;
_owners[_tokenId] = _to;
emit Transfer(msg.sender, _to, _tokenId);
return true;
}
function setTokenProperty(uint256 _tokenId, uint256 _propertyId, string calldata _content) public returns (bool success)
{
// Access restriction rules must be implemented here.
// The implementation depends on the requirements of the tokenomics.
// In most cases an "owner" of the contract or NFT class will have a permission to configure its properties.
_tokenProperties[_tokenId].properties[_propertyId] = _content;
emit TokenPropertyUpdated(_tokenId, _propertyId);
return true;
}
function addTokenProperty(uint256 _tokenId, string calldata _content) internal
{
// Access restriction rules must be implemented here.
// The implementation depends on the requirements of the tokenomics.
// In most cases an "owner" of the contract or NFT class will have a permission to add new property slots.
_tokenProperties[_tokenId].properties.push(_content);
uint newPropertyID = _tokenProperties[_tokenId].properties.length - 1;
emit TokenPropertyUpdated(_tokenId, newPropertyID);
}
function _exists(uint256 _tokenId) internal view returns (bool) {
return _owners[_tokenId] != address(0);
}
function _claimFee(uint256 _amountFrom, uint256 _tokenId) internal returns (uint256)
{
uint32 _level = _tokenFeeLevels[_tokenId];
address _feeReceiver = feeLevels[_level].feeReceiver;
uint256 _feePercentage = feeLevels[_level].feePercentage;
uint256 _feeAmount = _amountFrom * _feePercentage / 100000;
payable(_feeReceiver).transfer(_feeAmount);
return _feeAmount;
}
function _safeMint(
address to,
uint256 tokenId
) internal virtual {
_mint(to, tokenId);
}
function configureNFT(uint256 tokenId) internal
{
if(_tokenProperties[tokenId].properties.length == 0)
{
_tokenProperties[tokenId].properties.push("");
}
}
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "NFT: mint to the zero address");
require(!_exists(tokenId), "NFT: token already minted");
configureNFT(tokenId);
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
function _mint(address to, uint256 tokenId, uint32 feeLevel) internal virtual {
require(to != address(0), "NFT: mint to the zero address");
require(!_exists(tokenId), "NFT: token already minted");
configureNFT(tokenId);
_tokenFeeLevels[tokenId] = feeLevel;
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
function _burn(uint256 tokenId) internal {
address owner = CallistoNFT.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
_balances[owner] -= 1;
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
}
function _transfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal {
require(CallistoNFT.ownerOf(tokenId) == from, "NFT: transfer of token that is not own");
require(to != address(0), "NFT: transfer to the zero address");
_asks[tokenId] = 0; // Zero out price on transfer
// When a user transfers the NFT to another user
// it does not automatically mean that the new owner
// would like to sell this NFT at a price
// specified by the previous owner.
// However bids persist regardless of token transfers
// because we assume that the bidder still wants to buy the NFT
// no matter from whom.
_beforeTokenTransfer(from, to, tokenId);
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
if(to.isContract())
{
NFTReceiver(to).onERC721Received(msg.sender, from, tokenId, data);
}
emit Transfer(from, to, tokenId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
}