-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathMintNFT.sol
495 lines (440 loc) · 16.1 KB
/
MintNFT.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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/metatx/MinimalForwarderUpgradeable.sol";
import "./lib/Hashing.sol";
import "./ERC2771ContextUpgradeable.sol";
import "./IEvent.sol";
import "./ISecretPhraseVerifier.sol";
import "./IOperationController.sol";
contract MintNFT is
ERC721EnumerableUpgradeable,
ERC2771ContextUpgradeable,
OwnableUpgradeable
{
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address private eventManagerAddr;
function setEventManagerAddr(address _addr) public onlyOwner {
require(_addr != address(0), "event manager address is blank");
eventManagerAddr = _addr;
}
struct NFTAttribute {
string metaDataURL;
uint256 requiredParticipateCount;
}
struct NFTHolder {
address holderAddress;
uint256 tokenId;
}
struct NFTHolderWithEventId {
address holderAddress;
uint256 eventId;
uint256 tokenId;
}
struct TokenUriWithIds {
uint256 eventId;
uint256 tokenId;
string tokenUri;
}
// NFT meta data url via tokenId
mapping(uint256 => string) private nftMetaDataURL;
// Holding NFT via hash of eventId and address
mapping(bytes32 => bool) private isHoldingEventNFT;
// Participate count via hash of groupId and address hash
mapping(bytes32 => uint256) private countOfParticipation;
// NFT attribute location (ex. ipfs, centralized storage) via hash of participateCount, eventId
mapping(bytes32 => string) private eventNftAttributes;
// remaining mint count of Event
mapping(uint256 => uint256) private remainingEventNftCount;
// secretPhrase via EventId
mapping(uint256 => bytes32) private eventSecretPhrases;
// is mint locked via EventId
mapping(uint256 => bool) private isMintLocked;
address private secretPhraseVerifierAddr;
// Create a mapping to store NFT holders by event ID
mapping(uint256 => uint256[]) private tokenIdsByEvent;
address private operationControllerAddr;
// Create a mapping to store event ID by token ID
mapping(uint256 => uint256) private eventIdOfTokenId;
// is non transferable via EventId
mapping(uint256 => bool) private isNonTransferable;
event MintedNFTAttributeURL(address indexed holder, string url);
event MintLocked(uint256 indexed eventId, bool isLocked);
event ResetSecretPhrase(address indexed executor, uint256 indexed eventId);
event NonTransferable(uint256 indexed eventId, bool isNonTransferable);
event DroppedNFTs(address indexed executor, uint256 indexed eventId);
modifier onlyCollaboratorAccess(uint256 _eventId) {
IEventManager eventManager = IEventManager(eventManagerAddr);
require(
eventManager.hasCollaboratorAccessByEventId(_msgSender(), _eventId),
"you have no permission"
);
_;
}
modifier whenNotPaused() {
IOperationController operationController = IOperationController(
operationControllerAddr
);
require(!operationController.paused());
_;
}
// Currently, reinitializer(5) was executed as constructor.
function initialize(
address _owner,
MinimalForwarderUpgradeable _trustedForwarder,
address _secretPhraseVerifierAddr,
address _operationControllerAddr
) public reinitializer(5) {
__ERC721_init("MintRally", "MR");
__Ownable_init();
_transferOwnership(_owner);
__ERC2771Context_init(address(_trustedForwarder));
secretPhraseVerifierAddr = _secretPhraseVerifierAddr;
operationControllerAddr = _operationControllerAddr;
}
function _msgSender()
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (address sender)
{
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
/// @solidity memory-safe-assembly
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return super._msgSender();
}
}
function _msgData()
internal
view
virtual
override(ContextUpgradeable, ERC2771ContextUpgradeable)
returns (bytes calldata)
{
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return super._msgData();
}
}
function mintParticipateNFT(
uint256 _groupId,
uint256 _eventId,
uint256[24] memory _proof
) external whenNotPaused {
canMint(_eventId, _proof);
ISecretPhraseVerifier secretPhraseVerifier = ISecretPhraseVerifier(
secretPhraseVerifierAddr
);
secretPhraseVerifier.submitProof(_proof, _eventId);
_mintNFT(_groupId, _eventId, _msgSender());
}
function dropNFTs(
uint256 _eventId,
address[] memory _addresses
) external onlyCollaboratorAccess(_eventId) whenNotPaused {
uint256 groupId = getGroupIdByEvent(_eventId);
require(
remainingEventNftCount[_eventId] >= _addresses.length,
"remaining count is not enough"
);
for (uint256 index = 0; index < _addresses.length; index++) {
address addr = _addresses[index];
if (!isHoldingEventNFTByAddress(addr, _eventId)) {
_mintNFT(groupId, _eventId, addr);
}
}
emit DroppedNFTs(_msgSender(), _eventId);
}
function _mintNFT(
uint256 _groupId,
uint256 _eventId,
address _address
) internal {
remainingEventNftCount[_eventId] = remainingEventNftCount[_eventId] - 1;
isHoldingEventNFT[
Hashing.hashingAddressUint256(_address, _eventId)
] = true;
bytes32 groupHash = Hashing.hashingAddressUint256(_address, _groupId);
uint256 participationCount = countOfParticipation[groupHash];
countOfParticipation[groupHash] = participationCount + 1;
string memory metaDataURL = eventNftAttributes[
Hashing.hashingDoubleUint256(_eventId, 0)
];
string memory specialMetaDataURL = eventNftAttributes[
Hashing.hashingDoubleUint256(_eventId, participationCount)
];
if (
keccak256(abi.encodePacked(specialMetaDataURL)) !=
keccak256(abi.encodePacked(""))
) {
metaDataURL = specialMetaDataURL;
}
uint256 tokenId = _tokenIds.current();
nftMetaDataURL[tokenId] = metaDataURL;
tokenIdsByEvent[_eventId].push(tokenId);
eventIdOfTokenId[tokenId] = _eventId;
_tokenIds.increment();
_safeMint(_address, tokenId);
emit MintedNFTAttributeURL(_address, metaDataURL);
}
function canMint(
uint256 _eventId,
uint256[24] memory _proof
) public view returns (bool) {
require(verifySecretPhrase(_proof, _eventId), "invalid secret phrase");
require(
remainingEventNftCount[_eventId] != 0,
"remaining count is zero"
);
require(
!isHoldingEventNFTByAddress(_msgSender(), _eventId),
"already minted"
);
require(!isMintLocked[_eventId], "mint is locked");
return true;
}
function changeMintLocked(
uint256 _eventId,
bool _locked
) external onlyCollaboratorAccess(_eventId) whenNotPaused {
isMintLocked[_eventId] = _locked;
emit MintLocked(_eventId, _locked);
}
function changeNonTransferable(
uint256 _eventId,
bool _isNonTransferable
) external whenNotPaused {
IEventManager eventManager = IEventManager(eventManagerAddr);
require(
_msgSender() == eventManagerAddr ||
eventManager.hasAdminAccessByEventId(_msgSender(), _eventId),
"you have no permission"
);
isNonTransferable[_eventId] = _isNonTransferable;
emit NonTransferable(_eventId, _isNonTransferable);
}
function resetSecretPhrase(
uint256 _eventId,
bytes32 _secretPhrase
) external onlyCollaboratorAccess(_eventId) whenNotPaused {
eventSecretPhrases[_eventId] = _secretPhrase;
emit ResetSecretPhrase(_msgSender(), _eventId);
}
function getIsMintLocked(uint256 _eventId) external view returns (bool) {
return isMintLocked[_eventId];
}
function getIsNonTransferable(
uint256 _eventId
) external view returns (bool) {
return isNonTransferable[_eventId];
}
function isHoldingEventNFTByAddress(
address _addr,
uint256 _eventId
) public view returns (bool) {
return
isHoldingEventNFT[Hashing.hashingAddressUint256(_addr, _eventId)];
}
function getCountOfParticipation(
uint256 _groupId,
address _address
) public view returns (uint256) {
bytes32 groupHash = Hashing.hashingAddressUint256(_address, _groupId);
return countOfParticipation[groupHash];
}
function setEventInfo(
uint256 _eventId,
uint256 _mintLimit,
bytes32 _secretPhrase,
NFTAttribute[] memory attributes
) external whenNotPaused {
require(_msgSender() == eventManagerAddr, "unauthorized");
remainingEventNftCount[_eventId] = _mintLimit;
eventSecretPhrases[_eventId] = _secretPhrase;
for (uint256 index = 0; index < attributes.length; index++) {
eventNftAttributes[
Hashing.hashingDoubleUint256(
_eventId,
attributes[index].requiredParticipateCount
)
] = attributes[index].metaDataURL;
}
}
function getRemainingNFTCount(
uint256 _eventId
) external view returns (uint256) {
return remainingEventNftCount[_eventId];
}
function getNFTAttributeRecordsByEventId(
uint256 _eventId,
uint256 _limit,
uint256 _offset
) external view returns (NFTAttribute[] memory) {
if (_limit == 0) {
_limit = 100; // default limit
}
require(_limit <= 100, "limit is too large");
// create array of nft attributes
NFTAttribute[] memory _nftAttributeRecordsList = new NFTAttribute[](
_limit
);
require(
_offset <= type(uint256).max - _limit,
"limit + offset must be <= 2^256 - 1"
);
uint256 limitWithOffset = _limit + _offset;
uint256 count;
for (uint256 i = _offset; i < limitWithOffset; i++) {
string memory ipfsUrl = eventNftAttributes[
Hashing.hashingDoubleUint256(_eventId, i)
];
if (
keccak256(abi.encodePacked(ipfsUrl)) !=
keccak256(abi.encodePacked(""))
) {
_nftAttributeRecordsList[count] = NFTAttribute(ipfsUrl, i);
count++;
}
}
NFTAttribute[] memory _nftAttributeRecords = new NFTAttribute[](count);
for (uint256 j = 0; j < count; j++) {
_nftAttributeRecords[j] = _nftAttributeRecordsList[j];
}
return _nftAttributeRecords;
}
function burn(uint256 tokenId) public onlyOwner {
_burn(tokenId);
}
function tokenURI(
uint256 _tokenId
) public view override returns (string memory) {
string memory metaDataURL = nftMetaDataURL[_tokenId];
return metaDataURL;
}
function verifySecretPhrase(
uint256[24] memory _proof,
uint256 _eventId
) internal view returns (bool) {
ISecretPhraseVerifier secretPhraseVerifier = ISecretPhraseVerifier(
secretPhraseVerifierAddr
);
uint256[1] memory publicInput = [uint256(eventSecretPhrases[_eventId])];
bool result = secretPhraseVerifier.verifyProof(
_proof,
publicInput,
_eventId
);
return result;
}
// Function to return a list of owners from an array of token IDs
function ownerOfTokens(
uint256[] memory _tokenIdArray
) public view returns (NFTHolder[] memory) {
NFTHolder[] memory holders = new NFTHolder[](_tokenIdArray.length);
for (uint256 index = 0; index < _tokenIdArray.length; index++) {
holders[index] = NFTHolder(
ownerOf(_tokenIdArray[index]),
_tokenIdArray[index]
);
}
return holders;
}
// Function to return a list of NFT holders for a specific event ID
function getNFTHoldersByEvent(
uint256 _eventId
) public view returns (NFTHolder[] memory) {
return ownerOfTokens(tokenIdsByEvent[_eventId]);
}
// Function to return a list of NFT holders for a specific event group ID
function getNFTHoldersByEventGroup(
uint256 _groupId
) public view returns (NFTHolderWithEventId[] memory) {
IEventManager eventManager = IEventManager(eventManagerAddr);
EventRecord[] memory eventIds = eventManager.getEventRecordsByGroupId(
_groupId
);
NFTHolder[][] memory tempHolders = new NFTHolder[][](eventIds.length);
uint256 tokenidsLength = 0;
for (uint256 index = 0; index < eventIds.length; index++) {
tempHolders[index] = getNFTHoldersByEvent(
eventIds[index].eventRecordId
);
tokenidsLength = tokenidsLength + tempHolders[index].length;
}
NFTHolderWithEventId[] memory holders = new NFTHolderWithEventId[](
tokenidsLength
);
uint256 counter = 0;
while (counter < tokenidsLength) {
for (uint256 index = 0; index < eventIds.length; index++) {
for (
uint256 index2 = 0;
index2 < tempHolders[index].length;
index2++
) {
holders[counter] = NFTHolderWithEventId(
tempHolders[index][index2].holderAddress,
eventIds[index].eventRecordId,
tempHolders[index][index2].tokenId
);
counter++;
}
}
}
return holders;
}
function getTokenIdsByEvent(
uint256 eventId
) external view returns (uint256[] memory) {
return tokenIdsByEvent[eventId];
}
function getEventIdOfTokenId(
uint256 _tokenId
) public view returns (uint256) {
return eventIdOfTokenId[_tokenId];
}
function getGroupIdByEvent(uint256 _eventId) public view returns (uint256) {
IEventManager eventManager = IEventManager(eventManagerAddr);
EventRecord memory _event = eventManager.getEventById(_eventId);
return _event.groupId;
}
function getTokensOfOwner(
address _address
) public view returns (TokenUriWithIds[] memory) {
uint256 balance = balanceOf(_address);
TokenUriWithIds[] memory tokenUrisWithIds = new TokenUriWithIds[](
balance
);
for (uint256 index = 0; index < balance; index++) {
uint256 tokenId = tokenOfOwnerByIndex(_address, index);
uint256 eventId = eventIdOfTokenId[tokenId];
tokenUrisWithIds[index] = TokenUriWithIds(
eventId,
tokenId,
tokenURI(tokenId)
);
}
return tokenUrisWithIds;
}
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
require(
!isNonTransferable[eventIdOfTokenId[tokenId]],
"transfer is locked"
);
super._transfer(from, to, tokenId);
}
}