Skip to content

Commit c727f8a

Browse files
committed
Add fallback to global delegation if one is set
1 parent 0a14631 commit c727f8a

File tree

2 files changed

+205
-125
lines changed

2 files changed

+205
-125
lines changed

src/ExclusiveDelegateResolver.sol

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
1519
contract 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

Comments
 (0)