@@ -10,22 +10,34 @@ import {IDelegateRegistry} from "./interfaces/IDelegateRegistry.sol";
1010 * @dev This contract is designed to be used in conjunction with a delegate registry to resolve the most specific
1111 * delegation that matches the rights, with specificity being determined by delegation type in order of ERC721 >
1212 * CONTRACT > ALL. ERC20 and ERC1155 are not supported. If multiple delegations of the same specificity match the rights,
13- * the most recent one is respected
13+ * the most recent one is respected. If no delegation matches the rights, global delegations (bytes24(0) are considered,
14+ * but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
15+ * If no delegation matches the rights and there are no empty delegations, the owner is returned.
16+ * Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
17+ * If the expiration is past, the delegation is not considered to match the request.
1418 */
1519contract ExclusiveDelegateResolver {
1620 /// @dev The address of the Delegate Registry contract
1721 address public constant DELEGATE_REGISTRY = 0x00000000000000447e69651d841bD8D104Bed493 ;
1822
23+ /// @dev The rights value for a global delegation. These are considered only if no delegation by rights matches the request.
24+ bytes24 public constant GLOBAL_DELEGATION = bytes24 (0 );
25+
1926 /**
20- * @notice Gets the owner of an ERC721 token, resolving any hot wallet links
27+ * @notice Gets the owner of an ERC721 token, resolved through delegatexyz if possible
2128 * @param contractAddress The ERC721 contract address
2229 * @param tokenId The token ID to check
23- * @return owner The owner address, resolved through any active wallet links
30+ * @return owner The owner address or delegated owner if one exists
2431 * @notice returns the most specific delegation that matches the rights, with specificity being determined
2532 * by delegation type in order of ERC721 > CONTRACT > ALL. ERC20 and ERC1155 are not supported
26- * if multiple delegations of the same specificity match the rights, the most recent one is respected
33+ * if multiple delegations of the same specificity match the rights, the most recent one is respected.
34+ * If no delegation matches the rights, global delegations (bytes24(0) are considered,
35+ * but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
36+ * If no delegation matches the rights and there are no empty delegations, the owner is returned.
37+ * Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
38+ * If the expiration is past, the delegation is not considered to match the request.
2739 */
28- function exclusiveOwnerByRights (address contractAddress , uint256 tokenId , bytes32 rights )
40+ function exclusiveOwnerByRights (address contractAddress , uint256 tokenId , bytes24 rights )
2941 external
3042 view
3143 returns (address owner )
@@ -44,8 +56,12 @@ contract ExclusiveDelegateResolver {
4456 IDelegateRegistry.Delegation memory delegation = delegations[i];
4557
4658 if (_delegationMatchesRequest (delegation, contractAddress, tokenId, rights)) {
47- if (delegation.type_ > delegationToReturn.type_) {
48- if (delegation.type_ == IDelegateRegistry.DelegationType.ERC721 ) {
59+ if (_delegationOutranksCurrent (delegationToReturn, delegation)) {
60+ // re-check rights here to ensure global ERC721 type delegations do not get early returned
61+ if (
62+ delegation.type_ == IDelegateRegistry.DelegationType.ERC721
63+ && bytes24 (delegation.rights) == rights
64+ ) {
4965 return delegation.to;
5066 }
5167
@@ -57,23 +73,72 @@ contract ExclusiveDelegateResolver {
5773 return delegationToReturn.to == address (0 ) ? owner : delegationToReturn.to;
5874 }
5975
76+ /**
77+ * @notice Decodes a rights bytes32 value into its identifier and expiration
78+ * @param rights The rights bytes32 value
79+ * @return rightsIdentifier The rights identifier
80+ * @return expiration The expiration timestamp
81+ */
82+ function decodeRightsExpiration (bytes32 rights ) public pure returns (bytes24 , uint40 ) {
83+ bytes24 rightsIdentifier = bytes24 (rights);
84+ uint40 expiration = uint40 (uint256 (rights));
85+
86+ return (rightsIdentifier, expiration);
87+ }
88+
89+ /**
90+ * @notice Convenience function to generate a rights bytes32 rights value with an expiration
91+ * @param rightsIdentifier The rights identifier
92+ * @param expiration The expiration timestamp
93+ * @return rights The rights bytes32 value
94+ */
95+ function generateRightsWithExpiration (bytes24 rightsIdentifier , uint40 expiration )
96+ external
97+ pure
98+ returns (bytes32 )
99+ {
100+ uint256 rights = uint256 (uint192 (rightsIdentifier)) << 64 ;
101+ return bytes32 (rights | uint256 (expiration));
102+ }
103+
60104 function _delegationMatchesRequest (
61105 IDelegateRegistry.Delegation memory delegation ,
62106 address contractAddress ,
63107 uint256 tokenId ,
64- bytes32 rights
65- ) internal pure returns (bool ) {
66- if (delegation.rights != rights) {
108+ bytes24 rights
109+ ) internal view returns (bool ) {
110+ // Extract rights identifier (remaining 192 bits)
111+ (bytes24 rightsIdentifier , uint40 expiration ) = decodeRightsExpiration (delegation.rights);
112+
113+ if (block .timestamp > expiration) {
114+ return false ;
115+ } else if (rightsIdentifier != rights && rightsIdentifier != GLOBAL_DELEGATION) {
67116 return false ;
68117 } else if (delegation.type_ == IDelegateRegistry.DelegationType.ALL) {
69118 return true ;
70119 } else if (delegation.type_ == IDelegateRegistry.DelegationType.CONTRACT) {
71120 return delegation.contract_ == contractAddress;
72121 } else if (delegation.type_ == IDelegateRegistry.DelegationType.ERC721 ) {
73122 return delegation.contract_ == contractAddress && delegation.tokenId == tokenId;
123+ } else {
124+ return false ;
74125 }
126+ }
75127
76- return false ;
128+ function _delegationOutranksCurrent (
129+ IDelegateRegistry.Delegation memory currentDelegation ,
130+ IDelegateRegistry.Delegation memory newDelegation
131+ ) internal pure returns (bool ) {
132+ bytes24 currentRightsIdentifier = bytes24 (currentDelegation.rights);
133+ bytes24 newRightsIdentifier = bytes24 (newDelegation.rights);
134+
135+ if (currentRightsIdentifier == newRightsIdentifier) {
136+ return newDelegation.type_ > currentDelegation.type_;
137+ } else if (currentRightsIdentifier == GLOBAL_DELEGATION) {
138+ return true ;
139+ } else {
140+ return false ;
141+ }
77142 }
78143
79144 function _getOwner (address contractAddress , uint256 tokenId ) internal view returns (address owner ) {
0 commit comments