-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathCUBE.sol
731 lines (652 loc) · 29.5 KB
/
CUBE.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
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
// SPDX-License-Identifier: Apache-2.0
/*
.____ ________
| | _____ ___.__. __________\_____ \
| | \__ \< | |/ __ \_ __ \_(__ <
| |___ / __ \\___ \ ___/| | \/ \
|_______ (____ / ____|\___ >__| /______ /
\/ \/\/ \/ \/
*/
pragma solidity 0.8.20;
import {EIP712Upgradeable} from
"@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {ERC721Upgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {AccessControlUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {IFactory} from "./escrow/interfaces/IFactory.sol";
import {ITokenType} from "./escrow/interfaces/ITokenType.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @title CUBE
/// @dev Implementation of an NFT smart contract with EIP712 signatures.
/// The contract is upgradeable using OpenZeppelin's UUPSUpgradeable pattern.
/// @custom:oz-upgrades-from CUBE
contract CUBE is
Initializable,
ERC721Upgradeable,
AccessControlUpgradeable,
UUPSUpgradeable,
EIP712Upgradeable,
ReentrancyGuardUpgradeable,
ITokenType
{
using ECDSA for bytes32;
error CUBE__IsNotSigner();
error CUBE__MintingIsNotActive();
error CUBE__FeeNotEnough();
error CUBE__SignatureAndCubesInputMismatch();
error CUBE__WithdrawFailed();
error CUBE__NonceAlreadyUsed();
error CUBE__TransferFailed();
error CUBE__BPSTooHigh();
error CUBE__ExcessiveFeePayout();
error CUBE__ExceedsContractBalance();
error CUBE__QuestNotActive();
error CUBE__NativePaymentFailed();
error CUBE__ERC20TransferFailed();
error CUBE__ExceedsContractAllowance();
error CUBE__L3TokenNotSet();
error CUBE__TreasuryNotSet();
error CUBE__InvalidAdminAddress();
uint256 internal s_nextTokenId;
bool public s_isMintingActive;
bytes32 public constant SIGNER_ROLE = keccak256("SIGNER");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER");
bytes32 internal constant TX_DATA_HASH =
keccak256("TransactionData(string txHash,string networkChainId)");
bytes32 internal constant RECIPIENT_DATA_HASH =
keccak256("FeeRecipient(address recipient,uint16 BPS)");
bytes32 internal constant REWARD_DATA_HASH = keccak256(
"RewardData(address tokenAddress,uint256 chainId,uint256 amount,uint256 tokenId,uint8 tokenType,uint256 rakeBps,address factoryAddress)"
);
bytes32 internal constant CUBE_DATA_HASH = keccak256(
"CubeData(uint256 questId,uint256 nonce,uint256 price,bool isNative,address toAddress,string walletProvider,string tokenURI,string embedOrigin,TransactionData[] transactions,FeeRecipient[] recipients,RewardData reward)FeeRecipient(address recipient,uint16 BPS)RewardData(address tokenAddress,uint256 chainId,uint256 amount,uint256 tokenId,uint8 tokenType,uint256 rakeBps,address factoryAddress)TransactionData(string txHash,string networkChainId)"
);
mapping(uint256 => uint256) internal s_questIssueNumbers;
mapping(uint256 => string) internal s_tokenURIs;
mapping(uint256 nonce => bool isConsumed) internal s_nonces;
mapping(uint256 => bool) internal s_quests;
address public s_treasury;
address public s_l3Token;
bytes4 private constant TRANSFER_ERC20 =
bytes4(keccak256(bytes("transferFrom(address,address,uint256)")));
enum QuestType {
QUEST,
STREAK
}
enum Difficulty {
BEGINNER,
INTERMEDIATE,
ADVANCED
}
/// @notice Emitted when a new quest is initialized
/// @param questId The unique identifier of the quest
/// @param questType The type of the quest (QUEST, STREAK)
/// @param difficulty The difficulty level of the quest (BEGINNER, INTERMEDIATE, ADVANCED)
/// @param title The title of the quest
/// @param tags An array of tags associated with the quest
/// @param communities An array of communities associated with the quest
event QuestMetadata(
uint256 indexed questId,
QuestType questType,
Difficulty difficulty,
string title,
string[] tags,
string[] communities
);
/// @notice Emitted when a CUBE is claimed
/// @param questId The quest ID associated with the CUBE
/// @param tokenId The token ID of the minted CUBE
/// @param claimer Address of the CUBE claimer
/// @param isNative If the payment was made in native currency
/// @param price The price paid for the CUBE
/// @param issueNumber The issue number of the CUBE
/// @param walletProvider The name of the wallet provider used for claiming
/// @param embedOrigin The origin of the embed associated with the CUBE
event CubeClaim(
uint256 indexed questId,
uint256 indexed tokenId,
address indexed claimer,
bool isNative,
uint256 price,
uint256 issueNumber,
string walletProvider,
string embedOrigin
);
/// @notice Emitted for each transaction associated with a CUBE claim
/// This event is designed to support both EVM and non-EVM blockchains
/// @param cubeTokenId The token ID of the Cube
/// @param txHash The hash of the transaction
/// @param networkChainId The network and chain ID of the transaction in the format <network>:<chain-id>
event CubeTransaction(uint256 indexed cubeTokenId, string txHash, string networkChainId);
/// @notice Emitted when there is a reward associated with a CUBE
/// @param cubeTokenId The token ID of the CUBE giving the reward
/// @param tokenAddress The token address of the reward
/// @param chainId The blockchain chain ID where the transaction occurred
/// @param amount The amount of the reward
/// @param tokenId Token ID of the reward (only applicable for ERC721 and ERC1155)
/// @param tokenType The type of reward token
event TokenReward(
uint256 indexed cubeTokenId,
address indexed tokenAddress,
uint256 indexed chainId,
uint256 amount,
uint256 tokenId,
TokenType tokenType
);
/// @notice Emitted when a fee payout is made
/// @param recipient The address of the payout recipient
/// @param amount The amount of the payout
/// @param isNative If the payout was made in native currency
event FeePayout(address indexed recipient, uint256 amount, bool isNative);
/// @notice Emitted when the minting switch is turned on/off
/// @param isActive The boolean showing if the minting is active or not
event MintingSwitch(bool isActive);
/// @notice Emitted when the contract balance is withdrawn by an admin
/// @param amount The contract's balance that was withdrawn
event ContractWithdrawal(uint256 amount);
/// @notice Emitted when a quest is disabled
/// @param questId The ID of the quest that was disabled
event QuestDisabled(uint256 indexed questId);
/// @notice Emitted when the treasury address is updated
/// @param newTreasury The new treasury address
event UpdatedTreasury(address indexed newTreasury);
/// @notice Emitted when the L3 token address is updated
/// @param token The L3 token address
event UpdatedL3Address(address indexed token);
/// @dev Represents the data needed for minting a CUBE.
/// @param questId The ID of the quest associated with the CUBE
/// @param nonce A unique number to prevent replay attacks
/// @param price The price paid for minting the CUBE
/// @param isNative If the price is paid in native currency or with L3
/// @param toAddress The address where the CUBE will be minted
/// @param walletProvider The wallet provider used for the transaction
/// @param tokenURI The URI pointing to the CUBE's metadata
/// @param embedOrigin The origin source of the CUBE's embed content
/// @param transactions An array of transactions related to the CUBE
/// @param recipients An array of recipients for fee payouts
/// @param reward Data about the reward associated with the CUBE
struct CubeData {
uint256 questId;
uint256 nonce;
uint256 price;
bool isNative;
address toAddress;
string walletProvider;
string tokenURI;
string embedOrigin;
TransactionData[] transactions;
FeeRecipient[] recipients;
RewardData reward;
}
/// @dev Represents a recipient for fee distribution.
/// @param recipient The address of the fee recipient
/// @param BPS The basis points representing the fee percentage for the recipient
struct FeeRecipient {
address recipient;
uint16 BPS;
}
/// @dev Contains data about the token rewards associated with a CUBE.
/// @param tokenAddress The token address of the reward
/// @param chainId The blockchain chain ID where the transaction occurred
/// @param amount The amount of the reward
/// @param tokenId The token ID
/// @param tokenType The token type
/// @param rakeBps The rake basis points
/// @param factoryAddress The escrow factory address
struct RewardData {
address tokenAddress;
uint256 chainId;
uint256 amount;
uint256 tokenId;
TokenType tokenType;
uint256 rakeBps;
address factoryAddress;
}
/// @dev Contains data about a specific transaction related to a CUBE
/// and is designed to support both EVM and non-EVM data.
/// @param txHash The hash of the transaction
/// @param networkChainId The network and chain ID of the transaction in the format <network>:<chain-id>
struct TransactionData {
string txHash;
string networkChainId;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Returns the version of the CUBE smart contract
function cubeVersion() external pure returns (string memory) {
return "3";
}
/// @notice Initializes the CUBE contract with necessary parameters
/// @dev Sets up the ERC721 token with given name and symbol, and grants initial roles.
/// @param _tokenName Name of the NFT collection
/// @param _tokenSymbol Symbol of the NFT collection
/// @param _signingDomain Domain used for EIP712 signing
/// @param _signatureVersion Version of the EIP712 signature
/// @param _admin Address to be granted the admin roles
function initialize(
string memory _tokenName,
string memory _tokenSymbol,
string memory _signingDomain,
string memory _signatureVersion,
address _admin
) external initializer {
if (_admin == address(0)) revert CUBE__InvalidAdminAddress();
__ERC721_init(_tokenName, _tokenSymbol);
__EIP712_init(_signingDomain, _signatureVersion);
__AccessControl_init();
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
s_isMintingActive = true;
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
}
/// @notice Authorizes an upgrade to a new contract implementation
/// @dev Overrides the UUPSUpgradeable internal function with access control.
/// @param newImplementation Address of the new contract implementation
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(UPGRADER_ROLE)
{}
/// @notice Checks whether a quest is active or not
/// @param questId Unique identifier for the quest
function isQuestActive(uint256 questId) public view returns (bool) {
return s_quests[questId];
}
/// @notice Retrieves the URI for a given token
/// @dev Overrides the ERC721Upgradeable's tokenURI method.
/// @param _tokenId The ID of the token
/// @return _tokenURI The URI of the specified token
function tokenURI(uint256 _tokenId) public view override returns (string memory _tokenURI) {
return s_tokenURIs[_tokenId];
}
/// @notice Mints a CUBE based on the provided data
/// @param cubeData CubeData struct containing minting information
/// @param signature Signature of the CubeData struct
function mintCube(CubeData calldata cubeData, bytes calldata signature)
external
payable
nonReentrant
{
// Check if the minting function is currently active. If not, revert the transaction
if (!s_isMintingActive) {
revert CUBE__MintingIsNotActive();
}
if (cubeData.isNative) {
// Check if the sent value is at least equal to the price
if (msg.value < cubeData.price) {
revert CUBE__FeeNotEnough();
}
}
if (s_l3Token == address(0)) {
revert CUBE__L3TokenNotSet();
}
if (s_treasury == address(0)) {
revert CUBE__TreasuryNotSet();
}
_mintCube(cubeData, signature);
}
/// @notice Internal function to handle the logic of minting a single cube
/// @dev Verifies the signer, handles nonce, transactions, referral payments, and minting.
/// @param data The CubeData containing details of the minting
/// @param signature The signature for verification
function _mintCube(CubeData calldata data, bytes calldata signature) internal {
// Cache the tokenId
uint256 tokenId = s_nextTokenId;
// Validate the signature to ensure the mint request is authorized
_validateSignature(data, signature);
// Iterate over all the transactions in the mint request and emit events
for (uint256 i = 0; i < data.transactions.length;) {
emit CubeTransaction(
tokenId, data.transactions[i].txHash, data.transactions[i].networkChainId
);
unchecked {
++i;
}
}
// Set the token URI for the CUBE
s_tokenURIs[tokenId] = data.tokenURI;
// Increment the counters for quest completion, issue numbers, and token IDs
unchecked {
++s_questIssueNumbers[data.questId];
++s_nextTokenId;
}
// process payments
data.isNative ? _processNativePayouts(data) : _processL3Payouts(data);
// Perform the actual minting of the CUBE
_safeMint(data.toAddress, tokenId);
// Emit an event indicating a CUBE has been claimed
emit CubeClaim(
data.questId,
tokenId,
data.toAddress,
data.isNative,
data.price,
s_questIssueNumbers[data.questId],
data.walletProvider,
data.embedOrigin
);
if (data.reward.chainId != 0) {
if (data.reward.factoryAddress != address(0)) {
IFactory(data.reward.factoryAddress).distributeRewards(
data.questId,
data.reward.tokenAddress,
data.toAddress,
data.reward.amount,
data.reward.tokenId,
data.reward.tokenType,
data.reward.rakeBps
);
}
emit TokenReward(
tokenId,
data.reward.tokenAddress,
data.reward.chainId,
data.reward.amount,
data.reward.tokenId,
data.reward.tokenType
);
}
}
/// @notice Validates the signature for a Cube minting request
/// @dev Ensures that the signature is from a valid signer and the nonce hasn't been used before
/// @param data The CubeData struct containing minting details
/// @param signature The signature to be validated
function _validateSignature(CubeData calldata data, bytes calldata signature) internal {
address signer = _getSigner(data, signature);
if (!hasRole(SIGNER_ROLE, signer)) {
revert CUBE__IsNotSigner();
}
if (s_nonces[data.nonce]) {
revert CUBE__NonceAlreadyUsed();
}
s_nonces[data.nonce] = true;
}
/// @notice Processes fee payouts to specified recipients when handling L3 payments
/// @dev Distributes a portion of the minting fee to designated addresses based on their Basis Points (BPS)
/// @param data The CubeData struct containing payout details
function _processL3Payouts(CubeData calldata data) internal {
uint256 totalAmount;
// Process referral payouts to recipients
if (data.recipients.length > 0) {
// max basis points is 10k (100%)
uint16 maxBps = 10_000;
uint256 allowance = IERC20(s_l3Token).allowance(msg.sender, address(this));
for (uint256 i = 0; i < data.recipients.length;) {
if (data.recipients[i].BPS > maxBps) {
revert CUBE__BPSTooHigh();
}
// Calculate the referral amount for each recipient
uint256 referralAmount = (data.price * data.recipients[i].BPS) / maxBps;
totalAmount = totalAmount + referralAmount;
// Ensure the total payout does not exceed the cube price or contract allowance
if (totalAmount > data.price) {
revert CUBE__ExcessiveFeePayout();
}
if (totalAmount > allowance) {
revert CUBE__ExceedsContractAllowance();
}
// Transfer the referral amount to the recipient
address recipient = data.recipients[i].recipient;
if (recipient != address(0)) {
(bool payoutSuccess, bytes memory payoutData) = s_l3Token.call(
abi.encodeWithSelector(
TRANSFER_ERC20, msg.sender, recipient, referralAmount
)
);
if (
!payoutSuccess || (payoutData.length > 0 && !abi.decode(payoutData, (bool)))
) {
revert CUBE__ERC20TransferFailed();
}
emit FeePayout(recipient, referralAmount, data.isNative);
}
unchecked {
++i;
}
}
}
// Transfer the remaining amount to the treasury
uint256 treasuryAmount = data.price - totalAmount;
if (treasuryAmount > 0) {
(bool success, bytes memory returnData) = s_l3Token.call(
abi.encodeWithSelector(TRANSFER_ERC20, msg.sender, s_treasury, treasuryAmount)
);
if (!success || (returnData.length > 0 && !abi.decode(returnData, (bool)))) {
revert CUBE__ERC20TransferFailed();
}
}
}
/// @notice Processes fee payouts to specified recipients when handling native payments
/// @dev Distributes a portion of the minting fee to designated addresses based on their Basis Points (BPS)
/// @param data The CubeData struct containing payout details
function _processNativePayouts(CubeData calldata data) internal {
uint256 totalReferrals;
if (data.recipients.length > 0) {
// max basis points is 10k (100%)
uint16 maxBps = 10_000;
uint256 contractBalance = address(this).balance;
for (uint256 i = 0; i < data.recipients.length;) {
if (data.recipients[i].BPS > maxBps) {
revert CUBE__BPSTooHigh();
}
// Calculate the referral amount for each recipient
uint256 referralAmount = (data.price * data.recipients[i].BPS) / maxBps;
totalReferrals = totalReferrals + referralAmount;
// Ensure the total payout does not exceed the cube price or contract balance
if (totalReferrals > data.price) {
revert CUBE__ExcessiveFeePayout();
}
if (totalReferrals > contractBalance) {
revert CUBE__ExceedsContractBalance();
}
// Transfer the referral amount to the recipient
address recipient = data.recipients[i].recipient;
if (recipient != address(0)) {
(bool payoutSuccess,) = recipient.call{value: referralAmount}("");
if (!payoutSuccess) {
revert CUBE__TransferFailed();
}
emit FeePayout(recipient, referralAmount, data.isNative);
}
unchecked {
++i;
}
}
}
(bool success,) = payable(s_treasury).call{value: data.price - totalReferrals}("");
if (!success) {
revert CUBE__NativePaymentFailed();
}
}
/// @notice Recovers the signer's address from the CubeData and its associated signature
/// @dev Utilizes EIP-712 typed data hashing and ECDSA signature recovery
/// @param data The CubeData struct containing the details of the minting request
/// @param sig The signature associated with the CubeData
/// @return The address of the signer who signed the CubeData
function _getSigner(CubeData calldata data, bytes calldata sig)
internal
view
returns (address)
{
bytes32 digest = _computeDigest(data);
return digest.recover(sig);
}
/// @notice Internal function to compute the EIP712 digest for CubeData
/// @dev Generates the digest that must be signed by the signer.
/// @param data The CubeData to generate a digest for
/// @return The computed EIP712 digest
function _computeDigest(CubeData calldata data) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(_getStructHash(data)));
}
/// @notice Internal function to generate the struct hash for CubeData
/// @dev Encodes the CubeData struct into a hash as per EIP712 standard.
/// @param data The CubeData struct to hash
/// @return A hash representing the encoded CubeData
function _getStructHash(CubeData calldata data) internal pure returns (bytes memory) {
return abi.encode(
CUBE_DATA_HASH,
data.questId,
data.nonce,
data.price,
data.isNative,
data.toAddress,
_encodeString(data.walletProvider),
_encodeString(data.tokenURI),
_encodeString(data.embedOrigin),
_encodeCompletedTxs(data.transactions),
_encodeRecipients(data.recipients),
_encodeReward(data.reward)
);
}
/// @notice Encodes a string into a bytes32 hash
/// @dev Used for converting strings into a consistent format for EIP712 encoding
/// @param _string The string to be encoded
/// @return The keccak256 hash of the encoded string
function _encodeString(string calldata _string) internal pure returns (bytes32) {
return keccak256(bytes(_string));
}
/// @notice Encodes a transaction data into a byte array
/// @dev Used for converting transaction data into a consistent format for EIP712 encoding
/// @param transaction The TransactionData struct to be encoded
/// @return A byte array representing the encoded transaction data
function _encodeTx(TransactionData calldata transaction) internal pure returns (bytes memory) {
return abi.encode(
TX_DATA_HASH,
_encodeString(transaction.txHash),
_encodeString(transaction.networkChainId)
);
}
/// @notice Encodes an array of transaction data into a single bytes32 hash
/// @dev Used to aggregate multiple transactions into a single hash for EIP712 encoding
/// @param txData An array of TransactionData structs to be encoded
/// @return A bytes32 hash representing the aggregated and encoded transaction data
function _encodeCompletedTxs(TransactionData[] calldata txData)
internal
pure
returns (bytes32)
{
bytes32[] memory encodedTxs = new bytes32[](txData.length);
for (uint256 i = 0; i < txData.length;) {
encodedTxs[i] = keccak256(_encodeTx(txData[i]));
unchecked {
++i;
}
}
return keccak256(abi.encodePacked(encodedTxs));
}
/// @notice Encodes a fee recipient data into a byte array
/// @dev Used for converting fee recipient information into a consistent format for EIP712 encoding
/// @param data The FeeRecipient struct to be encoded
/// @return A byte array representing the encoded fee recipient data
function _encodeRecipient(FeeRecipient calldata data) internal pure returns (bytes memory) {
return abi.encode(RECIPIENT_DATA_HASH, data.recipient, data.BPS);
}
/// @notice Encodes an array of fee recipient data into a single bytes32 hash
/// @dev Used to aggregate multiple fee recipient entries into a single hash for EIP712 encoding
/// @param data An array of FeeRecipient structs to be encoded
/// @return A bytes32 hash representing the aggregated and encoded fee recipient data
function _encodeRecipients(FeeRecipient[] calldata data) internal pure returns (bytes32) {
bytes32[] memory encodedRecipients = new bytes32[](data.length);
for (uint256 i = 0; i < data.length;) {
encodedRecipients[i] = keccak256(_encodeRecipient(data[i]));
unchecked {
++i;
}
}
return keccak256(abi.encodePacked(encodedRecipients));
}
/// @notice Encodes the reward data for a CUBE mint
/// @param data An array of FeeRecipient structs to be encoded
/// @return A bytes32 hash representing the encoded reward data
function _encodeReward(RewardData calldata data) internal pure returns (bytes32) {
return keccak256(
abi.encode(
REWARD_DATA_HASH,
data.tokenAddress,
data.chainId,
data.amount,
data.tokenId,
data.tokenType,
data.rakeBps,
data.factoryAddress
)
);
}
/// @notice Enables or disables the minting process
/// @dev Can only be called by an account with the default admin role.
/// @param _isMintingActive Boolean indicating whether minting should be active
function setIsMintingActive(bool _isMintingActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
s_isMintingActive = _isMintingActive;
emit MintingSwitch(_isMintingActive);
}
/// @notice Sets a new treasury address
/// @dev Can only be called by an account with the default admin role.
/// @param _treasury Address of the new treasury to receive fees
function setTreasury(address _treasury) external onlyRole(DEFAULT_ADMIN_ROLE) {
s_treasury = _treasury;
emit UpdatedTreasury(_treasury);
}
/// @notice Sets the address of the L3 token
/// @dev Can only be called by an account with the default admin role.
/// @param _l3 L3 token address
function setL3TokenAddress(address _l3) external onlyRole(DEFAULT_ADMIN_ROLE) {
s_l3Token = _l3;
emit UpdatedL3Address(_l3);
}
/// @notice Withdraws the contract's balance to the message sender
/// @dev Can only be called by an account with the default admin role.
function withdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 withdrawAmount = address(this).balance;
(bool success,) = msg.sender.call{value: withdrawAmount}("");
if (!success) {
revert CUBE__WithdrawFailed();
}
emit ContractWithdrawal(withdrawAmount);
}
/// @notice Initializes a new quest with given parameters
/// @dev Can only be called by an account with the signer role.
/// @param questId Unique identifier for the quest
/// @param communities Array of community names associated with the quest
/// @param title Title of the quest
/// @param difficulty Difficulty level of the quest
/// @param questType Type of the quest
function initializeQuest(
uint256 questId,
string[] memory communities,
string memory title,
Difficulty difficulty,
QuestType questType,
string[] memory tags
) external onlyRole(SIGNER_ROLE) {
s_quests[questId] = true;
emit QuestMetadata(questId, questType, difficulty, title, tags, communities);
}
/// @notice Unpublishes and disables a quest
/// @dev Can only be called by an account with the signer role
/// @param questId Unique identifier for the quest
function unpublishQuest(uint256 questId) external onlyRole(SIGNER_ROLE) {
s_quests[questId] = false;
emit QuestDisabled(questId);
}
/// @notice Checks if the contract implements an interface
/// @dev Overrides the supportsInterface function of ERC721Upgradeable and AccessControlUpgradeable.
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @return True if the contract implements the interface, false otherwise
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, AccessControlUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}