-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnestable.sol
1761 lines (1608 loc) · 70.5 KB
/
nestable.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
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: CC0-1.0
//Generally all interactions should propagate downstream
pragma solidity ^0.8.16;
/// @title EIP-6059 Parent-Governed Nestable Non-Fungible Tokens
/// @dev See https://eips.ethereum.org/EIPS/eip-6059
/// @dev Note: the ERC-165 identifier for this interface is 0x42b0e56f.
interface IERC6059 /* is ERC165 */ {
/**
* @notice The core struct of ownership.
* @dev The `DirectOwner` struct is used to store information of the next immediate owner, be it the parent token,
* an `ERC721Receiver` contract or an externally owned account.
* @dev If the token is not owned by an NFT, the `tokenId` MUST equal `0`.
* @param tokenId ID of the parent token
* @param ownerAddress Address of the owner of the token. If the owner is another token, then the address MUST be
* the one of the parent token's collection smart contract. If the owner is externally owned account, the address
* MUST be the address of this account
*/
struct DirectOwner {
uint256 tokenId;
address ownerAddress;
}
/**
* @notice Used to notify listeners that the token is being transferred.
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
* @param from Address of the previous immediate owner, which is a smart contract if the token was nested.
* @param to Address of the new immediate owner, which is a smart contract if the token is being nested.
* @param fromTokenId ID of the previous parent token. If the token was not nested before, the value MUST be `0`
* @param toTokenId ID of the new parent token. If the token is not being nested, the value MUST be `0`
* @param tokenId ID of the token being transferred
*/
event NestTransfer(
address indexed from,
address indexed to,
uint256 fromTokenId,
uint256 toTokenId,
uint256 indexed tokenId
);
/**
* @notice Used to notify listeners that a new token has been added to a given token's pending children array.
* @dev Emitted when a child NFT is added to a token's pending array.
* @param tokenId ID of the token that received a new pending child token
* @param childIndex Index of the proposed child token in the parent token's pending children array
* @param childAddress Address of the proposed child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
*/
event ChildProposed(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId
);
/**
* @notice Used to notify listeners that a new child token was accepted by the parent token.
* @dev Emitted when a parent token accepts a token from its pending array, migrating it to the active array.
* @param tokenId ID of the token that accepted a new child token
* @param childIndex Index of the newly accepted child token in the parent token's active children array
* @param childAddress Address of the child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
*/
event ChildAccepted(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId
);
/**
* @notice Used to notify listeners that all pending child tokens of a given token have been rejected.
* @dev Emitted when a token removes all a child tokens from its pending array.
* @param tokenId ID of the token that rejected all of the pending children
*/
event AllChildrenRejected(uint256 indexed tokenId);
/**
* @notice Used to notify listeners a child token has been transferred from parent token.
* @dev Emitted when a token transfers a child from itself, transferring ownership.
* @param tokenId ID of the token that transferred a child token
* @param childIndex Index of a child in the array from which it is being transferred
* @param childAddress Address of the child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
* @param fromPending A boolean value signifying whether the token was in the pending child tokens array (`true`) or
* in the active child tokens array (`false`)
*/
event ChildTransferred(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId,
bool fromPending
);
/**
* @notice The core child token struct, holding the information about the child tokens.
* @return tokenId ID of the child token in the child token's collection smart contract
* @return contractAddress Address of the child token's smart contract
*/
struct Child {
uint256 tokenId;
address contractAddress;
}
/**
* @notice Used to retrieve the *root* owner of a given token.
* @dev The *root* owner of the token is the top-level owner in the hierarchy which is not an NFT.
* @dev If the token is owned by another NFT, it MUST recursively look up the parent's root owner.
* @param tokenId ID of the token for which the *root* owner has been retrieved
* @return owner The *root* owner of the token
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @notice Used to retrieve the immediate owner of the given token.
* @dev If the immediate owner is another token, the address returned, MUST be the one of the parent token's
* collection smart contract.
* @param tokenId ID of the token for which the direct owner is being retrieved
* @return address Address of the given token's owner
* @return uint256 The ID of the parent token. MUST be `0` if the owner is not an NFT
* @return bool The boolean value signifying whether the owner is an NFT or not
*/
function directOwnerOf(uint256 tokenId)
external
view
returns (
address,
uint256,
bool
);
/**
* @notice Used to burn a given token.
* @dev When a token is burned, all of its child tokens are recursively burned as well.
* @dev When specifying the maximum recursive burns, the execution MUST be reverted if there are more children to be
* burned.
* @dev Setting the `maxRecursiveBurn` value to 0 SHOULD only attempt to burn the specified token and MUST revert if
* there are any child tokens present.
* @param tokenId ID of the token to burn
* @param maxRecursiveBurns Maximum number of tokens to recursively burn
* @return uint256 Number of recursively burned children
*/
function burn(uint256 tokenId, uint256 maxRecursiveBurns)
external
returns (uint256);
/**
* @notice Used to add a child token to a given parent token.
* @dev This adds the child token into the given parent token's pending child tokens array.
* @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream
* child tokens.
* @dev This method MUST NOT be called directly. It MUST only be called from an instance of `IERC6059` as part of a
`nestTransfer` or `transferChild` to an NFT.
* @dev Requirements:
*
* - `directOwnerOf` on the child contract MUST resolve to the called contract.
* - the pending array of the parent contract MUST not be full.
* @param parentId ID of the parent token to receive the new child token
* @param childId ID of the new proposed child token
*/
function addChild(uint256 parentId, uint256 childId,bytes calldata data) external;
/**
* @notice Used to accept a pending child token for a given parent token.
* @dev This moves the child token from parent token's pending child tokens array into the active child tokens
* array.
* @param parentId ID of the parent token for which the child token is being accepted
* @param childIndex Index of the child token to accept in the pending children array of a given token
* @param childAddress Address of the collection smart contract of the child token expected to be at the specified
* index
* @param childId ID of the child token expected to be located at the specified index
*/
function acceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) external;
/**
* @notice Used to reject all pending children of a given parent token.
* @dev Removes the children from the pending array mapping.
* @dev The children's ownership structures are not updated.
* @dev Requirements:
*
* - `parentId` MUST exist
* @param parentId ID of the parent token for which to reject all of the pending tokens
* @param maxRejections Maximum number of expected children to reject, used to prevent from
* rejecting children which arrive just before this operation.
*/
function rejectAllChildren(uint256 parentId, uint256 maxRejections) external;
/**
* @notice Used to transfer a child token from a given parent token.
* @dev MUST remove the child from the parent's active or pending children.
* @dev When transferring a child token, the owner of the token MUST be set to `to`, or not updated in the event of `to`
* being the `0x0` address.
* @param tokenId ID of the parent token from which the child token is being transferred
* @param to Address to which to transfer the token to
* @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token)
* @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or
* pending array)
* @param childAddress Address of the child token's collection smart contract
* @param childId ID of the child token in its own collection smart contract
* @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the
* parent token (`true`) or in the active array (`false`)
* @param data Additional data with no specified format, sent in call to `to`
*/
function transferChild(
uint256 tokenId,
address to,
uint256 destinationId,
uint256 childIndex,
address childAddress,
uint256 childId,
bool isPending,
bytes memory data
) external;
/**
* @notice Used to retrieve the active child tokens of a given parent token.
* @dev Returns array of Child structs existing for parent token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which to retrieve the active child tokens
* @return struct[] An array of Child structs containing the parent token's active child tokens
*/
function childrenOf(uint256 parentId)
external
view
returns (Child[] memory);
/**
* @notice Used to retrieve the pending child tokens of a given parent token.
* @dev Returns array of pending Child structs existing for given parent.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which to retrieve the pending child tokens
* @return struct[] An array of Child structs containing the parent token's pending child tokens
*/
function pendingChildrenOf(uint256 parentId)
external
view
returns (Child[] memory);
/**
* @notice Used to retrieve a specific active child token for a given parent token.
* @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which the child is being retrieved
* @param index Index of the child token in the parent token's active child tokens array
* @return struct A Child struct containing data about the specified child
*/
function childOf(uint256 parentId, uint256 index)
external
view
returns (Child memory);
/**
* @notice Used to retrieve a specific pending child token from a given parent token.
* @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which the pending child token is being retrieved
* @param index Index of the child token in the parent token's pending child tokens array
* @return struct A Child struct containing data about the specified child
*/
function pendingChildOf(uint256 parentId, uint256 index)
external
view
returns (Child memory);
/**
* @notice Used to transfer the token into another token.
* @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream
* child tokens.
* @param from Address of the direct owner of the token to be transferred
* @param to Address of the receiving token's collection smart contract
* @param tokenId ID of the token being transferred
* @param destinationId ID of the token to receive the token being transferred
*/
function nestTransferFrom(
address from,
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) external;
}
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
error ChildAlreadyExists();
error ChildIndexOutOfRange();
error ERC721AddressZeroIsNotaValidOwner();
error ERC721ApprovalToCurrentOwner();
error ERC721ApproveCallerIsNotOwnerNorApprovedForAll();
error ERC721ApproveToCaller();
error ERC721InvalidTokenId();
error ERC721MintToTheZeroAddress();
error ERC721NotApprovedOrOwner();
error ERC721TokenAlreadyMinted();
error ERC721TransferFromIncorrectOwner();
error ERC721TransferToNonReceiverImplementer();
error ERC721TransferToTheZeroAddress();
error IdZeroForbidden();
error IsNotContract();
error MaxPendingChildrenReached();
error MaxRecursiveBurnsReached(address childContract, uint256 childId);
error MintToNonNestableImplementer();
error NestableTooDeep();
error NestableTransferToDescendant();
error NestableTransferToNonNestableImplementer();
error NestableTransferToSelf();
error NotApprovedOrDirectOwner();
error PendingChildIndexOutOfRange();
error UnexpectedChildId();
error UnexpectedNumberOfChildren();
/**
* @title NestableToken
* @author RMRK team
* @notice Smart contract of the Nestable module.
* @dev This contract is hierarchy agnostic and can support an arbitrary number of nested levels up and down, as long as
* gas limits allow it.
*/
contract NestableToken is Context, IERC165, IERC721, IERC6059 {
using Address for address;
uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approver address to approved address
// The approver is necessary so approvals are invalidated for nested children on transfer
// WARNING: If a child NFT returns to a previous root owner, old permissions would be active again
mapping(uint256 => mapping(address => address)) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// ------------------- NESTABLE --------------
// Mapping from token ID to DirectOwner struct
mapping(uint256 => DirectOwner) private _directOwners;
// Mapping of tokenId to array of active children structs
mapping(uint256 => Child[]) private _activeChildren;
// Mapping of tokenId to array of pending children structs
mapping(uint256 => Child[]) private _pendingChildren;
// Mapping of child token address to child token ID to whether they are pending or active on any token
// We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens
// we can strip it for size/gas savings.
mapping(address => mapping(uint256 => uint256)) internal _childIsInActive;
// -------------------------- MODIFIERS ----------------------------
/**
* @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner.
* @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be
* reverted.
* @param tokenId ID of the token to check
*/
function _onlyApprovedOrOwner(uint256 tokenId) private view {
if (!_isApprovedOrOwner(_msgSender(), tokenId))
revert ERC721NotApprovedOrOwner();
}
/**
* @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner.
* @param tokenId ID of the token to check
*/
modifier onlyApprovedOrOwner(uint256 tokenId) {
_onlyApprovedOrOwner(tokenId);
_;
}
/**
* @notice Used to verify that the caller is approved to manage the given token or it its direct owner.
* @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner
* struct.
* @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token.
* @dev Used for parent-scoped transfers.
* @param tokenId ID of the token to check.
*/
function _onlyApprovedOrDirectOwner(uint256 tokenId) private view {
if (!_isApprovedOrDirectOwner(_msgSender(), tokenId))
revert NotApprovedOrDirectOwner();
}
/**
* @notice Used to verify that the caller is approved to manage the given token or is its direct owner.
* @param tokenId ID of the token to check
*/
modifier onlyApprovedOrDirectOwner(uint256 tokenId) {
_onlyApprovedOrDirectOwner(tokenId);
_;
}
// ------------------------------- ERC721 ---------------------------------
/**
* @inheritdoc IERC165
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
interfaceId == type(IERC6059).interfaceId;
}
/**
* @inheritdoc IERC721
*/
function balanceOf(address owner) public view virtual returns (uint256) {
if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner();
return _balances[owner];
}
////////////////////////////////////////
// TRANSFERS
////////////////////////////////////////
/**
* @inheritdoc IERC721
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual onlyApprovedOrDirectOwner(tokenId) {
_transfer(from, to, tokenId);
}
/**
* @inheritdoc IERC721
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @inheritdoc IERC721
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {
_safeTransfer(from, to, tokenId, data);
}
/**
* @notice Used to transfer the token into another token.
* @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream
* child tokens.
* @param from Address of the direct owner of the token to be transferred
* @param to Address of the receiving token's collection smart contract
* @param tokenId ID of the token being transferred
* @param destinationId ID of the token to receive the token being transferred
*/
function nestTransferFrom(
address from,
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {
_nestTransfer(from, to, tokenId, destinationId, data);
}
/**
* @notice Used to safely transfer the token form `from` to `to`.
* @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being
* forever locked.
* @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative
* mechanisms to perform token transfer, such as signature-based.
* @dev Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
* @dev Emits a {Transfer} event.
* @param from Address of the account currently owning the given token
* @param to Address to transfer the token to
* @param tokenId ID of the token to transfer
* @param data Additional data with no specified format, sent in call to `to`
*/
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_transfer(from, to, tokenId);
if (!_checkOnERC721Received(from, to, tokenId, data))
revert ERC721TransferToNonReceiverImplementer();
}
/**
* @notice Used to transfer the token from `from` to `to`.
* @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
* @dev Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* @dev Emits a {Transfer} event.
* @param from Address of the account currently owning the given token
* @param to Address to transfer the token to
* @param tokenId ID of the token to transfer
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
if (to == address(0)) revert ERC721TransferToTheZeroAddress();
_beforeTokenTransfer(from, to, tokenId);
_beforeNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId);
_balances[from] -= 1;
_updateOwnerAndClearApprovals(tokenId, 0, to);
_balances[to] += 1;
emit Transfer(from, to, tokenId);
emit NestTransfer(immediateOwner, to, parentId, 0, tokenId);
_afterTokenTransfer(from, to, tokenId);
_afterNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId);
}
/**
* @notice Used to transfer a token into another token.
* @dev Attempting to nest a token into `0x0` address will result in reverted transaction.
* @dev Attempting to nest a token into itself will result in reverted transaction.
* @param from Address of the account currently owning the given token
* @param to Address of the receiving token's collection smart contract
* @param tokenId ID of the token to transfer
* @param destinationId ID of the token receiving the given token
* @param data Additional data with no specified format, sent in the addChild call
*/
function _nestTransfer(
address from,
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) internal virtual {
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
if (to == address(0)) revert ERC721TransferToTheZeroAddress();
if (to == address(this) && tokenId == destinationId)
revert NestableTransferToSelf();
// Destination contract checks:
// It seems redundant, but otherwise it would revert with no error
if (!to.isContract()) revert IsNotContract();
if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId))
revert NestableTransferToNonNestableImplementer();
_checkForInheritanceLoop(tokenId, to, destinationId);
_beforeTokenTransfer(from, to, tokenId);
_beforeNestedTokenTransfer(
immediateOwner,
to,
parentId,
destinationId,
tokenId
);
_balances[from] -= 1;
_updateOwnerAndClearApprovals(tokenId, destinationId, to);
_balances[to] += 1;
// Sending to NFT:
_sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data);
}
/**
* @notice Used to send a token to another token.
* @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`.
* @dev Emits {Transfer} event.
* @dev Emits {NestTransfer} event.
* @param from Address from which the token is being sent
* @param to Address of the collection smart contract of the token to receive the given token
* @param parentId ID of the current parent token of the token being sent
* @param destinationId ID of the tokento receive the token being sent
* @param tokenId ID of the token being sent
* @param data Additional data with no specified format, sent in the addChild call
*/
function _sendToNFT(
address from,
address to,
uint256 parentId,
uint256 destinationId,
uint256 tokenId,
bytes memory data
) private {
IERC6059 destContract = IERC6059(to);
destContract.addChild(destinationId, tokenId, data);
_afterTokenTransfer(from, to, tokenId);
_afterNestedTokenTransfer(from, to, parentId, destinationId, tokenId);
emit Transfer(from, to, tokenId);
emit NestTransfer(from, to, parentId, destinationId, tokenId);
}
/**
* @notice Used to check if nesting a given token into a specified token would create an inheritance loop.
* @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected.
* @dev The check for inheritance loop is bounded to guard against too much gas being consumed.
* @param currentId ID of the token that would be nested
* @param targetContract Address of the collection smart contract of the token into which the given token would be
* nested
* @param targetId ID of the token into which the given token would be nested
*/
function _checkForInheritanceLoop(
uint256 currentId,
address targetContract,
uint256 targetId
) private view {
for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) {
(
address nextOwner,
uint256 nextOwnerTokenId,
bool isNft
) = IERC6059(targetContract).directOwnerOf(targetId);
// If there's a final address, we're good. There's no loop.
if (!isNft) {
return;
}
// Ff the current nft is an ancestor at some point, there is an inheritance loop
if (nextOwner == address(this) && nextOwnerTokenId == currentId) {
revert NestableTransferToDescendant();
}
// We reuse the parameters to save some contract size
targetContract = nextOwner;
targetId = nextOwnerTokenId;
unchecked {
++i;
}
}
revert NestableTooDeep();
}
////////////////////////////////////////
// MINTING
////////////////////////////////////////
/**
* @notice Used to safely mint a token to a specified address.
* @dev Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
* @dev Emits a {Transfer} event.
* @param to Address to which to safely mint the gven token
* @param tokenId ID of the token to mint to the specified address
*/
function _safeMint(address to, uint256 tokenId) external virtual {
_safeMint(to, tokenId, "");
}
/**
* @notice Used to safely mint the token to the specified address while passing the additional data to contract
* recipients.
* @param to Address to which to mint the token
* @param tokenId ID of the token to mint
* @param data Additional data to send with the tokens
*/
function _safeMint(
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_mint(to, tokenId);
if (!_checkOnERC721Received(address(0), to, tokenId, data))
revert ERC721TransferToNonReceiverImplementer();
}
/**
* @notice Used to mint a specified token to a given address.
* @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible.
* @dev Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
* @dev Emits a {Transfer} event.
* @param to Address to mint the token to
* @param tokenId ID of the token to mint
*/
function _mint(address to, uint256 tokenId) internal virtual {
_innerMint(to, tokenId, 0);
emit Transfer(address(0), to, tokenId);
emit NestTransfer(address(0), to, 0, 0, tokenId);
_afterTokenTransfer(address(0), to, tokenId);
_afterNestedTokenTransfer(address(0), to, 0, 0, tokenId);
}
/**
* @notice Used to mint a child token to a given parent token.
* @param to Address of the collection smart contract of the token into which to mint the child token
* @param tokenId ID of the token to mint
* @param destinationId ID of the token into which to mint the new child token
* @param data Additional data with no specified format, sent in the addChild call
*/
function _nestMint(
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) external virtual {
// It seems redundant, but otherwise it would revert with no error
if (!to.isContract()) revert IsNotContract();
if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId))
revert MintToNonNestableImplementer();
_innerMint(to, tokenId, destinationId);
_sendToNFT(address(0), to, 0, destinationId, tokenId, data);
}
/**
* @notice Used to mint a child token into a given parent token.
* @dev Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` must not exist.
* - `tokenId` must not be `0`.
* @param to Address of the collection smart contract of the token into which to mint the child token
* @param tokenId ID of the token to mint
* @param destinationId ID of the token into which to mint the new token
*/
function _innerMint(
address to,
uint256 tokenId,
uint256 destinationId
) private {
if (to == address(0)) revert ERC721MintToTheZeroAddress();
if (_exists(tokenId)) revert ERC721TokenAlreadyMinted();
if (tokenId == 0) revert IdZeroForbidden();
_beforeTokenTransfer(address(0), to, tokenId);
_beforeNestedTokenTransfer(address(0), to, 0, destinationId, tokenId);
_balances[to] += 1;
_directOwners[tokenId] = DirectOwner({
ownerAddress: to,
tokenId: destinationId
});
}
////////////////////////////////////////
// Ownership
////////////////////////////////////////
/**
* @notice Used to retrieve the root owner of the given token.
* @dev Root owner is always the externally owned account.
* @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the
* root owner.
* @param tokenId ID of the token for which the root owner is being retrieved
* @return address Address of the root owner of the given token
*/
function ownerOf(
uint256 tokenId
) public view virtual override(IERC6059, IERC721) returns (address) {
(address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf(
tokenId
);
if (isNft) {
owner = IERC6059(owner).ownerOf(ownerTokenId);
}
return owner;
}
/**
* @notice Used to retrieve the immediate owner of the given token.
* @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0`.
* @param tokenId ID of the token for which the immediate owner is being retrieved
* @return address Address of the immediate owner. If the token is owned by an externally owned account, its address
* will be returned. If the token is owned by another token, the parent token's collection smart contract address
* is returned
* @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value
* should be `0`
* @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`)
*/
function directOwnerOf(
uint256 tokenId
) public view virtual returns (address, uint256, bool) {
DirectOwner memory owner = _directOwners[tokenId];
if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId();
return (owner.ownerAddress, owner.tokenId, owner.tokenId != 0);
}
////////////////////////////////////////
// BURNING
////////////////////////////////////////
/**
* @notice Used to burn a given token.
* @param tokenId ID of the token to burn
*/
function burn(uint256 tokenId) public virtual {
burn(tokenId, 0);
}
/**
* @notice Used to burn a token.
* @dev When a token is burned, its children are recursively burned as well.
* @dev The approvals are cleared when the token is burned.
* @dev Requirements:
*
* - `tokenId` must exist.
* @dev Emits a {Transfer} event.
* @param tokenId ID of the token to burn
* @param maxChildrenBurns Maximum children to recursively burn
* @return uint256 The number of recursive burns it took to burn all of the children
*/
function burn(
uint256 tokenId,
uint256 maxChildrenBurns
) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) {
return _burn(tokenId, maxChildrenBurns);
}
/**
* @notice Used to burn a token.
* @dev When a token is burned, its children are recursively burned as well.
* @dev The approvals are cleared when the token is burned.
* @dev Requirements:
*
* - `tokenId` must exist.
* @dev Emits a {Transfer} event.
* @dev Emits a {NestTransfer} event.
* @param tokenId ID of the token to burn
* @param maxChildrenBurns Maximum children to recursively burn
* @return uint256 The number of recursive burns it took to burn all of the children
*/
function _burn(
uint256 tokenId,
uint256 maxChildrenBurns
) internal virtual returns (uint256) {
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
address owner = ownerOf(tokenId);
_balances[immediateOwner] -= 1;
_beforeTokenTransfer(owner, address(0), tokenId);
_beforeNestedTokenTransfer(
immediateOwner,
address(0),
parentId,
0,
tokenId
);
_approve(address(0), tokenId);
_cleanApprovals(tokenId);
Child[] memory children = childrenOf(tokenId);
delete _activeChildren[tokenId];
delete _pendingChildren[tokenId];
delete _tokenApprovals[tokenId][owner];
uint256 pendingRecursiveBurns;
uint256 totalChildBurns;
uint256 length = children.length; //gas savings
for (uint256 i; i < length; ) {
if (totalChildBurns >= maxChildrenBurns)
revert MaxRecursiveBurnsReached(
children[i].contractAddress,
children[i].tokenId
);
delete _childIsInActive[children[i].contractAddress][
children[i].tokenId
];
unchecked {
// At this point we know pendingRecursiveBurns must be at least 1
pendingRecursiveBurns = maxChildrenBurns - totalChildBurns;
}
// We substract one to the next level to count for the token being burned, then add it again on returns
// This is to allow the behavior of 0 recursive burns meaning only the current token is deleted.
totalChildBurns +=
IERC6059(children[i].contractAddress).burn(
children[i].tokenId,
pendingRecursiveBurns - 1
) +
1;
unchecked {
++i;
}
}
// Can't remove before burning child since child will call back to get root owner
delete _directOwners[tokenId];
_afterTokenTransfer(owner, address(0), tokenId);
_afterNestedTokenTransfer(
immediateOwner,
address(0),
parentId,
0,
tokenId
);
emit Transfer(owner, address(0), tokenId);
emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId);
return totalChildBurns;
}
////////////////////////////////////////
// APPROVALS
////////////////////////////////////////
/**
* @inheritdoc IERC721
*/
function approve(address to, uint256 tokenId) public virtual {
address owner = ownerOf(tokenId);
if (to == owner) revert ERC721ApprovalToCurrentOwner();
if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender()))
revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll();
_approve(to, tokenId);
}
/**
* @inheritdoc IERC721
*/
function getApproved(
uint256 tokenId
) public view virtual returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId][ownerOf(tokenId)];
}
/**
* @inheritdoc IERC721
*/
function setApprovalForAll(address operator, bool approved) public virtual {
if (_msgSender() == operator) revert ERC721ApproveToCaller();
_operatorApprovals[_msgSender()][operator] = approved;
emit ApprovalForAll(_msgSender(), operator, approved);
}
/**
* @inheritdoc IERC721
*/
function isApprovedForAll(
address owner,
address operator
) public view virtual returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @notice Used to grant an approval to manage a given token.
* @dev Emits an {Approval} event.
* @param to Address to which the approval is being granted
* @param tokenId ID of the token for which the approval is being granted
*/