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