Skip to content

Commit

Permalink
feat: add reverse lookup function for delegated wallets by rights (#1)
Browse files Browse the repository at this point in the history
* feat: add reverse lookup function for delegated wallets by rights

* test updates

* update wf

* wf

* fmt

* wf
  • Loading branch information
coffeexcoin authored Jan 9, 2025
1 parent 9fd64a5 commit 15d9fe1
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 6 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: CI

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

Expand All @@ -10,12 +12,10 @@ env:

jobs:
test:
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
check:
strategy:
fail-fast: true

env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
name: Foundry project
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ docs/

# Dotenv file
.env

# zksolc
zkout/
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ libs = ["lib"]

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
abstract = "${ABSTRACT_RPC_URL}"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
192 changes: 192 additions & 0 deletions lcov.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
TN:
SF:script/Deploy.s.sol
DA:24,0
FN:24,Deploy.setUp
FNDA:0,Deploy.setUp
DA:26,0
FN:26,Deploy.run
FNDA:0,Deploy.run
DA:27,0
DA:31,0
DA:32,0
DA:33,0
FNF:2
FNH:0
LF:6
LH:0
BRF:0
BRH:0
end_of_record
TN:
SF:src/ExclusiveDelegateResolver.sol
DA:39,1
FN:39,ExclusiveDelegateResolver.exclusiveWalletByRights
FNDA:1,ExclusiveDelegateResolver.exclusiveWalletByRights
DA:40,4
DA:41,4
DA:43,4
DA:45,4
DA:47,4
DA:49,4
DA:51,4
BRDA:51,0,0,4
DA:52,4
BRDA:52,1,0,4
DA:55,4
DA:56,4
BRDA:56,2,0,4
DA:57,4
DA:59,0
DA:64,0
DA:79,1
FN:79,ExclusiveDelegateResolver.delegatedWalletsByRights
FNDA:1,ExclusiveDelegateResolver.delegatedWalletsByRights
DA:80,1
DA:81,1
DA:83,1
DA:84,1
DA:86,1
DA:87,3
DA:89,3
BRDA:89,3,0,3
DA:90,3
BRDA:90,4,0,2
DA:91,2
DA:92,2
DA:97,1
DA:98,1
DA:100,1
DA:101,2
BRDA:101,5,0,2
DA:102,2
DA:103,2
DA:106,1
DA:123,13
FN:123,ExclusiveDelegateResolver.exclusiveOwnerByRights
FNDA:13,ExclusiveDelegateResolver.exclusiveOwnerByRights
DA:128,13
DA:130,12
DA:131,12
DA:133,12
DA:135,12
DA:137,15
DA:139,15
DA:141,15
BRDA:141,6,0,10
DA:142,10
BRDA:142,7,0,9
DA:145,9
DA:146,6
DA:147,5
BRDA:147,8,0,5
DA:148,5
DA:151,4
DA:156,7
DA:165,1
FN:165,ExclusiveDelegateResolver.decodeRightsExpiration
FNDA:1,ExclusiveDelegateResolver.decodeRightsExpiration
DA:166,23
DA:167,23
DA:169,23
DA:178,33
FN:178,ExclusiveDelegateResolver.generateRightsWithExpiration
FNDA:33,ExclusiveDelegateResolver.generateRightsWithExpiration
DA:183,33
DA:184,33
DA:187,7
FN:187,ExclusiveDelegateResolver._delegationMatchesRequest
FNDA:7,ExclusiveDelegateResolver._delegationMatchesRequest
DA:192,7
DA:194,7
BRDA:194,9,0,-
BRDA:194,9,1,-
DA:195,0
DA:196,7
BRDA:196,10,0,-
BRDA:196,10,1,-
DA:197,0
DA:198,7
BRDA:198,11,0,7
BRDA:198,11,1,-
DA:199,7
DA:201,0
DA:205,15
FN:205,ExclusiveDelegateResolver._delegationMatchesRequest
FNDA:15,ExclusiveDelegateResolver._delegationMatchesRequest
DA:212,15
DA:214,15
BRDA:214,12,0,1
BRDA:214,12,1,6
DA:215,1
DA:216,14
BRDA:216,13,0,2
BRDA:216,13,1,6
DA:217,2
DA:218,12
BRDA:218,14,0,-
BRDA:218,14,1,6
DA:219,0
DA:220,12
BRDA:220,15,0,4
BRDA:220,15,1,6
DA:221,4
DA:222,8
BRDA:222,16,0,6
BRDA:222,16,1,6
DA:223,6
DA:225,2
DA:229,14
FN:229,ExclusiveDelegateResolver._delegationOutranksCurrent
FNDA:14,ExclusiveDelegateResolver._delegationOutranksCurrent
DA:233,14
DA:234,14
DA:236,14
BRDA:236,17,0,4
BRDA:236,17,1,-
DA:237,4
DA:238,10
BRDA:238,18,0,10
BRDA:238,18,1,-
DA:239,10
DA:241,0
DA:245,13
FN:245,ExclusiveDelegateResolver._getOwner
FNDA:13,ExclusiveDelegateResolver._getOwner
DA:248,13
DA:249,13
DA:250,13
DA:251,13
DA:252,13
BRDA:252,19,0,1
DA:253,1
DA:254,1
DA:256,12
FNF:9
FNH:9
LF:94
LH:87
BRF:30
BRH:22
end_of_record
TN:
SF:test/mocks/MockERC721.sol
DA:8,11
FN:8,MockERC721.mint
FNDA:11,MockERC721.mint
DA:9,11
DA:10,11
DA:13,12
FN:13,MockERC721.ownerOf
FNDA:12,MockERC721.ownerOf
DA:14,12
DA:17,0
FN:17,MockERC721.balanceOf
FNDA:0,MockERC721.balanceOf
DA:18,0
FNF:3
FNH:2
LF:7
LH:5
BRF:0
BRH:0
end_of_record
44 changes: 43 additions & 1 deletion src/ExclusiveDelegateResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract ExclusiveDelegateResolver {
* 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) external view returns (address) {
function exclusiveWalletByRights(address vault, bytes24 rights) public view returns (address) {
IDelegateRegistry.Delegation[] memory delegations =
IDelegateRegistry(DELEGATE_REGISTRY).getOutgoingDelegations(vault);

Expand Down Expand Up @@ -64,6 +64,48 @@ contract ExclusiveDelegateResolver {
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
Expand Down
41 changes: 40 additions & 1 deletion test/ExclusiveDelegateResolver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ contract ExclusiveDelegateResolverTest is Test {

address public coldWallet;
address public hotWallet;
address public additionalDelegatee1;
address public additionalDelegatee2;
bytes24 public constant RIGHTS = bytes24(keccak256("NFT_SHADOW"));
uint40 public constant FUTURE_EXPIRATION = type(uint40).max;
uint40 public constant PAST_EXPIRATION = 1;
Expand All @@ -25,16 +27,22 @@ contract ExclusiveDelegateResolverTest is Test {
function setUp() public {
// fork mainnet at block 21388707
vm.createSelectFork("mainnet", 21388707);
vm.etch(
address(0x6b176c958fb89Ddca0fc8214150DA4c4D0a32fbe),
address(0x00000000000000447e69651d841bD8D104Bed493).code
);

resolver = new ExclusiveDelegateResolver();
mockERC721 = new MockERC721();
delegateRegistry = IDelegateRegistry(0x00000000000000447e69651d841bD8D104Bed493);
delegateRegistry = IDelegateRegistry(0x6b176c958fb89Ddca0fc8214150DA4c4D0a32fbe);

rightsWithFutureExpiration = resolver.generateRightsWithExpiration(RIGHTS, FUTURE_EXPIRATION);
rightsWithPastExpiration = resolver.generateRightsWithExpiration(RIGHTS, PAST_EXPIRATION);

hotWallet = makeAddr("hotWallet");
coldWallet = makeAddr("coldWallet");
additionalDelegatee1 = makeAddr("additionalDelegatee1");
additionalDelegatee2 = makeAddr("additionalDelegatee2");
}

function testERC721Delegation() public {
Expand Down Expand Up @@ -77,6 +85,37 @@ contract ExclusiveDelegateResolverTest is Test {
assertEq(proxiedOwner, hotWallet);
}

function testExclusiveWalletByRights() public {
// Set global delegation after so that it is newer
bytes32 delegation = resolver.generateRightsWithExpiration(RIGHTS, FUTURE_EXPIRATION);

vm.prank(coldWallet);
delegateRegistry.delegateAll(hotWallet, delegation, true);

assertEq(hotWallet, resolver.exclusiveWalletByRights(coldWallet, RIGHTS));
}

function testDelegatedWalletsByRights() public {
bytes32 delegation = resolver.generateRightsWithExpiration(RIGHTS, FUTURE_EXPIRATION);

vm.prank(coldWallet);
delegateRegistry.delegateAll(hotWallet, delegation, true);

vm.prank(additionalDelegatee1);
delegateRegistry.delegateAll(hotWallet, delegation, true);

vm.prank(additionalDelegatee2);
delegateRegistry.delegateAll(hotWallet, delegation, true);

vm.prank(coldWallet);
delegateRegistry.delegateAll(additionalDelegatee1, delegation, true);

address[] memory delegatedWallets = resolver.delegatedWalletsByRights(hotWallet, RIGHTS);
assertEq(delegatedWallets.length, 2);
assertEq(delegatedWallets[0], additionalDelegatee1);
assertEq(delegatedWallets[1], additionalDelegatee2);
}

function testRightsDelegationOutranksGlobalDelegation() public {
uint256 tokenId = 1;

Expand Down

0 comments on commit 15d9fe1

Please sign in to comment.