Contract Name:
ExclusiveDelegateResolver
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {IDelegateRegistry} from "./interfaces/IDelegateRegistry.sol";
/**
* @title ExclusiveDelegateResolver
* @author 0xQuit
* @notice A contract to resolve a single canonical delegated owner for a given ERC721 token
* @dev This contract is designed to be used in conjunction with a delegate registry to resolve the most specific
* delegation that matches the rights, with specificity being determined by delegation type in order of ERC721 >
* CONTRACT > ALL. ERC20 and ERC1155 are not supported. If multiple delegations of the same specificity match the rights,
* the most recent one is respected. If no delegation matches the rights, global delegations (bytes24(0) are considered,
* but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
* If no delegation matches the rights and there are no empty delegations, the owner is returned.
* Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
* If the expiration is past, the delegation is not considered to match the request.
*/
contract ExclusiveDelegateResolver {
/// @dev The address of the Delegate Registry contract
address public constant DELEGATE_REGISTRY = 0x0000000059A24EB229eED07Ac44229DB56C5d797;
/// @dev The rights value for a global delegation. These are considered only if no delegation by rights matches the request.
bytes24 public constant GLOBAL_DELEGATION = bytes24(0);
/**
* @notice Gets an exclusive wallet delegation, resolved through delegatexyz if possible
* @param vault The vault address
* @param rights The rights to check
* @return owner The vault wallet address or delegated wallet if one exists
* @notice returns the most recent delegation that matches the rights for an entire wallet delegation
* (type ALL) if multiple delegations of the same specificity match the rights, the most recent one is respected.
* If no delegation matches the rights, global delegations (bytes24(0) are considered,
* but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
* If no delegation matches the rights and there are no empty delegations, the owner is returned.
* Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
* If the expiration is past, the delegation is not considered to match the request.
*/
function exclusiveWalletByRights(address vault, bytes24 rights) public view returns (address) {
IDelegateRegistry.Delegation[] memory delegations =
IDelegateRegistry(DELEGATE_REGISTRY).getOutgoingDelegations(vault);
IDelegateRegistry.Delegation memory delegationToReturn;
for (uint256 i = delegations.length; i > 0;) {
unchecked {
--i;
}
IDelegateRegistry.Delegation memory delegation = delegations[i];
if (_delegationMatchesRequest(delegation, rights)) {
if (_delegationOutranksCurrent(delegationToReturn, delegation)) {
// re-check rights here to ensure global delegation does not get early returned
if (
delegation.type_ == IDelegateRegistry.DelegationType.ALL && bytes24(delegation.rights) == rights
) {
return delegation.to;
}
delegationToReturn = delegation;
}
}
}
return delegationToReturn.to == address(0) ? vault : delegationToReturn.to;
}
/**
* @notice Gets all wallets that have delegated to a given wallet
* @param wallet The wallet to check
* @param rights The rights to check
* @return wallets The wallets that have delegated to the given wallet
* @notice returns all wallets that have delegated to the given wallet, filtered by the rights
* if multiple delegations of the same specificity match the rights, the most recent one is respected.
* If no delegation matches the rights, global delegations (bytes24(0)) are considered,
* but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
* Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
* If the expiration is past, the delegation is not considered to match the request.
*/
function delegatedWalletsByRights(address wallet, bytes24 rights) external view returns (address[] memory) {
IDelegateRegistry.Delegation[] memory delegations =
IDelegateRegistry(DELEGATE_REGISTRY).getIncomingDelegations(wallet);
uint256 matchesCount = 0;
bool[] memory matches = new bool[](delegations.length);
for (uint256 i; i < delegations.length; ++i) {
IDelegateRegistry.Delegation memory delegation = delegations[i];
if (_delegationMatchesRequest(delegation, rights)) {
if (exclusiveWalletByRights(delegation.from, rights) == wallet) {
matches[i] = true;
matchesCount++;
}
}
}
address[] memory matchesArray = new address[](matchesCount);
uint256 matchesIndex = 0;
// filter to the delegated wallets that match the request
for (uint256 i; i < delegations.length; ++i) {
if (matches[i]) {
matchesArray[matchesIndex] = delegations[i].from;
matchesIndex++;
}
}
return matchesArray;
}
/**
* @notice Gets the owner of an ERC721 token, resolved through delegatexyz if possible
* @param contractAddress The ERC721 contract address
* @param tokenId The token ID to check
* @return owner The owner address or delegated owner if one exists
* @notice returns the most specific delegation that matches the rights, with specificity being determined
* by delegation type in order of ERC721 > CONTRACT > ALL. ERC20 and ERC1155 are not supported
* if multiple delegations of the same specificity match the rights, the most recent one is respected.
* If no delegation matches the rights, global delegations (bytes24(0) are considered,
* but MUST have an expiration greater than 0 to avoid conflicts with pre-existing delegations.
* If no delegation matches the rights and there are no empty delegations, the owner is returned.
* Expirations are supported by extracting a uint40 from the final 40 bits of a given delegation's rights value.
* If the expiration is past, the delegation is not considered to match the request.
*/
function exclusiveOwnerByRights(address contractAddress, uint256 tokenId, bytes24 rights)
external
view
returns (address owner)
{
owner = _getOwner(contractAddress, tokenId);
IDelegateRegistry.Delegation[] memory delegations =
IDelegateRegistry(DELEGATE_REGISTRY).getOutgoingDelegations(owner);
IDelegateRegistry.Delegation memory delegationToReturn;
for (uint256 i = delegations.length; i > 0;) {
unchecked {
--i;
}
IDelegateRegistry.Delegation memory delegation = delegations[i];
if (_delegationMatchesRequest(delegation, contractAddress, tokenId, rights)) {
if (_delegationOutranksCurrent(delegationToReturn, delegation)) {
// re-check rights here to ensure global ERC721 type delegations do not get early returned
if (
delegation.type_ == IDelegateRegistry.DelegationType.ERC721
&& bytes24(delegation.rights) == rights
) {
return delegation.to;
}
delegationToReturn = delegation;
}
}
}
return delegationToReturn.to == address(0) ? owner : delegationToReturn.to;
}
/**
* @notice Decodes a rights bytes32 value into its identifier and expiration
* @param rights The rights bytes32 value
* @return rightsIdentifier The rights identifier
* @return expiration The expiration timestamp
*/
function decodeRightsExpiration(bytes32 rights) public pure returns (bytes24, uint40) {
bytes24 rightsIdentifier = bytes24(rights);
uint40 expiration = uint40(uint256(rights));
return (rightsIdentifier, expiration);
}
/**
* @notice Convenience function to generate a rights bytes32 rights value with an expiration
* @param rightsIdentifier The rights identifier
* @param expiration The expiration timestamp
* @return rights The rights bytes32 value
*/
function generateRightsWithExpiration(bytes24 rightsIdentifier, uint40 expiration)
external
pure
returns (bytes32)
{
uint256 rights = uint256(uint192(rightsIdentifier)) << 64;
return bytes32(rights | uint256(expiration));
}
function _delegationMatchesRequest(IDelegateRegistry.Delegation memory delegation, bytes24 rights)
internal
view
returns (bool)
{
(bytes24 rightsIdentifier, uint40 expiration) = decodeRightsExpiration(delegation.rights);
if (block.timestamp > expiration) {
return false;
} else if (rightsIdentifier != rights && rightsIdentifier != GLOBAL_DELEGATION) {
return false;
} else if (delegation.type_ == IDelegateRegistry.DelegationType.ALL) {
return true;
} else {
return false;
}
}
function _delegationMatchesRequest(
IDelegateRegistry.Delegation memory delegation,
address contractAddress,
uint256 tokenId,
bytes24 rights
) internal view returns (bool) {
// Extract rights identifier (remaining 192 bits)
(bytes24 rightsIdentifier, uint40 expiration) = decodeRightsExpiration(delegation.rights);
if (block.timestamp > expiration) {
return false;
} else if (rightsIdentifier != rights && rightsIdentifier != GLOBAL_DELEGATION) {
return false;
} else if (delegation.type_ == IDelegateRegistry.DelegationType.ALL) {
return true;
} else if (delegation.type_ == IDelegateRegistry.DelegationType.CONTRACT) {
return delegation.contract_ == contractAddress;
} else if (delegation.type_ == IDelegateRegistry.DelegationType.ERC721) {
return delegation.contract_ == contractAddress && delegation.tokenId == tokenId;
} else {
return false;
}
}
function _delegationOutranksCurrent(
IDelegateRegistry.Delegation memory currentDelegation,
IDelegateRegistry.Delegation memory newDelegation
) internal pure returns (bool) {
bytes24 currentRightsIdentifier = bytes24(currentDelegation.rights);
bytes24 newRightsIdentifier = bytes24(newDelegation.rights);
if (currentRightsIdentifier == newRightsIdentifier) {
return newDelegation.type_ > currentDelegation.type_;
} else if (currentRightsIdentifier == GLOBAL_DELEGATION) {
return true;
} else {
return false;
}
}
function _getOwner(address contractAddress, uint256 tokenId) internal view returns (address owner) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x6352211e00000000000000000000000000000000000000000000000000000000)
mstore(add(m, 0x04), tokenId)
let success := staticcall(gas(), contractAddress, m, 0x24, m, 0x20)
if iszero(success) {
mstore(0x00, 0x3204506f) // CallFailed()
revert(0x1c, 0x04)
}
owner := mload(m)
}
}
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;
/**
* @title IDelegateRegistry
* @custom:version 2.0
* @custom:author foobar (0xfoobar)
* @notice A standalone immutable registry storing delegated permissions from one address to another
*/
interface IDelegateRegistry {
/// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
enum DelegationType {
NONE,
ALL,
CONTRACT,
ERC721,
ERC20,
ERC1155
}
/// @notice Struct for returning delegations
struct Delegation {
DelegationType type_;
address to;
address from;
bytes32 rights;
address contract_;
uint256 tokenId;
uint256 amount;
}
/// @notice Emitted when an address delegates or revokes rights for their entire wallet
event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);
/// @notice Emitted when an address delegates or revokes rights for a contract address
event DelegateContract(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
event DelegateERC721(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
bool enable
);
/// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
event DelegateERC20(
address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount
);
/// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
event DelegateERC1155(
address indexed from,
address indexed to,
address indexed contract_,
uint256 tokenId,
bytes32 rights,
uint256 amount
);
/// @notice Thrown if multicall calldata is malformed
error MulticallFailed();
/**
* ----------- WRITE -----------
*/
/**
* @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
* @param data The encoded function data for each of the calls to make to this contract
* @return results The results from each of the calls passed in via data
*/
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
* @param to The address to act as delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateContract(address to, address contract_, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
* @param to The address to act as delegate
* @param contract_ The contract whose rights are being delegated
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param enable Whether to enable or disable this delegation, true delegates and false revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address for the fungible token contract
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
* @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
* @param to The address to act as delegate
* @param contract_ The address of the contract that holds the token
* @param tokenId The token id to delegate
* @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
* @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
* @return delegationHash The unique identifier of the delegation
*/
function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount)
external
payable
returns (bytes32 delegationHash);
/**
* ----------- CHECKS -----------
*/
/**
* @notice Check if `to` is a delegate of `from` for the entire wallet
* @param to The potential delegate address
* @param from The potential address who delegated rights
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on the from's behalf
*/
function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);
/**
* @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
* @param to The delegated address to check
* @param contract_ The specific contract address being checked
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
*/
function checkDelegateForContract(address to, address from, address contract_, bytes32 rights)
external
view
returns (bool);
/**
* @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
* @param to The delegated address to check
* @param contract_ The specific contract address being checked
* @param tokenId The token id for the token to delegating
* @param from The wallet that issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
*/
function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (bool);
/**
* @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
* @param to The delegated address to check
* @param contract_ The address of the token contract
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return balance The delegated balance, which will be 0 if the delegation does not exist
*/
function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights)
external
view
returns (uint256);
/**
* @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
* @param to The delegated address to check
* @param contract_ The address of the token contract
* @param tokenId The token id to check the delegated amount of
* @param from The cold wallet who issued the delegation
* @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
* @return balance The delegated balance, which will be 0 if the delegation does not exist
*/
function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights)
external
view
returns (uint256);
/**
* ----------- ENUMERATIONS -----------
*/
/**
* @notice Returns all enabled delegations a given delegate has received
* @param to The address to retrieve delegations for
* @return delegations Array of Delegation structs
*/
function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);
/**
* @notice Returns all enabled delegations an address has given out
* @param from The address to retrieve delegations for
* @return delegations Array of Delegation structs
*/
function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
/**
* @notice Returns all hashes associated with enabled delegations an address has received
* @param to The address to retrieve incoming delegation hashes for
* @return delegationHashes Array of delegation hashes
*/
function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);
/**
* @notice Returns all hashes associated with enabled delegations an address has given out
* @param from The address to retrieve outgoing delegation hashes for
* @return delegationHashes Array of delegation hashes
*/
function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);
/**
* @notice Returns the delegations for a given array of delegation hashes
* @param delegationHashes is an array of hashes that correspond to delegations
* @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
*/
function getDelegationsFromHashes(bytes32[] calldata delegationHashes)
external
view
returns (Delegation[] memory delegations);
/**
* ----------- STORAGE ACCESS -----------
*/
/**
* @notice Allows external contracts to read arbitrary storage slots
*/
function readSlot(bytes32 location) external view returns (bytes32);
/**
* @notice Allows external contracts to read an arbitrary array of storage slots
*/
function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
}