├── .github
└── workflows
│ ├── forge-test.yml
│ └── foundry-gas-diff.yml
├── .gitignore
├── .gitmodules
├── Makefile
├── README.md
├── foundry.toml
├── gasbenchmark10mil
├── gasbenchmark200
├── gasreport200
├── hashbenchmark10mil
├── logo_text_black.png
├── remappings.txt
├── script
└── Deploy.s.sol
├── src
├── DelegateRegistry.sol
├── IDelegateRegistry.sol
├── examples
│ ├── Airdrop.sol
│ ├── DelegateClaim.sol
│ └── IPLicenseCheck.sol
├── libraries
│ ├── RegistryHashes.sol
│ ├── RegistryOps.sol
│ └── RegistryStorage.sol
└── singlesig
│ └── Singlesig.sol
└── test
├── DelegateRegistry.t.sol
├── GasBenchmark.t.sol
├── HashBenchmark.t.sol
├── InitCodeHash.t.sol
├── RegistryHashTests.t.sol
├── RegistryOpsTests.t.sol
├── RegistrySingularIntegrations.t.sol
├── RegistryStorageTests.t.sol
├── RegistryUnitTests.t.sol
├── examples
├── Airdrop.t.sol
└── IPLicenseCheck.t.sol
└── tools
└── RegistryHarness.sol
/.github/workflows/forge-test.yml:
--------------------------------------------------------------------------------
1 | name: Forge Tests
2 |
3 | env:
4 | FOUNDRY_PROFILE: "ci"
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | forge-tests:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 |
14 | - name: Install Foundry
15 | uses: onbjerg/foundry-toolchain@v1
16 | with:
17 | version: nightly
18 |
19 | - name: Install dependencies
20 | run: forge install
21 | # NOTE: must run `forge update` explicitly, repo uses cached deps instead of pulling fresh
22 |
23 | - name: Check formatting
24 | run: forge fmt --check
25 |
26 | - name: Run forge tests
27 | # env:
28 | # ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }}
29 | # run: forge test --fork-url "https://eth-mainnet.alchemyapi.io/v2/$ALCHEMY_API_KEY"
30 | run: forge test --ffi -vvv
--------------------------------------------------------------------------------
/.github/workflows/foundry-gas-diff.yml:
--------------------------------------------------------------------------------
1 | name: Report Gas Diff
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - v2
8 | pull_request:
9 | # Optionally configure to run only for changes in specific files. For example:
10 | # paths:
11 | # - src/**
12 | # - test/**
13 | # - foundry.toml
14 | # - remappings.txt
15 | # - .github/workflows/foundry-gas-diff.yml
16 |
17 | jobs:
18 | compare_gas_reports:
19 | runs-on: ubuntu-latest
20 | permissions:
21 | pull-requests: write
22 | steps:
23 | - uses: actions/checkout@v4
24 | with:
25 | submodules: recursive
26 |
27 | - name: Install Foundry
28 | uses: onbjerg/foundry-toolchain@v1
29 | with:
30 | version: nightly
31 |
32 | # Add any step generating a gas report to a temporary file named gasreport.ansi. For example:
33 | - name: Run tests
34 | run: forge test --match-contract GasBenchmark --gas-report > gasreport.ansi # <- this file name should be unique in your repository!
35 | env:
36 | # make fuzzing semi-deterministic to avoid noisy gas cost estimation
37 | # due to non-deterministic fuzzing (but still use pseudo-random fuzzing seeds)
38 | FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }}
39 |
40 | - name: Compare gas reports
41 | uses: Rubilmax/foundry-gas-diff@v3.20
42 | with:
43 | summaryQuantile: 0.0 # display all the most significant gas diffs in the summary (defaults to 20%)
44 | sortCriteria: avg,max # sort diff rows by criteria
45 | sortOrders: desc,asc # and directions
46 | ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default)
47 | id: gas_diff
48 |
49 | - name: Add gas diff to sticky comment
50 | if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
51 | uses: marocchino/sticky-pull-request-comment@v2
52 | with:
53 | # delete the comment in case changes no longer impact gas costs
54 | delete: ${{ !steps.gas_diff.outputs.markdown }}
55 | message: ${{ steps.gas_diff.outputs.markdown }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cache/
2 | out/
3 | broadcast/
4 | Makefile
5 | abi.json
6 | .vscode/
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | branch = master
5 | [submodule "lib/openzeppelin-contracts"]
6 | path = lib/openzeppelin-contracts
7 | url = https://github.com/openzeppelin/openzeppelin-contracts
8 | branch = release-v4.9 # Master branch is not safe to use
9 | [submodule "lib/murky"]
10 | path = lib/murky
11 | url = https://github.com/dmfxyz/murky
12 | branch = main
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | simulate-deploy:
2 | # For live deployment, add --broadcast --verify --delay 30 --etherscan-api-key ${ETHERSCAN_API_KEY}
3 | forge script -vvv script/Deploy.s.sol --sig "run()" --rpc-url ${RPC_URL} --private-key ${PK} --broadcast --verify --delay 30 --etherscan-api-key ${ETHERSCAN_API_KEY}
4 |
5 | verify:
6 | forge verify-contract 0x00000000000000447e69651d841bD8D104Bed493 src/DelegateRegistry.sol:DelegateRegistry --chain 81457 --etherscan-api-key ${ETHERSCAN_API_KEY} --retries 5 --delay 30 --watch
7 |
8 | cast-deploy-singlesig:
9 | cast send --rpc-url ${RPC_URL} --private-key ${PK} 0x0000000000ffe8b47b3e2130213b802212439497 0x64e03087000000000000000000000000000000000000000016c7768a8c7a2824b846321d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000bf0608060405234801561001057600080fd5b50604051610bd0380380610bd083398101604081905261002f9161007d565b600080546001600160a01b0319166001600160a01b03831690811782556040519091907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a3506100ad565b60006020828403121561008f57600080fd5b81516001600160a01b03811681146100a657600080fd5b9392505050565b610b14806100bc6000396000f3fe6080604052600436106100915760003560e01c8063b61d27f611610063578063e30c39781161004b578063e30c397814610214578063f23a6e6114610241578063f2fde38b1461028757005b8063b61d27f6146101ac578063bc197c81146101cc57005b806301ffc9a71461009a578063150b7a02146100cf57806379ba5097146101455780638da5cb5b1461015a57005b3661009857005b005b3480156100a657600080fd5b506100ba6100b5366004610703565b6102a7565b60405190151581526020015b60405180910390f35b3480156100db57600080fd5b506101146100ea3660046107be565b7f150b7a020000000000000000000000000000000000000000000000000000000095945050505050565b6040517fffffffff0000000000000000000000000000000000000000000000000000000090911681526020016100c6565b34801561015157600080fd5b5061009861038c565b34801561016657600080fd5b506000546101879073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100c6565b3480156101b857600080fd5b506100ba6101c736600461085c565b6104cb565b3480156101d857600080fd5b506101146101e736600461098a565b7fbc197c810000000000000000000000000000000000000000000000000000000098975050505050505050565b34801561022057600080fd5b506001546101879073ffffffffffffffffffffffffffffffffffffffff1681565b34801561024d57600080fd5b5061011461025c366004610a45565b7ff23a6e61000000000000000000000000000000000000000000000000000000009695505050505050565b34801561029357600080fd5b506100986102a2366004610abd565b6105e6565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316148061033a57507f150b7a02000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b8061038657507f4e2312e0000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610438576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f74207468652060448201527f6e6577206f776e6572000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b6001546000805460405173ffffffffffffffffffffffffffffffffffffffff93841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600154600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909216919091179055565b6000805473ffffffffffffffffffffffffffffffffffffffff163314610573576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f74207468652060448201527f6f776e6572000000000000000000000000000000000000000000000000000000606482015260840161042f565b8373ffffffffffffffffffffffffffffffffffffffff1683836040516105999190610ad8565b60006040518083038185875af1925050503d80600081146105d6576040519150601f19603f3d011682016040523d82523d6000602084013e6105db565b606091505b509095945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461068d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f74207468652060448201527f6f776e6572000000000000000000000000000000000000000000000000000000606482015260840161042f565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227009190a350565b60006020828403121561071557600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461074557600080fd5b9392505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461077057600080fd5b919050565b60008083601f84011261078757600080fd5b50813567ffffffffffffffff81111561079f57600080fd5b6020830191508360208285010111156107b757600080fd5b9250929050565b6000806000806000608086880312156107d657600080fd5b6107df8661074c565b94506107ed6020870161074c565b935060408601359250606086013567ffffffffffffffff81111561081057600080fd5b61081c88828901610775565b969995985093965092949392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561087157600080fd5b61087a8461074c565b925060208401359150604084013567ffffffffffffffff8082111561089e57600080fd5b818601915086601f8301126108b257600080fd5b8135818111156108c4576108c461082d565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561090a5761090a61082d565b8160405282815289602084870101111561092357600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b60008083601f84011261095757600080fd5b50813567ffffffffffffffff81111561096f57600080fd5b6020830191508360208260051b85010111156107b757600080fd5b60008060008060008060008060a0898b0312156109a657600080fd5b6109af8961074c565b97506109bd60208a0161074c565b9650604089013567ffffffffffffffff808211156109da57600080fd5b6109e68c838d01610945565b909850965060608b01359150808211156109ff57600080fd5b610a0b8c838d01610945565b909650945060808b0135915080821115610a2457600080fd5b50610a318b828c01610775565b999c989b5096995094979396929594505050565b60008060008060008060a08789031215610a5e57600080fd5b610a678761074c565b9550610a756020880161074c565b94506040870135935060608701359250608087013567ffffffffffffffff811115610a9f57600080fd5b610aab89828a01610775565b979a9699509497509295939492505050565b600060208284031215610acf57600080fd5b6107458261074c565b6000825160005b81811015610af95760208186018101518583015201610adf565b50600092019182525091905056fea164736f6c6343000815000a0000000000000000000000006ed7d526b020780f694f3c10dfb25e1b134d321500000000000000000000000000000000
10 |
11 | cast-deploy-registry-v1:
12 | # If you get `custom error: EIP-1559 not activated`, then add the --legacy flag
13 | cast send --rpc-url ${RPC_URL} --private-key ${PK} 0x0000000000ffe8b47b3e2130213b802212439497 0x64e0308700000000000000000000000000000000000000008b99e5a778edb0257201000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001876608060405234801561001057600080fd5b50611856806100206000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c8063685ee3e811610097578063aba69cf811610066578063aba69cf81461021c578063ed4b878e1461022f578063f956cf9414610242578063fa352c001461026257600080fd5b8063685ee3e8146101c35780636f007d87146101d657806390c9a2d0146101f65780639c395bc21461020957600080fd5b806336137872116100d3578063361378721461017557806349c95d291461017d5780634fc6928214610190578063537a5c3d146101b057600080fd5b806301ffc9a7146101055780631221156b1461012d5780631b61f6751461014d578063219044b014610160575b600080fd5b61011861011336600461138f565b610275565b60405190151581526020015b60405180910390f35b61014061013b3660046113d5565b6102ac565b6040516101249190611411565b61014061015b36600461145e565b6102c3565b61017361016e36600461145e565b6102d3565b005b6101736102e0565b61017361018b366004611489565b610336565b6101a361019e36600461145e565b6103ad565b60405161012491906114e2565b6101736101be36600461157f565b6105e8565b6101736101d13660046115cc565b610667565b6101e96101e436600461145e565b6106d4565b60405161012491906115ff565b610118610204366004611665565b61084d565b61011861021736600461169f565b6108f8565b61011861022a3660046116c9565b61097e565b61014061023d36600461169f565b610a33565b61025561025036600461145e565b610a4a565b6040516101249190611714565b61017361027036600461145e565b610baa565b60006001600160e01b03198216630596d3d560e01b14806102a657506301ffc9a760e01b6001600160e01b03198316145b92915050565b60606102bb8460038585610bb4565b949350505050565b60606102a6826001600080610bb4565b6102dd3382610e2e565b50565b33600090815260016020526040812080549091906102fd90611779565b909155506040513381527f32d74befd0b842e19694e3e3af46263e18bcce41352c8b600ff0002b49edf6629060200160405180910390a1565b6000610343338585610eac565b9050610356848284600233886000610f1e565b604080513381526001600160a01b038681166020830152851681830152831515606082015290517f8d6b2f5255b8d815cc368855b2251146e003bf4e2fcccaec66145fff5c174b4f9181900360800190a150505050565b6001600160a01b03811660009081526003602052604081206060916103d182611100565b905060008167ffffffffffffffff8111156103ee576103ee611792565b60405190808252806020026020018201604052801561044757816020015b6040805160a08101825260008082526020808301829052928201819052606082018190526080820152825260001990920191018161040c5790505b50935060005b828110156105cd576000610461858361110a565b600081815260046020526040808220815160a0810190925280549394509192909190829060ff166003811115610499576104996114cc565b60038111156104aa576104aa6114cc565b815281546001600160a01b03610100909104811660208084019190915260018085015483166040850152600285015490921660608401526003909301546080909201919091529082015182519293509190600090826003811115610510576105106114cc565b0361052e5761051f838c611116565b8503610529575060015b61058d565b6002826003811115610542576105426114cc565b036105565761051f838c8660600151610eac565b600382600381111561056a5761056a6114cc565b0361058d57610583838c86606001518760800151611184565b850361058d575060015b80156105bd57838a8861059f81611779565b9950815181106105b1576105b16117a8565b60200260200101819052505b856001019550505050505061044d565b50808211156105e0578351818303900384525b505050919050565b60006105f633868686611184565b90506106088582846003338989610f1e565b604080513381526001600160a01b03878116602083015286168183015260608101859052831515608082015290517fe89c6ba1e8957285aed22618f52aa1dcb9d5bb64e1533d8b55136c72fcf5aa5d9181900360a00190a15050505050565b60006106733384611116565b9050610686838284600133600080610f1e565b604080513381526001600160a01b03851660208201528315158183015290517f58781eab4a0743ab1c285a238be846a235f06cdb5b968030573a635e5f8c92fa9181900360600190a1505050565b6001600160a01b03811660009081526020818152604080832060018352818420548452909152812060609161070882611100565b905060008167ffffffffffffffff81111561072557610725611792565b60405190808252806020026020018201604052801561077057816020015b60408051606081018252600080825260208083018290529282015282526000199092019101816107435790505b50935060005b828110156105cd57600061078a858361110a565b60008181526004602052604090209091506003815460ff1660038111156107b3576107b36114cc565b03610843576001810154600282015460038301546107e1928b926001600160a01b0391821692911690611184565b8203610843576040805160608101825260028301546001600160a01b0390811682526003840154602083015260018401541691810191909152878561082581611779565b965081518110610837576108376117a8565b60200260200101819052505b5050600101610776565b6001600160a01b03828116600081815260016020908152604080832054600283528184208987168086529084528285205483518086019290925281840187905296881660608201526080810182905260a0808201979097528251808203909701875260c00182528551958301959095209383528282528083209483529390529182206108d990826111f3565b6108ec576108e785856108f8565b6108ef565b60015b95945050505050565b6001600160a01b038181166000818152600160209081526040808320546002835281842095881680855295835281842054825180850197909752868301869052606087018290526080808801919091528251808803909101815260a090960182528551958301959095209383528282528083209483529390529182206102bb90826111f3565b6001600160a01b03808416600090815260016020908152604080832054600283528184209489168452938252808320549051929384936109c9938a938a938a938a93919291016117be565b60408051601f1981840301815291815281516020928301206001600160a01b0388166000908152808452828120600185528382205482529093529120909150610a1290826111f3565b610a2657610a2186868661084d565b610a29565b60015b9695505050505050565b6060610a43836002846000610bb4565b9392505050565b6001600160a01b038116600090815260208181526040808320600183528184205484529091528120606091610a7e82611100565b905060008167ffffffffffffffff811115610a9b57610a9b611792565b604051908082528060200260200182016040528015610ae057816020015b6040805180820190915260008082526020820152815260200190600190039081610ab95790505b50935060005b828110156105cd576000610afa858361110a565b60008181526004602052604090209091506002815460ff166003811115610b2357610b236114cc565b03610ba05760018101546002820154610b4a918a916001600160a01b039182169116610eac565b8203610ba0576040805180820190915260028201546001600160a01b03908116825260018301541660208201528785610b8281611779565b965081518110610b9457610b946117a8565b60200260200101819052505b5050600101610ae6565b6102dd8133610e2e565b6001600160a01b038416600090815260208181526040808320600183528184205484529091528120606091610be882611100565b905060008167ffffffffffffffff811115610c0557610c05611792565b604051908082528060200260200182016040528015610c2e578160200160208202803683370190505b50935060005b82811015610e10576000610c48858361110a565b6000818152600460205260409020909150896003811115610c6b57610c6b6114cc565b815460ff166003811115610c8157610c816114cc565b03610e065760018a6003811115610c9a57610c9a6114cc565b03610d0f576001810154610cb8908c906001600160a01b0316611116565b8203610d0a5760018101546001600160a01b03168785610cd781611779565b965081518110610ce957610ce96117a8565b60200260200101906001600160a01b031690816001600160a01b0316815250505b610e06565b60028a6003811115610d2357610d236114cc565b03610d595760028101546001600160a01b03808b16911603610d0a576001810154610cb8908c906001600160a01b03168b610eac565b60038a6003811115610d6d57610d6d6114cc565b03610e065760028101546001600160a01b038a81169116148015610d945750878160030154145b15610e06576001810154610db4908c906001600160a01b03168b8b611184565b8203610e065760018101546001600160a01b03168785610dd381611779565b965081518110610de557610de56117a8565b60200260200101906001600160a01b031690816001600160a01b0316815250505b5050600101610c34565b5080821115610e23578351818303900384525b505050949350505050565b6001600160a01b03808216600090815260026020908152604080832093861683529290529081208054909190610e6390611779565b90915550604080516001600160a01b03831681523360208201527f3e34a3ee53064fb79c0ee57448f03774a627a9270b0c41286efb7d8e32dcde93910160405180910390a15050565b6001600160a01b0392831660008181526001602090815260408083205460028352818420968816808552968352928190205481518084019790975286820194909452939095166060850152608084015260a0808401919091528151808403909101815260c09092019052805191012090565b8415611060576001600160a01b038316600090815260208181526040808320600183528184205484529091529020610f56908761120b565b506001600160a01b0387166000908152600360205260409020610f79908761120b565b506040518060a00160405280856003811115610f9757610f976114cc565b81526001600160a01b038086166020808401919091528a82166040808501919091529186166060840152608090920184905260008981526004909252902081518154829060ff19166001836003811115610ff357610ff36114cc565b0217905550602082015181546001600160a01b0391821661010002610100600160a81b031990911617825560408301516001830180549183166001600160a01b031992831617905560608401516002840180549190931691161790556080909101516003909101556110f7565b6001600160a01b0383166000908152602081815260408083206001835281842054845290915290206110929087611217565b506001600160a01b03871660009081526003602052604090206110b59087611217565b50600086815260046020526040812080546001600160a81b03191681556001810180546001600160a01b03199081169091556002820180549091169055600301555b50505050505050565b60006102a6825490565b6000610a438383611223565b6001600160a01b03918216600081815260016020908152604080832054600283528184209590961680845294825291829020548251808301959095528483019390935260608401949094526080808401929092528051808403909201825260a0909201909152805191012090565b6001600160a01b0380851660009081526001602090815260408083205460028352818420948816845293825280832054905192939290916111d19188918a918991899188918891016117be565b6040516020818303038152906040528051906020012092505050949350505050565b60008181526001830160205260408120541515610a43565b6000610a43838361124d565b6000610a43838361129c565b600082600001828154811061123a5761123a6117a8565b9060005260206000200154905092915050565b6000818152600183016020526040812054611294575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556102a6565b5060006102a6565b600081815260018301602052604081205480156113855760006112c06001836117f7565b85549091506000906112d4906001906117f7565b90508181146113395760008660000182815481106112f4576112f46117a8565b9060005260206000200154905080876000018481548110611317576113176117a8565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061134a5761134a61180a565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506102a6565b60009150506102a6565b6000602082840312156113a157600080fd5b81356001600160e01b031981168114610a4357600080fd5b80356001600160a01b03811681146113d057600080fd5b919050565b6000806000606084860312156113ea57600080fd5b6113f3846113b9565b9250611401602085016113b9565b9150604084013590509250925092565b6020808252825182820181905260009190848201906040850190845b818110156114525783516001600160a01b03168352928401929184019160010161142d565b50909695505050505050565b60006020828403121561147057600080fd5b610a43826113b9565b803580151581146113d057600080fd5b60008060006060848603121561149e57600080fd5b6114a7846113b9565b92506114b5602085016113b9565b91506114c360408501611479565b90509250925092565b634e487b7160e01b600052602160045260246000fd5b60208082528251828201819052600091906040908185019086840185805b838110156115715782518051600480821061152857634e487b7160e01b855260218152602485fd5b508652808801516001600160a01b039081168988015287820151811688880152606080830151909116908701526080908101519086015260a09094019391860191600101611500565b509298975050505050505050565b6000806000806080858703121561159557600080fd5b61159e856113b9565b93506115ac602086016113b9565b9250604085013591506115c160608601611479565b905092959194509250565b600080604083850312156115df57600080fd5b6115e8836113b9565b91506115f660208401611479565b90509250929050565b602080825282518282018190526000919060409081850190868401855b8281101561165857815180516001600160a01b03908116865287820151888701529086015116858501526060909301929085019060010161161c565b5091979650505050505050565b60008060006060848603121561167a57600080fd5b611683846113b9565b9250611691602085016113b9565b91506114c3604085016113b9565b600080604083850312156116b257600080fd5b6116bb836113b9565b91506115f6602084016113b9565b600080600080608085870312156116df57600080fd5b6116e8856113b9565b93506116f6602086016113b9565b9250611704604086016113b9565b9396929550929360600135925050565b602080825282518282018190526000919060409081850190868401855b8281101561165857815180516001600160a01b0390811686529087015116868501529284019290850190600101611731565b634e487b7160e01b600052601160045260246000fd5b60006001820161178b5761178b611763565b5060010190565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6001600160a01b03968716815294861660208601529290941660408401526060830152608082019290925260a081019190915260c00190565b818103818111156102a6576102a6611763565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206b0b0a636b8da72fa85cb1817045c30ca104b227b960f8aae85f0c81f76bd66764736f6c6343000811003300000000000000000000
14 |
15 | cast-deploy-registry-v2:
16 | # If you get `custom error: EIP-1559 not activated`, then add the --legacy flag
17 | cast send --rpc-url ${RPC_URL} --private-key ${PK} 0x0000000000ffe8b47b3e2130213b802212439497 0x64e0308700000000000000000000000000000000000000002bbc593dd77cb93fbb932d5f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000275e608060405234801561001057600080fd5b5061273e806100206000396000f3fe60806040526004361061015e5760003560e01c80638988eea9116100c0578063b9f3687411610074578063d90e73ab11610059578063d90e73ab14610383578063e839bd5314610396578063e8e834a9146103b657600080fd5b8063b9f3687414610343578063ba63c8171461036357600080fd5b8063ac9650d8116100a5578063ac9650d8146102f0578063b18e2bbb14610310578063b87058751461032357600080fd5b80638988eea9146102bd578063ab764683146102dd57600080fd5b806335faa416116101175780634705ed38116100fc5780634705ed381461025d57806351525e9a1461027d57806361451a301461029d57600080fd5b806335faa4161461021957806342f87c251461023057600080fd5b806301ffc9a71161014857806301ffc9a7146101b6578063063182a5146101e657806330ff31401461020657600080fd5b80623c2ba61461016357806301a920a014610189575b600080fd5b6101766101713660046120b4565b6103d5565b6040519081526020015b60405180910390f35b34801561019557600080fd5b506101a96101a43660046120f6565b610637565b6040516101809190612118565b3480156101c257600080fd5b506101d66101d136600461215c565b61066e565b6040519015158152602001610180565b3480156101f257600080fd5b506101a96102013660046120f6565b6106e1565b6101766102143660046121ae565b610712565b34801561022557600080fd5b5061022e6108f9565b005b34801561023c57600080fd5b5061025061024b3660046120f6565b610917565b6040516101809190612219565b34801561026957600080fd5b50610250610278366004612368565b610948565b34801561028957600080fd5b506102506102983660046120f6565b610bf0565b3480156102a957600080fd5b506101a96102b8366004612368565b610c21565b3480156102c957600080fd5b506101d66102d83660046123aa565b610cc6565b6101766102eb3660046123f5565b610dd8565b6103036102fe366004612368565b611056565b6040516101809190612442565b61017661031e366004612510565b61118d565b34801561032f57600080fd5b5061017661033e366004612567565b6113bd565b34801561034f57600080fd5b506101d661035e366004612567565b6115d8565b34801561036f57600080fd5b5061017661037e3660046123aa565b611767565b6101766103913660046125bc565b61192d565b3480156103a257600080fd5b506101d66103b1366004612609565b611b3f565b3480156103c257600080fd5b506101766103d1366004612645565b5490565b60408051603c810185905260288101869052336014820152838152605c902060081b6004176000818152602081905291909120805473ffffffffffffffffffffffffffffffffffffffff1683156105865773ffffffffffffffffffffffffffffffffffffffff81166104ec57336000818152600160208181526040808420805480850182559085528285200188905573ffffffffffffffffffffffffffffffffffffffff8c1680855260028352818520805480860182559086529290942090910187905589901b7bffffffffffffffff000000000000000000000000000000000000000016909217845560a088901b17908301556104d582600486910155565b84156104e7576104e782600287910155565b6105d4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff82160161055d5781547fffffffffffffffffffffffff000000000000000000000000000000000000000016331782556104e782600486910155565b3373ffffffffffffffffffffffffffffffffffffffff8216036104e7576104e782600486910155565b3373ffffffffffffffffffffffffffffffffffffffff8216036105d45781547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001178255600060048301555b604080518681526020810186905273ffffffffffffffffffffffffffffffffffffffff80891692908a169133917f6ebd000dfc4dc9df04f723f827bae7694230795e8f22ed4af438e074cc982d1891015b60405180910390a45050949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040902060609061066890611bc2565b92915050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083169081147f5f68bc5a0000000000000000000000000000000000000000000000000000000090911417610668565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260026020526040902060609061066890611bc2565b60408051602881018590523360148201528381526048902060081b6001176000818152602081905291909120805473ffffffffffffffffffffffffffffffffffffffff1683156108555773ffffffffffffffffffffffffffffffffffffffff81166107eb57336000818152600160208181526040808420805480850182559085528285200188905573ffffffffffffffffffffffffffffffffffffffff8b16808552600283529084208054808501825590855291909320018690559184559083015584156107e6576107e682600287910155565b61089c565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff8216016107e65781547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317825561089c565b3373ffffffffffffffffffffffffffffffffffffffff82160361089c5781547fffffffffffffffffffffffff00000000000000000000000000000000000000001660011782555b60408051868152851515602082015273ffffffffffffffffffffffffffffffffffffffff88169133917fda3ef6410e30373a9137f83f9781a8129962b6882532b7c229de2e39de423227910160405180910390a350509392505050565b6000806000804770de1e80ea5a234fb5488fee2584251bc7e85af150565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260026020526040902060609061066890611d41565b60608167ffffffffffffffff8111156109635761096361265e565b6040519080825280602002602001820160405280156109e857816020015b6040805160e08101825260008082526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816109815790505b50905060005b82811015610be9576000610a25858584818110610a0d57610a0d61268d565b90506020020135600090815260208190526040902090565b90506000610a47825473ffffffffffffffffffffffffffffffffffffffff1690565b9050610a528161200f565b15610ab6576040805160e08101909152806000815260006020820181905260408201819052606082018190526080820181905260a0820181905260c0909101528451859085908110610aa657610aa661268d565b6020026020010181905250610bdf565b815460018301546040805160e08101825273ffffffffffffffffffffffffffffffffffffffff83169360a09390931c9290911c73ffffffffffffffff00000000000000000000000016919091179080610b278a8a89818110610b1a57610b1a61268d565b9050602002013560ff1690565b6005811115610b3857610b386121ea565b81526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff168152602001610b80866002015490565b81526020018273ffffffffffffffffffffffffffffffffffffffff168152602001610bac866003015490565b8152602001610bbc866004015490565b815250868681518110610bd157610bd161268d565b602002602001018190525050505b50506001016109ee565b5092915050565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040902060609061066890611d41565b6060818067ffffffffffffffff811115610c3d57610c3d61265e565b604051908082528060200260200182016040528015610c66578160200160208202803683370190505b50915060008060005b83811015610cbc57868682818110610c8957610c8961268d565b9050602002013592508254915081858281518110610ca957610ca961268d565b6020908102919091010152600101610c6f565b5050505092915050565b6000610cd18461200f565b610dcc576040805160288101879052601481018690526000808252604890912060081b6001178152602081905220610d0a905b85612035565b80610d4a575060408051603c810185905260288101879052601481018690526000808252605c90912060081b6002178152602081905220610d4a90610d04565b9050801515821517610dcc576040805160288101879052601481018690528381526048902060081b6001176000908152602081905220610d8990610d04565b80610dc9575060408051603c81018590526028810187905260148101869052838152605c902060081b6002176000908152602081905220610dc990610d04565b90505b80151560005260206000f35b60408051605c8101859052603c810186905260288101879052336014820152838152607c902060081b6005176000818152602081905291909120805473ffffffffffffffffffffffffffffffffffffffff168315610f9c5773ffffffffffffffffffffffffffffffffffffffff8116610f0257336000818152600160208181526040808420805480850182559085528285200188905573ffffffffffffffffffffffffffffffffffffffff8d168085526002835281852080548086018255908652929094209091018790558a901b7bffffffffffffffff000000000000000000000000000000000000000016909217845560a089901b1790830155610edf82600388910155565b610eeb82600486910155565b8415610efd57610efd82600287910155565b610fea565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff821601610f735781547fffffffffffffffffffffffff00000000000000000000000000000000000000001633178255610efd82600486910155565b3373ffffffffffffffffffffffffffffffffffffffff821603610efd57610efd82600486910155565b3373ffffffffffffffffffffffffffffffffffffffff821603610fea5781547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001178255600060048301555b604080518781526020810187905290810185905273ffffffffffffffffffffffffffffffffffffffff80891691908a169033907f27ab1adc9bca76301ed7a691320766dfa4b4b1aa32c9e05cf789611be7f8c75f906060015b60405180910390a4505095945050505050565b60608167ffffffffffffffff8111156110715761107161265e565b6040519080825280602002602001820160405280156110a457816020015b606081526020019060019003908161108f5790505b5090506000805b8381101561118557308585838181106110c6576110c661268d565b90506020028101906110d891906126bc565b6040516110e6929190612721565b600060405180830381855af49150503d8060008114611121576040519150601f19603f3d011682016040523d82523d6000602084013e611126565b606091505b508483815181106111395761113961268d565b602090810291909101015291508161117d576040517f4d6a232800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001016110ab565b505092915050565b60408051605c8101859052603c810186905260288101879052336014820152838152607c902060081b6003176000818152602081905291909120805473ffffffffffffffffffffffffffffffffffffffff1683156113155773ffffffffffffffffffffffffffffffffffffffff81166112ab57336000818152600160208181526040808420805480850182559085528285200188905573ffffffffffffffffffffffffffffffffffffffff8d168085526002835281852080548086018255908652929094209091018790558a901b7bffffffffffffffff000000000000000000000000000000000000000016909217845560a089901b179083015561129482600388910155565b84156112a6576112a682600287910155565b61135c565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff8216016112a65781547fffffffffffffffffffffffff0000000000000000000000000000000000000000163317825561135c565b3373ffffffffffffffffffffffffffffffffffffffff82160361135c5781547fffffffffffffffffffffffff00000000000000000000000000000000000000001660011782555b60408051878152602081018790528515159181019190915273ffffffffffffffffffffffffffffffffffffffff80891691908a169033907f15e7a1bdcd507dd632d797d38e60cc5a9c0749b9a63097a215c4d006126825c690606001611043565b60006113c88561200f565b6115ce576040805160288101889052601481018790526000808252604890912060081b6001178152602081905220611401905b86612035565b80611441575060408051603c810186905260288101889052601481018790526000808252605c90912060081b6002178152602081905220611441906113fb565b61148e5760408051605c8101859052603c810186905260288101889052601481018790526000808252607c90912060081b6005178152602081905220611489905b6004015490565b6114b0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81148215176115ce576040805160288101889052601481018790528381526048902060081b60011760009081526020819052908120611513905b87612035565b80611553575060408051603c81018790526028810189905260148101889052848152605c902060081b60021760009081526020819052206115539061150d565b61159d5760408051605c8101869052603c81018790526028810189905260148101889052848152607c902060081b600517600090815260208190522061159890611482565b6115bf565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b90508181108282180281189150505b8060005260206000f35b60006115e38561200f565b610dcc576040805160288101889052601481018790526000808252604890912060081b600117815260208190522061161a906113fb565b8061165a575060408051603c810186905260288101889052601481018790526000808252605c90912060081b600217815260208190522061165a906113fb565b806116a1575060408051605c8101859052603c810186905260288101889052601481018790526000808252607c90912060081b60031781526020819052206116a1906113fb565b9050801515821517610dcc576040805160288101889052601481018790528381526048902060081b60011760009081526020819052206116e0906113fb565b80611720575060408051603c81018690526028810188905260148101879052838152605c902060081b6002176000908152602081905220611720906113fb565b80610dc9575060408051605c8101859052603c81018690526028810188905260148101879052838152607c902060081b6003176000908152602081905220610dc9906113fb565b60006117728461200f565b6115ce576040805160288101879052601481018690526000808252604890912060081b60011781526020819052206117a990610d04565b806117e9575060408051603c810185905260288101879052601481018690526000808252605c90912060081b60021781526020819052206117e990610d04565b61182c5760408051603c810185905260288101879052601481018690526000808252605c90912060081b600417815260208190522061182790611482565b61184e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81148215176115ce576040805160288101879052601481018690528381526048902060081b600117600090815260208190529081206118af906113fb565b806118ef575060408051603c81018690526028810188905260148101879052848152605c902060081b60021760009081526020819052206118ef906113fb565b61159d5760408051603c81018690526028810188905260148101879052848152605c902060081b600417600090815260208190522061159890611482565b60408051603c810185905260288101869052336014820152838152605c902060081b6002176000818152602081905291909120805473ffffffffffffffffffffffffffffffffffffffff168315611aa25773ffffffffffffffffffffffffffffffffffffffff8116611a3857336000818152600160208181526040808420805480850182559085528285200188905573ffffffffffffffffffffffffffffffffffffffff8c1680855260028352818520805480860182559086529290942090910187905589901b7bffffffffffffffff000000000000000000000000000000000000000016909217845560a088901b17908301558415611a3357611a3382600287910155565b611ae9565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff821601611a335781547fffffffffffffffffffffffff00000000000000000000000000000000000000001633178255611ae9565b3373ffffffffffffffffffffffffffffffffffffffff821603611ae95781547fffffffffffffffffffffffff00000000000000000000000000000000000000001660011782555b60408051868152851515602082015273ffffffffffffffffffffffffffffffffffffffff80891692908a169133917f021be15e24de4afc43cfb5d0ba95ca38e0783571e05c12bbe6aece8842ae82df9101610625565b6000611b4a8361200f565b610dcc576040805160288101869052601481018590526000808252604890912060081b6001178152602081905220611b83905b84612035565b9050801515821517610dcc576040805160288101869052601481018590528381526048902060081b6001176000908152602081905220610dc990611b7d565b805460609060009081808267ffffffffffffffff811115611be557611be561265e565b604051908082528060200260200182016040528015611c0e578160200160208202803683370190505b50905060005b83811015611ca757868181548110611c2e57611c2e61268d565b90600052602060002001549250611c75611c70611c5685600090815260208190526040902090565b5473ffffffffffffffffffffffffffffffffffffffff1690565b61200f565b611c9f5782828680600101975081518110611c9257611c9261268d565b6020026020010181815250505b600101611c14565b508367ffffffffffffffff811115611cc157611cc161265e565b604051908082528060200260200182016040528015611cea578160200160208202803683370190505b50945060005b84811015611d3757818181518110611d0a57611d0a61268d565b6020026020010151868281518110611d2457611d2461268d565b6020908102919091010152600101611cf0565b5050505050919050565b805460609060009081808267ffffffffffffffff811115611d6457611d6461265e565b604051908082528060200260200182016040528015611d8d578160200160208202803683370190505b50905060005b83811015611e0757868181548110611dad57611dad61268d565b90600052602060002001549250611dd5611c70611c5685600090815260208190526040902090565b611dff5782828680600101975081518110611df257611df261268d565b6020026020010181815250505b600101611d93565b508367ffffffffffffffff811115611e2157611e2161265e565b604051908082528060200260200182016040528015611ea657816020015b6040805160e08101825260008082526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909201910181611e3f5790505b5094506000805b8581101561200457828181518110611ec757611ec761268d565b60200260200101519350611ee684600090815260208190526040902090565b805460018201546040805160e08101825293955073ffffffffffffffffffffffffffffffffffffffff808416949083169360a09390931c9290911c73ffffffffffffffff0000000000000000000000001691909117908060ff89166005811115611f5257611f526121ea565b81526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff168152602001611f9a876002015490565b81526020018273ffffffffffffffffffffffffffffffffffffffff168152602001611fc6876003015490565b8152602001611fd6876004015490565b8152508a8581518110611feb57611feb61268d565b6020026020010181905250505050806001019050611ead565b505050505050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff8316908114901517610668565b6000612055835473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614905092915050565b803573ffffffffffffffffffffffffffffffffffffffff811681146120af57600080fd5b919050565b600080600080608085870312156120ca57600080fd5b6120d38561208b565b93506120e16020860161208b565b93969395505050506040820135916060013590565b60006020828403121561210857600080fd5b6121118261208b565b9392505050565b6020808252825182820181905260009190848201906040850190845b8181101561215057835183529284019291840191600101612134565b50909695505050505050565b60006020828403121561216e57600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461211157600080fd5b803580151581146120af57600080fd5b6000806000606084860312156121c357600080fd5b6121cc8461208b565b9250602084013591506121e16040850161219e565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60208082528251828201819052600091906040908185019086840185805b8381101561230e578251805160068110612278577f4e487b710000000000000000000000000000000000000000000000000000000084526021600452602484fd5b86528088015173ffffffffffffffffffffffffffffffffffffffff1688870152868101516122bd8888018273ffffffffffffffffffffffffffffffffffffffff169052565b506060818101519087015260808082015173ffffffffffffffffffffffffffffffffffffffff169087015260a0808201519087015260c0908101519086015260e09094019391860191600101612237565b509298975050505050505050565b60008083601f84011261232e57600080fd5b50813567ffffffffffffffff81111561234657600080fd5b6020830191508360208260051b850101111561236157600080fd5b9250929050565b6000806020838503121561237b57600080fd5b823567ffffffffffffffff81111561239257600080fd5b61239e8582860161231c565b90969095509350505050565b600080600080608085870312156123c057600080fd5b6123c98561208b565b93506123d76020860161208b565b92506123e56040860161208b565b9396929550929360600135925050565b600080600080600060a0868803121561240d57600080fd5b6124168661208b565b94506124246020870161208b565b94979496505050506040830135926060810135926080909101359150565b6000602080830181845280855180835260408601915060408160051b87010192508387016000805b83811015612502577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc089870301855282518051808852835b818110156124bd578281018a01518982018b015289016124a2565b508781018901849052601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690960187019550938601939186019160010161246a565b509398975050505050505050565b600080600080600060a0868803121561252857600080fd5b6125318661208b565b945061253f6020870161208b565b9350604086013592506060860135915061255b6080870161219e565b90509295509295909350565b600080600080600060a0868803121561257f57600080fd5b6125888661208b565b94506125966020870161208b565b93506125a46040870161208b565b94979396509394606081013594506080013592915050565b600080600080608085870312156125d257600080fd5b6125db8561208b565b93506125e96020860161208b565b9250604085013591506125fe6060860161219e565b905092959194509250565b60008060006060848603121561261e57600080fd5b6126278461208b565b92506126356020850161208b565b9150604084013590509250925092565b60006020828403121561265757600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126126f157600080fd5b83018035915067ffffffffffffffff82111561270c57600080fd5b60200191503681900382131561236157600080fd5b818382376000910190815291905056fea164736f6c6343000815000a0000
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # delegate-registry
2 |
3 |
4 |
5 | Frontend website interface at [delegate.xyz](https://delegate.xyz). Full docs at [docs.delegate.xyz](https://docs.delegate.xyz).
6 |
7 | ## Finalized Deployment
8 |
9 | |Mainnet Chain|Address|
10 | |---|---|
11 | |Ethereum|[0x00000000000000447e69651d841bd8d104bed493](https://etherscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
12 | |Apechain|[0x00000000000000447e69651d841bd8d104bed493](https://apescan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
13 | |Arbitrum|[0x00000000000000447e69651d841bd8d104bed493](https://arbiscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
14 | |Arbitrum (Nova)|[0x00000000000000447e69651d841bd8d104bed493](https://nova.arbiscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
15 | |Avalanche|[0x00000000000000447e69651d841bd8d104bed493](https://snowtrace.io/address/0x00000000000000447e69651d841bd8d104bed493)|
16 | |Base|[0x00000000000000447e69651d841bd8d104bed493](https://basescan.org/address/0x00000000000000447e69651d841bd8d104bed493)|
17 | |Blast|[0x00000000000000447e69651d841bd8d104bed493](https://blastscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
18 | |BNB Chain|[0x00000000000000447e69651d841bd8d104bed493](https://bscscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
19 | |Canto|[0x00000000000000447e69651d841bd8d104bed493](https://cantoscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
20 | |Celo|[0x00000000000000447e69651d841bd8d104bed493](https://explorer.celo.org/mainnet/address/0x00000000000000447e69651d841bd8d104bed493)|
21 | |Fantom|[0x00000000000000447e69651d841bd8d104bed493](https://ftmscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
22 | |Gnosis|[0x00000000000000447e69651d841bd8d104bed493](https://gnosisscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
23 | |Hychain|[0x00000000000000447e69651d841bd8d104bed493](https://explorer.hychain.com/address/0x00000000000000447e69651d841bd8d104bed493)|
24 | |Linea|[0x00000000000000447e69651d841bd8d104bed493](https://lineascan.build/address/0x00000000000000447e69651d841bd8d104bed493)|
25 | |Mantle|[0x00000000000000447e69651d841bd8d104bed493](https://explorer.mantle.xyz/address/0x00000000000000447e69651d841bd8d104bed493)|
26 | |Moonbeam|[0x00000000000000447e69651d841bd8d104bed493](https://moonscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
27 | |Moonriver|[0x00000000000000447e69651d841bd8d104bed493](https://moonriver.moonscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
28 | |Optimism|[0x00000000000000447e69651d841bd8d104bed493](https://optimistic.etherscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
29 | |Polygon|[0x00000000000000447e69651d841bd8d104bed493](https://polygonscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
30 | |Polygon zkEVM|[0x00000000000000447e69651d841bd8d104bed493](https://zkevm.polygonscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
31 | |Plume|[0x00000000000000447e69651d841bd8d104bed493](https://phoenix-explorer.plumenetwork.xyz/address/0x00000000000000447e69651d841bD8D104Bed493)|
32 | |Ronin|[0x00000000000000447e69651d841bd8d104bed493](https://app.roninchain.com/address/0x00000000000000447e69651d841bd8d104bed493)|
33 | |Sanko|[0x00000000000000447e69651d841bd8d104bed493](https://explorer.sanko.xyz/address/0x00000000000000447e69651d841bd8d104bed493)|
34 | |Scroll|[0x00000000000000447e69651d841bd8d104bed493](https://scrollscan.com/address/0x00000000000000447e69651d841bd8d104bed493)|
35 | |Sei|[0x00000000000000447e69651d841bd8d104bed493](https://seiscan.app/address/0x00000000000000447e69651d841bd8d104bed493)|
36 | |Shape|[0x00000000000000447e69651d841bd8d104bed493](https://shapescan.xyz/address/0x00000000000000447e69651d841bd8d104bed493)|
37 | |Taiko|[0x00000000000000447e69651d841bd8d104bed493](https://taikoscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
38 | |Shape|[0x00000000000000447e69651d841bd8d104bed493](https://shapescan.xyz/address/0x00000000000000447e69651d841bd8d104bed493)|
39 | |ZetaChain|[0x00000000000000447e69651d841bd8d104bed493](https://mainnet.explorer.zetachain.app/address/0x00000000000000447e69651d841bd8d104bed493)|
40 | |Zora|[0x00000000000000447e69651d841bd8d104bed493](https://explorer.zora.energy/address/0x00000000000000447e69651d841bd8d104bed493)|
41 |
42 | |ZKSync Chain|Address|
43 | |---|---|
44 | |Abstract (Sepolia)|[0x0000000059A24EB229eED07Ac44229DB56C5d797](https://sepolia.abscan.org/address/0x0000000059A24EB229eED07Ac44229DB56C5d797)|
45 | |ZKsync Era|[0x0000000059A24EB229eED07Ac44229DB56C5d797](https://holesky.etherscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
46 | |Treasure|[0x0000000059A24EB229eED07Ac44229DB56C5d797](https://sepolia.basescan.org/address/0x00000000000000447e69651d841bd8d104bed493)|
47 |
48 | |Testnet Chain|Address|
49 | |---|---|
50 | |Ethereum (Sepolia)|[0x00000000000000447e69651d841bd8d104bed493](https://sepolia.etherscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
51 | |Ethereum (Holesky)|[0x00000000000000447e69651d841bd8d104bed493](https://holesky.etherscan.io/address/0x00000000000000447e69651d841bd8d104bed493)|
52 | |Abstract (Sepolia)|[0x00000000000000447e69651d841bd8d104bed493](https://sepolia.abscan.org/address/0x00000000000000447e69651d841bd8d104bed493)|
53 | |Base (Sepolia)|[0x00000000000000447e69651d841bd8d104bed493](https://sepolia.basescan.org/address/0x00000000000000447e69651d841bd8d104bed493)|
54 | |Berachain bArtio|[0x00000000000000447e69651d841bd8d104bed493](https://bartio.beratrail.io/address/0x00000000000000447e69651d841bD8D104Bed493)|
55 | |Monad|[0x00000000000000447e69651d841bd8d104bed493](https://testnet.monadexplorer.com/address/0x00000000000000447e69651d841bD8D104Bed493)|
56 | |Ronin (Saigon)|[0x00000000000000447e69651d841bd8d104bed493](https://saigon-app.roninchain.com/address/0x00000000000000447e69651d841bd8d104bed493)|
57 |
58 |
59 |
60 | If you'd like to get the DelegateRegistry on another EVM chain, anyone in the community can deploy to the same address! Simply run the script in [Deploy.s.sol](script/Deploy.s.sol) with the specified salt. The CREATE2 factory must be deployed at `0x0000000000FFe8B47B3e2130213B802212439497`, but this factory exists on 19 separate chains so shouldn't be an issue. If you've run a community deployment, open a PR adding the link to the above table.
61 |
62 | ## Overview
63 |
64 | Welcome! If you're a programmer, view [the specific registry code here](src/DelegateRegistry.sol). If you want to discuss specific open questions, click on the "Issues" tab to leave a comment. If you're interested in integrating this standard into your token project or marketplace, we're in the process of creating example templates - or reach out directly via a [Twitter DM](https://twitter.com/0xfoobar).
65 |
66 | We have an exciting group of initial people circling around this standard, including foobar (hi!), punk6529 (open metaverse), loopify (loopiverse), andy8052 (fractional), purplehat (artblocks), emiliano (nftrentals), arran (proof), james (collabland), john (gnosis safe), wwhchung (manifoldxyz) tally labs and many more. The dream is to move from a fragmented world where no individual deployment gets serious use to a global registry where users can register their vault once and use it safely for a variety of airdrops & other claims! Please reach out if interested in helping make this a reality on either the technical, social, or integration side.
67 |
68 | ## Standardization
69 |
70 | In the interest of broader visibility and adoption around this registry, we've started the process for considering this effort for an EIP (Ethereum Improvement Proposal), which can be found here: https://eips.ethereum.org/EIPS/eip-5639
71 |
72 | ## Why delegation?
73 |
74 | Proving ownership of an asset to a third party application in the Ethereum ecosystem is common. Common examples include claiming airdrops, minting from collection whitelists, and verifying token ownership for a gated discord/telegram channel. Users frequently sign payloads of data to authenticate themselves before gaining access to perform some operation. However, this introduces the danger of accidentally signing a malicious transaction from a cold wallet vault.
75 |
76 | While a technical solution that "just works" may appear easy to code up, there's a reason no existing approaches have delighted users and hit mass adoption yet.
77 | - EIP712 signatures are not smart contract compatible.
78 | - ENS names are a dangerous & clunky dependency not suitable for an EIP standard.
79 | - Some solutions are too specific or hardcoded for general reuse.
80 |
81 | ## What features does this include?
82 |
83 | ### Fully Onchain, No EIP 712 signatures
84 | Why? This is critical for smart contract composability, which cannot produce a private key signature. And while we could have two separate paths for delegation setup, one with smart contract calls and one with signature calls, this fragments adoption and developer use. Not to mention that allowing offchain signatures encourages people to interact with their vault and hotwallet in rapid succession, and accidental signatures can float around offchain with no easy way to revoke as we saw with the OpenSea "old ape offer" attack vector.
85 |
86 | ### Fully Immutable, No Admin Powers
87 | Why? Because governance is an attack vector. There should be none of it in a neutral trustless delegation standard. The standard is designed to be as flexible as possible, but upgrades are always possible by deploying a new registry with different functionality.
88 |
89 | ### Fully Standalone, No External Dependencies
90 | Why? Because external dependencies are an unnecessary attack vector.
91 |
92 | ### Fully Identifiable, Clear Unique Method Names
93 | Why? Delegation is distinct from token ownership. Delegation implies the ability to claim or act on behalf of a token owner, but it does not imply the ability to move the token. So method names such as `balanceOf()` and `ownerOf()` should be avoided at all costs, replaced with clear method names that make it clear the hotwallet has delegation powers but not token ownership powers.
94 |
95 | ### Reusable Global Registry w/ Same Address Across Multiple EVM Chains
96 | Why? For ease of use and adoption. It should also be a vanity address that's clearly distinguishable from others via CREATE2, either leading zeros or some fun prefix/postfix.
97 |
98 | ## Why not existing solutions?
99 |
100 | Sincere appreciation for everyone who's taken a crack at this problem in the past with different tradeoffs. Comparison is done not to denigrate, but with the goal of hitting the best unified standard for mass adoption.
101 |
102 | ENS delegation via [EIP-5131](https://eips.ethereum.org/EIPS/eip-5131): ENS is an offshore foundation with a for-profit token that charges rent for every new domain registration. We applaud the widespread adoption it's gotten, however this is a dangerous dependency for what should be a timeless standard. Additionally, delegations for a fresh wallet should be free (only gas) rather than costing additional economic rents.
103 |
104 | wenew's approach via [HotWalletProxy](https://github.com/wenewlabs/public/blob/main/HotWalletProxy/HotWalletProxy.sol): This is the right directional approach, with an onchain registry that can be set via either a vault transaction or a vault signature submitted from a hot wallet. However it doesn't provide enough generalizability of drilling down into specific collections or specific tokens, the `ownerOf()` method naming overlaps with existing standards, and doesn't generalize to other types of delegation such as governance-specific standards. Delegation should be explicit rather than overwriting existing ERC721 method names.
105 |
106 | ## How do I use it?
107 |
108 | Check out the [IDelegateRegistry.sol](src/IDelegateRegistry.sol) file. This is the interface to interact with, and contains the following core interface:
109 |
110 | ```code
111 | /// WRITE ///
112 | function delegateAll(address to, bytes32 rights, bool enable) external;
113 | function delegateContract(address to, address contract_, bytes32 rights, bool enable) external;
114 | function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external;
115 | function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external;
116 | function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external;
117 | /// READ ///
118 | function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);
119 | function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
120 | function checkDelegateForAll(address delegate, address vault) external view returns (bool);
121 | function checkDelegateForContract(address delegate, address vault, address contract_) external view returns (bool);
122 | function checkDelegateForERC721(address delegate, address vault, address contract_, uint256 tokenId) external view returns (bool);
123 | function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256);
124 | function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256);
125 | ```
126 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = 'src'
3 | out = 'out'
4 | libs = ['lib']
5 |
6 | auto_detect_remappings = false
7 | # remappings = []
8 |
9 | solc_version = "0.8.21"
10 | # EVM version must be Paris not Shanghai to prevent PUSH0 incompatibility with other EVM chains
11 | # Extra 137 deployment size, extra 0.1% runtime gas costs from using older version
12 | evm_version = "paris"
13 | # Etherscan verification max is 100 million
14 | optimizer_runs = 9_999_999
15 | # Get reproducible bytecode across machines by removing metadata hash from runtime bytecode, also saves 41 deployment size
16 | bytecode_hash = "none"
17 | # Disable ir optimizer, minimal gas savings not worth the potential for bugs
18 | via_ir = false
19 |
20 |
21 | [fmt]
22 | line_length = 180
23 | wrap_comments = true # Increases readability of comments
24 |
25 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
--------------------------------------------------------------------------------
/gasbenchmark10mil:
--------------------------------------------------------------------------------
1 | No files changed, compilation skipped
2 |
3 | Running 1 test for test/GasBenchmark.t.sol:GasBenchmark
4 | [32m[PASS][0m testGas(address,bytes32) (runs: 256, μ: 13573356, ~: 13573452)
5 | Test result: [32mok[0m. [32m1[0m passed; [31m0[0m failed; [33m0[0m skipped; finished in 247.64ms
6 | | src/DelegateRegistry.sol:DelegateRegistry contract | | | | | |
7 | |----------------------------------------------------|-----------------|--------|--------|--------|---------|
8 | | Deployment Cost | Deployment Size | | | | |
9 | | 2011327 | 10078 | | | | |
10 | | Function Name | min | avg | median | max | # calls |
11 | | checkDelegateForAll | 3002 | 3198 | 3198 | 3395 | 2 |
12 | | checkDelegateForContract | 5491 | 5899 | 5899 | 6308 | 2 |
13 | | checkDelegateForERC1155 | 7932 | 8550 | 8550 | 9169 | 2 |
14 | | checkDelegateForERC20 | 7882 | 8494 | 8494 | 9106 | 2 |
15 | | checkDelegateForERC721 | 7975 | 8607 | 8607 | 9240 | 2 |
16 | | delegateAll | 135825 | 135825 | 135825 | 135825 | 2 |
17 | | delegateContract | 114433 | 125383 | 125383 | 136333 | 2 |
18 | | delegateERC1155 | 5710 | 93282 | 93282 | 180854 | 2 |
19 | | delegateERC20 | 5357 | 81865 | 81865 | 158374 | 2 |
20 | | delegateERC721 | 136921 | 147871 | 147871 | 158821 | 2 |
21 | | multicall | 404294 | 404294 | 404294 | 404294 | 1 |
22 |
23 |
24 |
25 |
26 | Ran 1 test suites: [32m1[0m tests passed, [31m0[0m failed, [33m0[0m skipped (1 total tests)
27 |
--------------------------------------------------------------------------------
/gasbenchmark200:
--------------------------------------------------------------------------------
1 | | src/DelegationRegistry.sol:DelegationRegistry contract | | | | | |
2 | |--------------------------------------------------------|-----------------|--------|--------|--------|---------|
3 | | Deployment Cost | Deployment Size | | | | |
4 | | 1247295 | 6262 | | | | |
5 | | Function Name | min | avg | median | max | # calls |
6 | | checkDelegateForAll | 7588 | 7588 | 7588 | 7588 | 2 |
7 | | checkDelegateForContract | 4786 | 7786 | 7786 | 10786 | 2 |
8 | | checkDelegateForToken | 8186 | 11186 | 11186 | 14186 | 2 |
9 | | delegateForAll | 190137 | 190137 | 190137 | 190137 | 1 |
10 | | delegateForContract | 210458 | 210458 | 210458 | 210458 | 1 |
11 | | delegateForToken | 230841 | 230841 | 230841 | 230841 | 1 |
--------------------------------------------------------------------------------
/gasreport200:
--------------------------------------------------------------------------------
1 | | src/DelegationRegistry.sol:DelegationRegistry contract | | | | | |
2 | |--------------------------------------------------------|-----------------|--------|--------|--------|---------|
3 | | Deployment Cost | Deployment Size | | | | |
4 | | 1247295 | 6262 | | | | |
5 | | Function Name | min | avg | median | max | # calls |
6 | | checkDelegateForAll | 1588 | 2338 | 1588 | 3588 | 8 |
7 | | checkDelegateForContract | 1706 | 3417 | 4786 | 4786 | 9 |
8 | | checkDelegateForToken | 1978 | 4594 | 5645 | 8186 | 10 |
9 | | delegateForAll | 5216 | 150923 | 167237 | 190137 | 20 |
10 | | delegateForContract | 5553 | 139465 | 162658 | 210458 | 19 |
11 | | delegateForToken | 5940 | 157903 | 183041 | 230841 | 20 |
12 | | getContractLevelDelegations | 3221 | 7964 | 8211 | 10534 | 6 |
13 | | getDelegatesForAll | 3273 | 5350 | 5026 | 7730 | 6 |
14 | | getDelegatesForContract | 1353 | 5728 | 6649 | 8260 | 4 |
15 | | getDelegatesForToken | 1377 | 6140 | 7140 | 8906 | 4 |
16 | | getDelegationsByDelegate | 9526 | 13364 | 13917 | 18051 | 8 |
17 | | getTokenLevelDelegations | 3200 | 9378 | 10121 | 11861 | 6 |
18 | | revokeAllDelegates | 21478 | 21478 | 21478 | 21478 | 4 |
19 | | revokeDelegate | 22131 | 22131 | 22131 | 22131 | 4 |
20 | | revokeSelf | 22133 | 22133 | 22133 | 22133 | 1 |
--------------------------------------------------------------------------------
/hashbenchmark10mil:
--------------------------------------------------------------------------------
1 | No files changed, compilation skipped
2 |
3 | Running 1 test for test/HashBenchmark.t.sol:HashBenchmark
4 | [32m[PASS][0m testHashGas(address,bytes32,address,uint256,address,bytes32) (runs: 256, μ: 19906, ~: 19906)
5 | Test result: [32mok[0m. [32m1[0m passed; [31m0[0m failed; [33m0[0m skipped; finished in 32.07ms
6 | | test/HashBenchmark.t.sol:HashHarness contract | | | | | |
7 | |-----------------------------------------------|-----------------|-----|--------|-----|---------|
8 | | Deployment Cost | Deployment Size | | | | |
9 | | 282524 | 1443 | | | | |
10 | | Function Name | min | avg | median | max | # calls |
11 | | allHash | 658 | 658 | 658 | 658 | 1 |
12 | | allLocation | 715 | 715 | 715 | 715 | 1 |
13 | | contractHash | 777 | 777 | 777 | 777 | 1 |
14 | | contractLocation | 824 | 824 | 824 | 824 | 1 |
15 | | decodeType | 371 | 371 | 371 | 371 | 1 |
16 | | erc1155Hash | 785 | 785 | 785 | 785 | 1 |
17 | | erc1155Location | 899 | 899 | 899 | 899 | 1 |
18 | | erc20Hash | 756 | 756 | 756 | 756 | 1 |
19 | | erc20Location | 793 | 793 | 793 | 793 | 1 |
20 | | erc721Hash | 830 | 830 | 830 | 830 | 1 |
21 | | erc721Location | 867 | 867 | 867 | 867 | 1 |
22 | | location | 384 | 384 | 384 | 384 | 1 |
23 |
24 |
25 |
26 |
27 | Ran 1 test suites: [32m1[0m tests passed, [31m0[0m failed, [33m0[0m skipped (1 total tests)
28 |
--------------------------------------------------------------------------------
/logo_text_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delegatexyz/delegate-registry/209788dd449d4013e6f4adfd35ed3df8a7ef05d8/logo_text_black.png
--------------------------------------------------------------------------------
/remappings.txt:
--------------------------------------------------------------------------------
1 | ds-test/=lib/forge-std/lib/ds-test/src/
2 | forge-std/=lib/forge-std/src/
3 | murky/=lib/murky/src/
4 | openzeppelin/=lib/openzeppelin-contracts/contracts/
--------------------------------------------------------------------------------
/script/Deploy.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Script} from "forge-std/Script.sol";
5 | import {console2} from "forge-std/console2.sol";
6 | import {DelegateRegistry} from "../src/DelegateRegistry.sol";
7 | import {Singlesig} from "../src/singlesig/Singlesig.sol";
8 |
9 | interface ImmutableCreate2Factory {
10 | function safeCreate2(bytes32 salt, bytes calldata initCode) external payable returns (address deploymentAddress);
11 | function findCreate2Address(bytes32 salt, bytes calldata initCode) external view returns (address deploymentAddress);
12 | function findCreate2AddressViaHash(bytes32 salt, bytes32 initCodeHash) external view returns (address deploymentAddress);
13 | }
14 |
15 | contract Deploy is Script {
16 | ImmutableCreate2Factory immutable factory = ImmutableCreate2Factory(0x0000000000FFe8B47B3e2130213B802212439497);
17 | bytes initCode = type(DelegateRegistry).creationCode;
18 | // bytes32 salt = 0x0000000000000000000000000000000000000000fbe49ecfc3decb1164228b89;
19 | bytes32 salt = 0x00000000000000000000000000000000000000002bbc593dd77cb93fbb932d5f;
20 |
21 | // bytes initCode = abi.encodePacked(type(Singlesig).creationCode, abi.encode(address(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215)));
22 | // bytes32 salt = 0x000000000000000000000000000000000000000016c7768a8c7a2824b846321d;
23 |
24 | function run() external {
25 | vm.startBroadcast();
26 |
27 | // address singlesigAddress = factory.safeCreate2(salt, initCode);
28 | // Singlesig singlesig = Singlesig(payable(singlesigAddress));
29 | // console2.log(address(singlesig));
30 |
31 | address registryAddress = factory.safeCreate2(salt, initCode);
32 | DelegateRegistry registry = DelegateRegistry(registryAddress);
33 | console2.log(address(registry));
34 |
35 | // address registryAddress = factory.safeCreate2(salt, initCode);
36 | // DelegateRegistry registry = DelegateRegistry(registryAddress);
37 | // console2.log(address(registry));
38 |
39 | vm.stopBroadcast();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/DelegateRegistry.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {IDelegateRegistry as IDelegateRegistry} from "./IDelegateRegistry.sol";
5 | import {RegistryHashes as Hashes} from "./libraries/RegistryHashes.sol";
6 | import {RegistryStorage as Storage} from "./libraries/RegistryStorage.sol";
7 | import {RegistryOps as Ops} from "./libraries/RegistryOps.sol";
8 |
9 | /**
10 | * @title DelegateRegistry
11 | * @custom:version 2.0
12 | * @custom:coauthor foobar (0xfoobar)
13 | * @custom:coauthor mireynolds
14 | * @notice A standalone immutable registry storing delegated permissions from one address to another
15 | */
16 | contract DelegateRegistry is IDelegateRegistry {
17 | /// @dev Only this mapping should be used to verify delegations; the other mapping arrays are for enumerations
18 | mapping(bytes32 delegationHash => bytes32[5] delegationStorage) internal delegations;
19 |
20 | /// @dev Vault delegation enumeration outbox, for pushing new hashes only
21 | mapping(address from => bytes32[] delegationHashes) internal outgoingDelegationHashes;
22 |
23 | /// @dev Delegate enumeration inbox, for pushing new hashes only
24 | mapping(address to => bytes32[] delegationHashes) internal incomingDelegationHashes;
25 |
26 | /**
27 | * ----------- WRITE -----------
28 | */
29 |
30 | /// @inheritdoc IDelegateRegistry
31 | function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) {
32 | results = new bytes[](data.length);
33 | bool success;
34 | unchecked {
35 | for (uint256 i = 0; i < data.length; ++i) {
36 | //slither-disable-next-line calls-loop,delegatecall-loop
37 | (success, results[i]) = address(this).delegatecall(data[i]);
38 | if (!success) revert MulticallFailed();
39 | }
40 | }
41 | }
42 |
43 | /// @inheritdoc IDelegateRegistry
44 | function delegateAll(address to, bytes32 rights, bool enable) external payable override returns (bytes32 hash) {
45 | hash = Hashes.allHash(msg.sender, rights, to);
46 | bytes32 location = Hashes.location(hash);
47 | address loadedFrom = _loadFrom(location);
48 | if (enable) {
49 | if (loadedFrom == Storage.DELEGATION_EMPTY) {
50 | _pushDelegationHashes(msg.sender, to, hash);
51 | _writeDelegationAddresses(location, msg.sender, to, address(0));
52 | if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights);
53 | } else if (loadedFrom == Storage.DELEGATION_REVOKED) {
54 | _updateFrom(location, msg.sender);
55 | }
56 | } else if (loadedFrom == msg.sender) {
57 | _updateFrom(location, Storage.DELEGATION_REVOKED);
58 | }
59 | emit DelegateAll(msg.sender, to, rights, enable);
60 | }
61 |
62 | /// @inheritdoc IDelegateRegistry
63 | function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable override returns (bytes32 hash) {
64 | hash = Hashes.contractHash(msg.sender, rights, to, contract_);
65 | bytes32 location = Hashes.location(hash);
66 | address loadedFrom = _loadFrom(location);
67 | if (enable) {
68 | if (loadedFrom == Storage.DELEGATION_EMPTY) {
69 | _pushDelegationHashes(msg.sender, to, hash);
70 | _writeDelegationAddresses(location, msg.sender, to, contract_);
71 | if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights);
72 | } else if (loadedFrom == Storage.DELEGATION_REVOKED) {
73 | _updateFrom(location, msg.sender);
74 | }
75 | } else if (loadedFrom == msg.sender) {
76 | _updateFrom(location, Storage.DELEGATION_REVOKED);
77 | }
78 | emit DelegateContract(msg.sender, to, contract_, rights, enable);
79 | }
80 |
81 | /// @inheritdoc IDelegateRegistry
82 | function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable override returns (bytes32 hash) {
83 | hash = Hashes.erc721Hash(msg.sender, rights, to, tokenId, contract_);
84 | bytes32 location = Hashes.location(hash);
85 | address loadedFrom = _loadFrom(location);
86 | if (enable) {
87 | if (loadedFrom == Storage.DELEGATION_EMPTY) {
88 | _pushDelegationHashes(msg.sender, to, hash);
89 | _writeDelegationAddresses(location, msg.sender, to, contract_);
90 | _writeDelegation(location, Storage.POSITIONS_TOKEN_ID, tokenId);
91 | if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights);
92 | } else if (loadedFrom == Storage.DELEGATION_REVOKED) {
93 | _updateFrom(location, msg.sender);
94 | }
95 | } else if (loadedFrom == msg.sender) {
96 | _updateFrom(location, Storage.DELEGATION_REVOKED);
97 | }
98 | emit DelegateERC721(msg.sender, to, contract_, tokenId, rights, enable);
99 | }
100 |
101 | // @inheritdoc IDelegateRegistry
102 | function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable override returns (bytes32 hash) {
103 | hash = Hashes.erc20Hash(msg.sender, rights, to, contract_);
104 | bytes32 location = Hashes.location(hash);
105 | address loadedFrom = _loadFrom(location);
106 | if (amount != 0) {
107 | if (loadedFrom == Storage.DELEGATION_EMPTY) {
108 | _pushDelegationHashes(msg.sender, to, hash);
109 | _writeDelegationAddresses(location, msg.sender, to, contract_);
110 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
111 | if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights);
112 | } else if (loadedFrom == Storage.DELEGATION_REVOKED) {
113 | _updateFrom(location, msg.sender);
114 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
115 | } else if (loadedFrom == msg.sender) {
116 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
117 | }
118 | } else if (loadedFrom == msg.sender) {
119 | _updateFrom(location, Storage.DELEGATION_REVOKED);
120 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, uint256(0));
121 | }
122 | emit DelegateERC20(msg.sender, to, contract_, rights, amount);
123 | }
124 |
125 | /// @inheritdoc IDelegateRegistry
126 | function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable override returns (bytes32 hash) {
127 | hash = Hashes.erc1155Hash(msg.sender, rights, to, tokenId, contract_);
128 | bytes32 location = Hashes.location(hash);
129 | address loadedFrom = _loadFrom(location);
130 | if (amount != 0) {
131 | if (loadedFrom == Storage.DELEGATION_EMPTY) {
132 | _pushDelegationHashes(msg.sender, to, hash);
133 | _writeDelegationAddresses(location, msg.sender, to, contract_);
134 | _writeDelegation(location, Storage.POSITIONS_TOKEN_ID, tokenId);
135 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
136 | if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights);
137 | } else if (loadedFrom == Storage.DELEGATION_REVOKED) {
138 | _updateFrom(location, msg.sender);
139 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
140 | } else if (loadedFrom == msg.sender) {
141 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount);
142 | }
143 | } else if (loadedFrom == msg.sender) {
144 | _updateFrom(location, Storage.DELEGATION_REVOKED);
145 | _writeDelegation(location, Storage.POSITIONS_AMOUNT, uint256(0));
146 | }
147 | emit DelegateERC1155(msg.sender, to, contract_, tokenId, rights, amount);
148 | }
149 |
150 | /// @dev Transfer native token out
151 | function sweep() external {
152 | assembly ("memory-safe") {
153 | // This hardcoded address is a CREATE2 factory counterfactual smart contract wallet that will always accept native token transfers
154 | let result := call(gas(), 0x000000dE1E80ea5a234FB5488fee2584251BC7e8, selfbalance(), 0, 0, 0, 0)
155 | }
156 | }
157 |
158 | /**
159 | * ----------- CHECKS -----------
160 | */
161 |
162 | /// @inheritdoc IDelegateRegistry
163 | function checkDelegateForAll(address to, address from, bytes32 rights) external view override returns (bool valid) {
164 | if (!_invalidFrom(from)) {
165 | valid = _validateFrom(Hashes.allLocation(from, "", to), from);
166 | if (!Ops.or(rights == "", valid)) valid = _validateFrom(Hashes.allLocation(from, rights, to), from);
167 | }
168 | assembly ("memory-safe") {
169 | // Only first 32 bytes of scratch space is accessed
170 | mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here
171 | return(0, 32) // Direct return, skips Solidity's redundant copying to save gas
172 | }
173 | }
174 |
175 | /// @inheritdoc IDelegateRegistry
176 | function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view override returns (bool valid) {
177 | if (!_invalidFrom(from)) {
178 | valid = _validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from);
179 | if (!Ops.or(rights == "", valid)) {
180 | valid = _validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from);
181 | }
182 | }
183 | assembly ("memory-safe") {
184 | // Only first 32 bytes of scratch space is accessed
185 | mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here
186 | return(0, 32) // Direct return, skips Solidity's redundant copying to save gas
187 | }
188 | }
189 |
190 | /// @inheritdoc IDelegateRegistry
191 | function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view override returns (bool valid) {
192 | if (!_invalidFrom(from)) {
193 | valid = _validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from)
194 | || _validateFrom(Hashes.erc721Location(from, "", to, tokenId, contract_), from);
195 | if (!Ops.or(rights == "", valid)) {
196 | valid = _validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from)
197 | || _validateFrom(Hashes.erc721Location(from, rights, to, tokenId, contract_), from);
198 | }
199 | }
200 | assembly ("memory-safe") {
201 | // Only first 32 bytes of scratch space is accessed
202 | mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here
203 | return(0, 32) // Direct return, skips Solidity's redundant copying to save gas
204 | }
205 | }
206 |
207 | /// @inheritdoc IDelegateRegistry
208 | function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view override returns (uint256 amount) {
209 | if (!_invalidFrom(from)) {
210 | amount = (_validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from))
211 | ? type(uint256).max
212 | : _loadDelegationUint(Hashes.erc20Location(from, "", to, contract_), Storage.POSITIONS_AMOUNT);
213 | if (!Ops.or(rights == "", amount == type(uint256).max)) {
214 | uint256 rightsBalance = (_validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from))
215 | ? type(uint256).max
216 | : _loadDelegationUint(Hashes.erc20Location(from, rights, to, contract_), Storage.POSITIONS_AMOUNT);
217 | amount = Ops.max(rightsBalance, amount);
218 | }
219 | }
220 | assembly ("memory-safe") {
221 | mstore(0, amount) // Only first 32 bytes of scratch space being accessed
222 | return(0, 32) // Direct return, skips Solidity's redundant copying to save gas
223 | }
224 | }
225 |
226 | /// @inheritdoc IDelegateRegistry
227 | function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view override returns (uint256 amount) {
228 | if (!_invalidFrom(from)) {
229 | amount = (_validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from))
230 | ? type(uint256).max
231 | : _loadDelegationUint(Hashes.erc1155Location(from, "", to, tokenId, contract_), Storage.POSITIONS_AMOUNT);
232 | if (!Ops.or(rights == "", amount == type(uint256).max)) {
233 | uint256 rightsBalance = (_validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from))
234 | ? type(uint256).max
235 | : _loadDelegationUint(Hashes.erc1155Location(from, rights, to, tokenId, contract_), Storage.POSITIONS_AMOUNT);
236 | amount = Ops.max(rightsBalance, amount);
237 | }
238 | }
239 | assembly ("memory-safe") {
240 | mstore(0, amount) // Only first 32 bytes of scratch space is accessed
241 | return(0, 32) // Direct return, skips Solidity's redundant copying to save gas
242 | }
243 | }
244 |
245 | /**
246 | * ----------- ENUMERATIONS -----------
247 | */
248 |
249 | /// @inheritdoc IDelegateRegistry
250 | function getIncomingDelegations(address to) external view override returns (Delegation[] memory delegations_) {
251 | delegations_ = _getValidDelegationsFromHashes(incomingDelegationHashes[to]);
252 | }
253 |
254 | /// @inheritdoc IDelegateRegistry
255 | function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations_) {
256 | delegations_ = _getValidDelegationsFromHashes(outgoingDelegationHashes[from]);
257 | }
258 |
259 | /// @inheritdoc IDelegateRegistry
260 | function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes) {
261 | delegationHashes = _getValidDelegationHashesFromHashes(incomingDelegationHashes[to]);
262 | }
263 |
264 | /// @inheritdoc IDelegateRegistry
265 | function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes) {
266 | delegationHashes = _getValidDelegationHashesFromHashes(outgoingDelegationHashes[from]);
267 | }
268 |
269 | /// @inheritdoc IDelegateRegistry
270 | function getDelegationsFromHashes(bytes32[] calldata hashes) external view returns (Delegation[] memory delegations_) {
271 | delegations_ = new Delegation[](hashes.length);
272 | unchecked {
273 | for (uint256 i = 0; i < hashes.length; ++i) {
274 | bytes32 location = Hashes.location(hashes[i]);
275 | address from = _loadFrom(location);
276 | if (_invalidFrom(from)) {
277 | delegations_[i] = Delegation({type_: DelegationType.NONE, to: address(0), from: address(0), rights: "", amount: 0, contract_: address(0), tokenId: 0});
278 | } else {
279 | (, address to, address contract_) = _loadDelegationAddresses(location);
280 | delegations_[i] = Delegation({
281 | type_: Hashes.decodeType(hashes[i]),
282 | to: to,
283 | from: from,
284 | rights: _loadDelegationBytes32(location, Storage.POSITIONS_RIGHTS),
285 | amount: _loadDelegationUint(location, Storage.POSITIONS_AMOUNT),
286 | contract_: contract_,
287 | tokenId: _loadDelegationUint(location, Storage.POSITIONS_TOKEN_ID)
288 | });
289 | }
290 | }
291 | }
292 | }
293 |
294 | /**
295 | * ----------- EXTERNAL STORAGE ACCESS -----------
296 | */
297 | function readSlot(bytes32 location) external view returns (bytes32 contents) {
298 | assembly {
299 | contents := sload(location)
300 | }
301 | }
302 |
303 | function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory contents) {
304 | uint256 length = locations.length;
305 | contents = new bytes32[](length);
306 | bytes32 tempLocation;
307 | bytes32 tempValue;
308 | unchecked {
309 | for (uint256 i = 0; i < length; ++i) {
310 | tempLocation = locations[i];
311 | assembly {
312 | tempValue := sload(tempLocation)
313 | }
314 | contents[i] = tempValue;
315 | }
316 | }
317 | }
318 |
319 | /**
320 | * ----------- ERC165 -----------
321 | */
322 |
323 | /// @notice Query if a contract implements an ERC-165 interface
324 | /// @param interfaceId The interface identifier
325 | /// @return valid Whether the queried interface is supported
326 | function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
327 | return Ops.or(interfaceId == type(IDelegateRegistry).interfaceId, interfaceId == 0x01ffc9a7);
328 | }
329 |
330 | /**
331 | * ----------- INTERNAL -----------
332 | */
333 |
334 | /// @dev Helper function to push new delegation hashes to the incoming and outgoing hashes mappings
335 | function _pushDelegationHashes(address from, address to, bytes32 delegationHash) internal {
336 | outgoingDelegationHashes[from].push(delegationHash);
337 | incomingDelegationHashes[to].push(delegationHash);
338 | }
339 |
340 | /// @dev Helper function that writes bytes32 data to delegation data location at array position
341 | function _writeDelegation(bytes32 location, uint256 position, bytes32 data) internal {
342 | assembly {
343 | sstore(add(location, position), data)
344 | }
345 | }
346 |
347 | /// @dev Helper function that writes uint256 data to delegation data location at array position
348 | function _writeDelegation(bytes32 location, uint256 position, uint256 data) internal {
349 | assembly {
350 | sstore(add(location, position), data)
351 | }
352 | }
353 |
354 | /// @dev Helper function that writes addresses according to the packing rule for delegation storage
355 | function _writeDelegationAddresses(bytes32 location, address from, address to, address contract_) internal {
356 | (bytes32 firstSlot, bytes32 secondSlot) = Storage.packAddresses(from, to, contract_);
357 | uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED;
358 | uint256 secondPacked = Storage.POSITIONS_SECOND_PACKED;
359 | assembly {
360 | sstore(add(location, firstPacked), firstSlot)
361 | sstore(add(location, secondPacked), secondSlot)
362 | }
363 | }
364 |
365 | /// @dev Helper function that writes `from` while preserving the rest of the storage slot
366 | function _updateFrom(bytes32 location, address from) internal {
367 | uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED;
368 | uint256 cleanAddress = Storage.CLEAN_ADDRESS;
369 | uint256 cleanUpper12Bytes = type(uint256).max << 160;
370 | assembly {
371 | let slot := and(sload(add(location, firstPacked)), cleanUpper12Bytes)
372 | sstore(add(location, firstPacked), or(slot, and(from, cleanAddress)))
373 | }
374 | }
375 |
376 | /// @dev Helper function that takes an array of delegation hashes and returns an array of Delegation structs with their onchain information
377 | function _getValidDelegationsFromHashes(bytes32[] storage hashes) internal view returns (Delegation[] memory delegations_) {
378 | uint256 count = 0;
379 | uint256 hashesLength = hashes.length;
380 | bytes32 hash;
381 | bytes32[] memory filteredHashes = new bytes32[](hashesLength);
382 | unchecked {
383 | for (uint256 i = 0; i < hashesLength; ++i) {
384 | hash = hashes[i];
385 | if (_invalidFrom(_loadFrom(Hashes.location(hash)))) continue;
386 | filteredHashes[count++] = hash;
387 | }
388 | delegations_ = new Delegation[](count);
389 | bytes32 location;
390 | for (uint256 i = 0; i < count; ++i) {
391 | hash = filteredHashes[i];
392 | location = Hashes.location(hash);
393 | (address from, address to, address contract_) = _loadDelegationAddresses(location);
394 | delegations_[i] = Delegation({
395 | type_: Hashes.decodeType(hash),
396 | to: to,
397 | from: from,
398 | rights: _loadDelegationBytes32(location, Storage.POSITIONS_RIGHTS),
399 | amount: _loadDelegationUint(location, Storage.POSITIONS_AMOUNT),
400 | contract_: contract_,
401 | tokenId: _loadDelegationUint(location, Storage.POSITIONS_TOKEN_ID)
402 | });
403 | }
404 | }
405 | }
406 |
407 | /// @dev Helper function that takes an array of delegation hashes and returns an array of valid delegation hashes
408 | function _getValidDelegationHashesFromHashes(bytes32[] storage hashes) internal view returns (bytes32[] memory validHashes) {
409 | uint256 count = 0;
410 | uint256 hashesLength = hashes.length;
411 | bytes32 hash;
412 | bytes32[] memory filteredHashes = new bytes32[](hashesLength);
413 | unchecked {
414 | for (uint256 i = 0; i < hashesLength; ++i) {
415 | hash = hashes[i];
416 | if (_invalidFrom(_loadFrom(Hashes.location(hash)))) continue;
417 | filteredHashes[count++] = hash;
418 | }
419 | validHashes = new bytes32[](count);
420 | for (uint256 i = 0; i < count; ++i) {
421 | validHashes[i] = filteredHashes[i];
422 | }
423 | }
424 | }
425 |
426 | /// @dev Helper function that loads delegation data from a particular array position and returns as bytes32
427 | function _loadDelegationBytes32(bytes32 location, uint256 position) internal view returns (bytes32 data) {
428 | assembly {
429 | data := sload(add(location, position))
430 | }
431 | }
432 |
433 | /// @dev Helper function that loads delegation data from a particular array position and returns as uint256
434 | function _loadDelegationUint(bytes32 location, uint256 position) internal view returns (uint256 data) {
435 | assembly {
436 | data := sload(add(location, position))
437 | }
438 | }
439 |
440 | // @dev Helper function that loads the from address from storage according to the packing rule for delegation storage
441 | function _loadFrom(bytes32 location) internal view returns (address) {
442 | bytes32 data;
443 | uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED;
444 | assembly {
445 | data := sload(add(location, firstPacked))
446 | }
447 | return Storage.unpackAddress(data);
448 | }
449 |
450 | /// @dev Helper function to establish whether a delegation is enabled
451 | function _validateFrom(bytes32 location, address from) internal view returns (bool) {
452 | return (from == _loadFrom(location));
453 | }
454 |
455 | /// @dev Helper function that loads the address for the delegation according to the packing rule for delegation storage
456 | function _loadDelegationAddresses(bytes32 location) internal view returns (address from, address to, address contract_) {
457 | bytes32 firstSlot;
458 | bytes32 secondSlot;
459 | uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED;
460 | uint256 secondPacked = Storage.POSITIONS_SECOND_PACKED;
461 | assembly {
462 | firstSlot := sload(add(location, firstPacked))
463 | secondSlot := sload(add(location, secondPacked))
464 | }
465 | (from, to, contract_) = Storage.unpackAddresses(firstSlot, secondSlot);
466 | }
467 |
468 | function _invalidFrom(address from) internal pure returns (bool) {
469 | return Ops.or(from == Storage.DELEGATION_EMPTY, from == Storage.DELEGATION_REVOKED);
470 | }
471 | }
472 |
--------------------------------------------------------------------------------
/src/IDelegateRegistry.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity >=0.8.13;
3 |
4 | /**
5 | * @title IDelegateRegistry
6 | * @custom:version 2.0
7 | * @custom:author foobar (0xfoobar)
8 | * @notice A standalone immutable registry storing delegated permissions from one address to another
9 | */
10 | interface IDelegateRegistry {
11 | /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
12 | enum DelegationType {
13 | NONE,
14 | ALL,
15 | CONTRACT,
16 | ERC721,
17 | ERC20,
18 | ERC1155
19 | }
20 |
21 | /// @notice Struct for returning delegations
22 | struct Delegation {
23 | DelegationType type_;
24 | address to;
25 | address from;
26 | bytes32 rights;
27 | address contract_;
28 | uint256 tokenId;
29 | uint256 amount;
30 | }
31 |
32 | /// @notice Emitted when an address delegates or revokes rights for their entire wallet
33 | event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);
34 |
35 | /// @notice Emitted when an address delegates or revokes rights for a contract address
36 | event DelegateContract(address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable);
37 |
38 | /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
39 | event DelegateERC721(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable);
40 |
41 | /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
42 | event DelegateERC20(address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount);
43 |
44 | /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
45 | event DelegateERC1155(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount);
46 |
47 | /// @notice Thrown if multicall calldata is malformed
48 | error MulticallFailed();
49 |
50 | /**
51 | * ----------- WRITE -----------
52 | */
53 |
54 | /**
55 | * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
56 | * @param data The encoded function data for each of the calls to make to this contract
57 | * @return results The results from each of the calls passed in via data
58 | */
59 | function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
60 |
61 | /**
62 | * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
63 | * @param to The address to act as delegate
64 | * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
65 | * @param enable Whether to enable or disable this delegation, true delegates and false revokes
66 | * @return delegationHash The unique identifier of the delegation
67 | */
68 | function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
69 |
70 | /**
71 | * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
72 | * @param to The address to act as delegate
73 | * @param contract_ The contract whose rights are being delegated
74 | * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
75 | * @param enable Whether to enable or disable this delegation, true delegates and false revokes
76 | * @return delegationHash The unique identifier of the delegation
77 | */
78 | function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
79 |
80 | /**
81 | * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
82 | * @param to The address to act as delegate
83 | * @param contract_ The contract whose rights are being delegated
84 | * @param tokenId The token id to delegate
85 | * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
86 | * @param enable Whether to enable or disable this delegation, true delegates and false revokes
87 | * @return delegationHash The unique identifier of the delegation
88 | */
89 | function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);
90 |
91 | /**
92 | * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
93 | * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
94 | * @param to The address to act as delegate
95 | * @param contract_ The address for the fungible token contract
96 | * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
97 | * @param amount The amount to delegate, > 0 delegates and 0 revokes
98 | * @return delegationHash The unique identifier of the delegation
99 | */
100 | function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);
101 |
102 | /**
103 | * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
104 | * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
105 | * @param to The address to act as delegate
106 | * @param contract_ The address of the contract that holds the token
107 | * @param tokenId The token id to delegate
108 | * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
109 | * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
110 | * @return delegationHash The unique identifier of the delegation
111 | */
112 | function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);
113 |
114 | /**
115 | * ----------- CHECKS -----------
116 | */
117 |
118 | /**
119 | * @notice Check if `to` is a delegate of `from` for the entire wallet
120 | * @param to The potential delegate address
121 | * @param from The potential address who delegated rights
122 | * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
123 | * @return valid Whether delegate is granted to act on the from's behalf
124 | */
125 | function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);
126 |
127 | /**
128 | * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
129 | * @param to The delegated address to check
130 | * @param contract_ The specific contract address being checked
131 | * @param from The cold wallet who issued the delegation
132 | * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
133 | * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
134 | */
135 | function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view returns (bool);
136 |
137 | /**
138 | * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
139 | * @param to The delegated address to check
140 | * @param contract_ The specific contract address being checked
141 | * @param tokenId The token id for the token to delegating
142 | * @param from The wallet that issued the delegation
143 | * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
144 | * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
145 | */
146 | function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (bool);
147 |
148 | /**
149 | * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
150 | * @param to The delegated address to check
151 | * @param contract_ The address of the token contract
152 | * @param from The cold wallet who issued the delegation
153 | * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
154 | * @return balance The delegated balance, which will be 0 if the delegation does not exist
155 | */
156 | function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256);
157 |
158 | /**
159 | * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
160 | * @param to The delegated address to check
161 | * @param contract_ The address of the token contract
162 | * @param tokenId The token id to check the delegated amount of
163 | * @param from The cold wallet who issued the delegation
164 | * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
165 | * @return balance The delegated balance, which will be 0 if the delegation does not exist
166 | */
167 | function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256);
168 |
169 | /**
170 | * ----------- ENUMERATIONS -----------
171 | */
172 |
173 | /**
174 | * @notice Returns all enabled delegations a given delegate has received
175 | * @param to The address to retrieve delegations for
176 | * @return delegations Array of Delegation structs
177 | */
178 | function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);
179 |
180 | /**
181 | * @notice Returns all enabled delegations an address has given out
182 | * @param from The address to retrieve delegations for
183 | * @return delegations Array of Delegation structs
184 | */
185 | function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);
186 |
187 | /**
188 | * @notice Returns all hashes associated with enabled delegations an address has received
189 | * @param to The address to retrieve incoming delegation hashes for
190 | * @return delegationHashes Array of delegation hashes
191 | */
192 | function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);
193 |
194 | /**
195 | * @notice Returns all hashes associated with enabled delegations an address has given out
196 | * @param from The address to retrieve outgoing delegation hashes for
197 | * @return delegationHashes Array of delegation hashes
198 | */
199 | function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);
200 |
201 | /**
202 | * @notice Returns the delegations for a given array of delegation hashes
203 | * @param delegationHashes is an array of hashes that correspond to delegations
204 | * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
205 | */
206 | function getDelegationsFromHashes(bytes32[] calldata delegationHashes) external view returns (Delegation[] memory delegations);
207 |
208 | /**
209 | * ----------- STORAGE ACCESS -----------
210 | */
211 |
212 | /**
213 | * @notice Allows external contracts to read arbitrary storage slots
214 | */
215 | function readSlot(bytes32 location) external view returns (bytes32);
216 |
217 | /**
218 | * @notice Allows external contracts to read an arbitrary array of storage slots
219 | */
220 | function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
221 | }
222 |
--------------------------------------------------------------------------------
/src/examples/Airdrop.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol";
5 | import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
6 | import {Math} from "openzeppelin/utils/math/Math.sol";
7 | import {DelegateClaim} from "src/examples/DelegateClaim.sol";
8 |
9 | /**
10 | * @title Airdrop
11 | * @notice A contract for distributing tokens through a merkle tree-based airdrop mechanism.
12 | * @dev Inherits the DelegateClaim contract to allow delegates to claim on behalf of vaults.
13 | */
14 | contract Airdrop is ERC20, DelegateClaim {
15 | bytes32 public immutable merkleRoot;
16 | mapping(address vault => uint256 claimed) public claimed;
17 |
18 | /**
19 | * @notice Initializes the Airdrop contract.
20 | * @param registry_ The address of the v2 delegation registry contract.
21 | * @param referenceToken_ The address of the reference token used by delegateClaimable inherited from DelegateClaim.
22 | * @param totalSupply_ The total supply of the airdrop token.
23 | * @param merkleRoot_ The root hash of the merkle tree representing the airdrop.
24 | */
25 | constructor(address registry_, address referenceToken_, bytes32 airdropRight, uint256 totalSupply_, bytes32 merkleRoot_)
26 | ERC20("Airdrop", "Air")
27 | DelegateClaim(registry_, referenceToken_, airdropRight)
28 | {
29 | _mint(address(this), totalSupply_);
30 | merkleRoot = merkleRoot_;
31 | }
32 |
33 | /**
34 | * @notice Allows the caller to claim tokens from the airdrop based on the merkle proof, and if they aren't the
35 | * vault, claim tokens on behalf of vault if they have a delegation.
36 | * @param vault The address of the vault.
37 | * @param claimAmount The amount of tokens to claim from the airdrop.
38 | * @param airdropSize The total size of the airdrop for the vault.
39 | * @param merkleProof The merkle proof to verify the airdrop allocation for vault of airdropSize.
40 | */
41 | function claim(address vault, uint256 claimAmount, uint256 airdropSize, bytes32[] calldata merkleProof) external {
42 | // First verify that airdrop for vault of amount airdropSize exists
43 | require(MerkleProof.verifyCalldata(merkleProof, merkleRoot, keccak256(abi.encodePacked(vault, airdropSize))), "Invalid Proof");
44 | // Set claimable to the minimum of claimAmount and the maximum remaining airdrop tokens that can be claimed by
45 | // the vault
46 | uint256 claimable = Math.min(claimAmount, airdropSize - claimed[vault]);
47 | // If msg.sender != vault, check balance delegation instead
48 | if (msg.sender != vault) claimable = _delegateClaimable(vault, claimable);
49 | // Increment claimed
50 | claimed[vault] += claimable;
51 | // Transfer tokens to msg.sender
52 | _transfer(address(this), msg.sender, claimable);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/examples/DelegateClaim.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {IDelegateRegistry} from "src/IDelegateRegistry.sol";
5 | import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
6 |
7 | /**
8 | * @title DelegateClaim
9 | * @dev A contract for claiming tokens on behalf of a vault using the v2 delegation registry.
10 | */
11 | contract DelegateClaim {
12 | IDelegateRegistry public immutable delegateRegistry;
13 | address public immutable referenceToken;
14 | bytes32 public immutable acceptedRight;
15 | /**
16 | * @dev stores accounting for the tokens claimed by a delegate on behalf of a vault.
17 | */
18 | mapping(address vault => mapping(address delegate => uint256 claimed)) public delegateClaimed;
19 |
20 | /**
21 | * @param registry_ The address of the v2 delegation registry contract.
22 | * @param referenceToken_ The address of the reference token.
23 | */
24 | constructor(address registry_, address referenceToken_, bytes32 acceptedRight_) {
25 | require(registry_ != address(0), "RegistryZero");
26 | require(referenceToken_ != address(0), "ReferenceTokenZero");
27 | delegateRegistry = IDelegateRegistry(registry_);
28 | referenceToken = referenceToken_;
29 | acceptedRight = acceptedRight_;
30 | }
31 |
32 | /**
33 | * @dev Calculates the claimable tokens for a specific vault and claimable amount.
34 | * @param vault The address of the vault.
35 | * @param claimable The amount of tokens that can be claimed by vault.
36 | * @return The actual amount of tokens that can be claimed by the caller on behalf of vault.
37 | */
38 | function _delegateClaimable(address vault, uint256 claimable) internal returns (uint256) {
39 | // Fetch the referenceToken balance delegated by the vault to msg.sender from the delegate registry
40 | uint256 balance = delegateRegistry.checkDelegateForERC20(msg.sender, vault, referenceToken, acceptedRight);
41 | // Load the amount tokens already claimed by msg.sender on behalf of the vault
42 | uint256 alreadyClaimed = delegateClaimed[vault][msg.sender];
43 | // Revert if msg.sender has already used up all the delegated balance
44 | require(balance > alreadyClaimed, "Insufficient Delegation");
45 | // Calculate maximum further tokens that can be claimed by msg.sender on behalf of vault
46 | uint256 remainingLimit = balance - alreadyClaimed;
47 | // Reduce claimable to remainingLimit if the limit is smaller
48 | claimable = Math.min(claimable, remainingLimit);
49 | // Increment beneficiaryClaimed by this amount
50 | delegateClaimed[vault][msg.sender] += claimable;
51 | return claimable;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/examples/IPLicenseCheck.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {IERC721} from "openzeppelin/token/ERC721/IERC721.sol";
5 | import {IDelegateRegistry} from "src/IDelegateRegistry.sol";
6 |
7 | /**
8 | * @title IP License Checker
9 | * @notice A contract for checking whether a wallet has been granted IP licenses for an NFT. It supports both NFT vault -> IP license and NFT vault -> IP
10 | * licensor -> IP license workflows.
11 | */
12 | contract IPLicenseCheck {
13 | IDelegateRegistry public immutable delegateRegistry;
14 |
15 | /// @param registry_ is the address of the v2 delegation registry contract.
16 | constructor(address registry_) {
17 | delegateRegistry = IDelegateRegistry(registry_);
18 | }
19 |
20 | /**
21 | * @notice Checks whether a wallet has been granted an IP license for an NFT
22 | * @param wallet to be checked for having the IP license
23 | * @param vault is the address that owns the NFT
24 | * @param nftContract is the contract address of the NFT
25 | * @param tokenId is the ID of the NFT
26 | * @param ipLicense is a bytes32 representation of the IP license to be check for
27 | * @return valid is returned true if the wallet has rights to the IP license for the NFT
28 | */
29 | function checkForIPLicense(address wallet, address vault, address nftContract, uint256 tokenId, bytes32 ipLicense) external view returns (bool valid) {
30 | // Return false if the vault does not own the NFT
31 | if (IERC721(nftContract).ownerOf(tokenId) != vault) return false;
32 | // Call the v2 registry, which will return true if the wallet has a valid delegation or sub-delegation with "" rights
33 | return delegateRegistry.checkDelegateForERC721(wallet, vault, nftContract, tokenId, ipLicense);
34 | }
35 |
36 | /**
37 | * @notice Checks whether a wallet has been granted an IP license for an NFT
38 | * @param wallet to be checked for having the IP license
39 | * @param licensor is the address that has been granted the right to create IP licenses for the vault
40 | * @param vault is the address that owns the NFT
41 | * @param nftContract is the contract address of the NFT
42 | * @param tokenId is the ID of the NFT
43 | * @param ipLicense is a bytes32 representation of the IP license to be check for
44 | * @return valid is returned true if the wallet has rights to the IP license for the NFT
45 | */
46 | function checkForIPLicenseFromLicensor(address wallet, address licensor, address vault, address nftContract, uint256 tokenId, bytes32 ipLicense) external view returns (bool) {
47 | // Return false if the vault does not own the NFT
48 | if (IERC721(nftContract).ownerOf(tokenId) != vault) return false;
49 | // Call the v2 registry, and return false if vault has not granted "ip licensor" rights to the licensor for the nft
50 | if (!delegateRegistry.checkDelegateForERC721(licensor, vault, nftContract, tokenId, "ip licensor")) return false;
51 | // Call the v2 registry, which will return true if the wallet has a valid delegation or sub-delegation with "" rights
52 | return delegateRegistry.checkDelegateForERC721(wallet, licensor, nftContract, tokenId, ipLicense);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/libraries/RegistryHashes.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {IDelegateRegistry} from "../IDelegateRegistry.sol";
5 |
6 | /**
7 | * @title Library for calculating the hashes and storage locations used in the delegate registry
8 | *
9 | * The encoding for the 5 types of delegate registry hashes should be as follows:
10 | *
11 | * ALL: keccak256(abi.encodePacked(rights, from, to))
12 | * CONTRACT: keccak256(abi.encodePacked(rights, from, to, contract_))
13 | * ERC721: keccak256(abi.encodePacked(rights, from, to, contract_, tokenId))
14 | * ERC20: keccak256(abi.encodePacked(rights, from, to, contract_))
15 | * ERC1155: keccak256(abi.encodePacked(rights, from, to, contract_, tokenId))
16 | *
17 | * To avoid collisions between the hashes with respect to type, the hash is shifted left by one byte
18 | * and the last byte is then encoded with a unique number for the delegation type
19 | *
20 | */
21 | library RegistryHashes {
22 | /// @dev Used to delete everything but the last byte of a 32 byte word with and(word, EXTRACT_LAST_BYTE)
23 | uint256 internal constant EXTRACT_LAST_BYTE = 0xff;
24 | /// @dev Constants for the delegate registry delegation type enumeration
25 | uint256 internal constant ALL_TYPE = 1;
26 | uint256 internal constant CONTRACT_TYPE = 2;
27 | uint256 internal constant ERC721_TYPE = 3;
28 | uint256 internal constant ERC20_TYPE = 4;
29 | uint256 internal constant ERC1155_TYPE = 5;
30 | /// @dev Constant for the location of the delegations array in the delegate registry, defined to be zero
31 | uint256 internal constant DELEGATION_SLOT = 0;
32 |
33 | /**
34 | * @notice Helper function to decode last byte of a delegation hash into its delegation type enum
35 | * @param inputHash The bytehash to decode the type from
36 | * @return decodedType The delegation type
37 | */
38 | function decodeType(bytes32 inputHash) internal pure returns (IDelegateRegistry.DelegationType decodedType) {
39 | assembly {
40 | decodedType := and(inputHash, EXTRACT_LAST_BYTE)
41 | }
42 | }
43 |
44 | /**
45 | * @notice Helper function that computes the storage location of a particular delegation array
46 | * @dev Storage keys further down the array can be obtained by adding computedLocation with the element position
47 | * @dev Follows the solidity storage location encoding for a mapping(bytes32 => fixedArray) at the position of the delegationSlot
48 | * @param inputHash The bytehash to decode the type from
49 | * @return computedLocation is the storage key of the delegation array at position 0
50 | */
51 | function location(bytes32 inputHash) internal pure returns (bytes32 computedLocation) {
52 | assembly ("memory-safe") {
53 | // This block only allocates memory in the scratch space
54 | mstore(0, inputHash)
55 | mstore(32, DELEGATION_SLOT)
56 | computedLocation := keccak256(0, 64) // Run keccak256 over bytes in scratch space to obtain the storage key
57 | }
58 | }
59 |
60 | /**
61 | * @notice Helper function to compute delegation hash for `DelegationType.ALL`
62 | * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to))` then left-shift by 1 byte and write the delegation type to the cleaned last byte
63 | * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes
64 | * @param from The address making the delegation
65 | * @param rights The rights specified by the delegation
66 | * @param to The address receiving the delegation
67 | * @return hash The delegation parameters encoded with ALL_TYPE
68 | */
69 | function allHash(address from, bytes32 rights, address to) internal pure returns (bytes32 hash) {
70 | assembly ("memory-safe") {
71 | // This block only allocates memory after the free memory pointer
72 | let ptr := mload(64) // Load the free memory pointer
73 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
74 | mstore(add(ptr, 40), to)
75 | mstore(add(ptr, 20), from)
76 | mstore(ptr, rights)
77 | hash := or(shl(8, keccak256(ptr, 72)), ALL_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte
78 | }
79 | }
80 |
81 | /**
82 | * @notice Helper function to compute delegation location for `DelegationType.ALL`
83 | * @dev Equivalent to `location(allHash(rights, from, to))`
84 | * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes
85 | * @param from The address making the delegation
86 | * @param rights The rights specified by the delegation
87 | * @param to The address receiving the delegation
88 | * @return computedLocation The storage location of the all delegation with those parameters in the delegations mapping
89 | */
90 | function allLocation(address from, bytes32 rights, address to) internal pure returns (bytes32 computedLocation) {
91 | assembly ("memory-safe") {
92 | // This block only allocates memory after the free memory pointer and in the scratch space
93 | let ptr := mload(64) // Load the free memory pointer
94 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
95 | mstore(add(ptr, 40), to)
96 | mstore(add(ptr, 20), from)
97 | mstore(ptr, rights)
98 | mstore(0, or(shl(8, keccak256(ptr, 72)), ALL_TYPE)) // Computes `allHash`, then stores the result in scratch space
99 | mstore(32, DELEGATION_SLOT)
100 | computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key
101 | }
102 | }
103 |
104 | /**
105 | * @notice Helper function to compute delegation hash for `DelegationType.CONTRACT`
106 | * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_)) left-shifted by 1 then last byte overwritten with CONTRACT_TYPE
107 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
108 | * @param from The address making the delegation
109 | * @param rights The rights specified by the delegation
110 | * @param to The address receiving the delegation
111 | * @param contract_ The address of the contract specified by the delegation
112 | * @return hash The delegation parameters encoded with CONTRACT_TYPE
113 | */
114 | function contractHash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) {
115 | assembly ("memory-safe") {
116 | // This block only allocates memory after the free memory pointer
117 | let ptr := mload(64) // Load the free memory pointer
118 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
119 | mstore(add(ptr, 60), contract_)
120 | mstore(add(ptr, 40), to)
121 | mstore(add(ptr, 20), from)
122 | mstore(ptr, rights)
123 | hash := or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte
124 | }
125 | }
126 |
127 | /**
128 | * @notice Helper function to compute delegation location for `DelegationType.CONTRACT`
129 | * @dev Equivalent to `location(contractHash(rights, from, to, contract_))`
130 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
131 | * @param from The address making the delegation
132 | * @param rights The rights specified by the delegation
133 | * @param to The address receiving the delegation
134 | * @param contract_ The address of the contract specified by the delegation
135 | * @return computedLocation The storage location of the contract delegation with those parameters in the delegations mapping
136 | */
137 | function contractLocation(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) {
138 | assembly ("memory-safe") {
139 | // This block only allocates memory after the free memory pointer and in the scratch space
140 | let ptr := mload(64) // Load free memory pointer
141 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
142 | mstore(add(ptr, 60), contract_)
143 | mstore(add(ptr, 40), to)
144 | mstore(add(ptr, 20), from)
145 | mstore(ptr, rights)
146 | mstore(0, or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE)) // Computes `contractHash`, then stores the result in scratch space
147 | mstore(32, DELEGATION_SLOT)
148 | computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key
149 | }
150 | }
151 |
152 | /**
153 | * @notice Helper function to compute delegation hash for `DelegationType.ERC721`
154 | * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted by 1 then last byte overwritten with ERC721_TYPE
155 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
156 | * @param from The address making the delegation
157 | * @param rights The rights specified by the delegation
158 | * @param to The address receiving the delegation
159 | * @param tokenId The id of the token specified by the delegation
160 | * @param contract_ The address of the contract specified by the delegation
161 | * @return hash The delegation parameters encoded with ERC721_TYPE
162 | */
163 | function erc721Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) {
164 | assembly ("memory-safe") {
165 | // This block only allocates memory after the free memory pointer
166 | let ptr := mload(64) // Cache the free memory pointer.
167 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
168 | mstore(add(ptr, 92), tokenId)
169 | mstore(add(ptr, 60), contract_)
170 | mstore(add(ptr, 40), to)
171 | mstore(add(ptr, 20), from)
172 | mstore(ptr, rights)
173 | hash := or(shl(8, keccak256(ptr, 124)), ERC721_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte
174 | }
175 | }
176 |
177 | /**
178 | * @notice Helper function to compute delegation location for `DelegationType.ERC721`
179 | * @dev Equivalent to `location(ERC721Hash(rights, from, to, contract_, tokenId))`
180 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
181 | * @param from The address making the delegation
182 | * @param rights The rights specified by the delegation
183 | * @param to The address receiving the delegation
184 | * @param tokenId The id of the ERC721 token
185 | * @param contract_ The address of the ERC721 token contract
186 | * @return computedLocation The storage location of the ERC721 delegation with those parameters in the delegations mapping
187 | */
188 | function erc721Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) {
189 | assembly ("memory-safe") {
190 | // This block only allocates memory after the free memory pointer and in the scratch space
191 | let ptr := mload(64) // Cache the free memory pointer.
192 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
193 | mstore(add(ptr, 92), tokenId)
194 | mstore(add(ptr, 60), contract_)
195 | mstore(add(ptr, 40), to)
196 | mstore(add(ptr, 20), from)
197 | mstore(ptr, rights)
198 | mstore(0, or(shl(8, keccak256(ptr, 124)), ERC721_TYPE)) // Computes erc721Hash, then stores the result in scratch space
199 | mstore(32, DELEGATION_SLOT)
200 | computedLocation := keccak256(0, 64) // Runs keccak256 over the scratch space to obtain the storage key
201 | }
202 | }
203 |
204 | /**
205 | * @notice Helper function to compute delegation hash for `DelegationType.ERC20`
206 | * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_))` with the last byte overwritten with ERC20_TYPE
207 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
208 | * @param from The address making the delegation
209 | * @param rights The rights specified by the delegation
210 | * @param to The address receiving the delegation
211 | * @param contract_ The address of the ERC20 token contract
212 | * @return hash The parameters encoded with ERC20_TYPE
213 | */
214 | function erc20Hash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) {
215 | assembly ("memory-safe") {
216 | // This block only allocates memory after the free memory pointer
217 | let ptr := mload(64) // Load free memory pointer
218 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
219 | mstore(add(ptr, 60), contract_)
220 | mstore(add(ptr, 40), to)
221 | mstore(add(ptr, 20), from)
222 | mstore(ptr, rights)
223 | hash := or(shl(8, keccak256(ptr, 92)), ERC20_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte
224 | }
225 | }
226 |
227 | /**
228 | * @notice Helper function to compute delegation location for `DelegationType.ERC20`
229 | * @dev Equivalent to `location(ERC20Hash(rights, from, to, contract_))`
230 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
231 | * @param from The address making the delegation
232 | * @param rights The rights specified by the delegation
233 | * @param to The address receiving the delegation
234 | * @param contract_ The address of the ERC20 token contract
235 | * @return computedLocation The storage location of the ERC20 delegation with those parameters in the delegations mapping
236 | */
237 | function erc20Location(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) {
238 | assembly ("memory-safe") {
239 | // This block only allocates memory after the free memory pointer and in the scratch space
240 | let ptr := mload(64) // Loads the free memory pointer
241 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
242 | mstore(add(ptr, 60), contract_)
243 | mstore(add(ptr, 40), to)
244 | mstore(add(ptr, 20), from)
245 | mstore(ptr, rights)
246 | mstore(0, or(shl(8, keccak256(ptr, 92)), ERC20_TYPE)) // Computes erc20Hash, then stores the result in scratch space
247 | mstore(32, DELEGATION_SLOT)
248 | computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key
249 | }
250 | }
251 |
252 | /**
253 | * @notice Helper function to compute delegation hash for `DelegationType.ERC1155`
254 | * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted with the last byte overwritten with ERC1155_TYPE
255 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
256 | * @param from The address making the delegation
257 | * @param rights The rights specified by the delegation
258 | * @param to The address receiving the delegation
259 | * @param tokenId The id of the ERC1155 token
260 | * @param contract_ The address of the ERC1155 token contract
261 | * @return hash The parameters encoded with ERC1155_TYPE
262 | */
263 | function erc1155Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) {
264 | assembly ("memory-safe") {
265 | // This block only allocates memory after the free memory pointer
266 | let ptr := mload(64) // Load the free memory pointer.
267 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
268 | mstore(add(ptr, 92), tokenId)
269 | mstore(add(ptr, 60), contract_)
270 | mstore(add(ptr, 40), to)
271 | mstore(add(ptr, 20), from)
272 | mstore(ptr, rights)
273 | hash := or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte
274 | }
275 | }
276 |
277 | /**
278 | * @notice Helper function to compute delegation location for `DelegationType.ERC1155`
279 | * @dev Equivalent to `location(ERC1155Hash(rights, from, to, contract_, tokenId))`
280 | * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes
281 | * @param from The address making the delegation
282 | * @param rights The rights specified by the delegation
283 | * @param to The address receiving the delegation
284 | * @param tokenId The id of the ERC1155 token
285 | * @param contract_ The address of the ERC1155 token contract
286 | * @return computedLocation The storage location of the ERC1155 delegation with those parameters in the delegations mapping
287 | */
288 | function erc1155Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) {
289 | assembly ("memory-safe") {
290 | // This block only allocates memory after the free memory pointer and in the scratch space
291 | let ptr := mload(64) // Cache the free memory pointer.
292 | // Lay out the variables from last to first, agnostic to upper 96 bits of address words.
293 | mstore(add(ptr, 92), tokenId)
294 | mstore(add(ptr, 60), contract_)
295 | mstore(add(ptr, 40), to)
296 | mstore(add(ptr, 20), from)
297 | mstore(ptr, rights)
298 | mstore(0, or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE)) // Computes erc1155Hash, then stores the result in scratch space
299 | mstore(32, DELEGATION_SLOT)
300 | computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key
301 | }
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/src/libraries/RegistryOps.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | library RegistryOps {
5 | /// @dev `x > y ? x : y`.
6 | function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
7 | assembly {
8 | // `gt(y, x)` will evaluate to 1 if `y > x`, else 0.
9 | //
10 | // If `y > x`:
11 | // `x ^ ((x ^ y) * 1) = x ^ (x ^ y) = (x ^ x) ^ y = 0 ^ y = y`.
12 | // otherwise:
13 | // `x ^ ((x ^ y) * 0) = x ^ 0 = x`.
14 | z := xor(x, mul(xor(x, y), gt(y, x)))
15 | }
16 | }
17 |
18 | /// @dev `x & y`.
19 | function and(bool x, bool y) internal pure returns (bool z) {
20 | assembly {
21 | z := and(iszero(iszero(x)), iszero(iszero(y))) // Compiler cleans dirty booleans on the stack to 1, so do the same here
22 | }
23 | }
24 |
25 | /// @dev `x | y`.
26 | function or(bool x, bool y) internal pure returns (bool z) {
27 | assembly {
28 | z := or(iszero(iszero(x)), iszero(iszero(y))) // Compiler cleans dirty booleans on the stack to 1, so do the same here
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/libraries/RegistryStorage.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | library RegistryStorage {
5 | /// @dev Standardizes `from` storage flags to prevent double-writes in the delegation in/outbox if the same delegation is revoked and rewritten
6 | address internal constant DELEGATION_EMPTY = address(0);
7 | address internal constant DELEGATION_REVOKED = address(1);
8 |
9 | /// @dev Standardizes storage positions of delegation data
10 | uint256 internal constant POSITIONS_FIRST_PACKED = 0; // | 4 bytes empty | first 8 bytes of contract address | 20 bytes of from address |
11 | uint256 internal constant POSITIONS_SECOND_PACKED = 1; // | last 12 bytes of contract address | 20 bytes of to address |
12 | uint256 internal constant POSITIONS_RIGHTS = 2;
13 | uint256 internal constant POSITIONS_TOKEN_ID = 3;
14 | uint256 internal constant POSITIONS_AMOUNT = 4;
15 |
16 | /// @dev Used to clean address types of dirty bits with `and(address, CLEAN_ADDRESS)`
17 | uint256 internal constant CLEAN_ADDRESS = 0x00ffffffffffffffffffffffffffffffffffffffff;
18 |
19 | /// @dev Used to clean everything but the first 8 bytes of an address
20 | uint256 internal constant CLEAN_FIRST8_BYTES_ADDRESS = 0xffffffffffffffff << 96;
21 |
22 | /// @dev Used to clean everything but the first 8 bytes of an address in the packed position
23 | uint256 internal constant CLEAN_PACKED8_BYTES_ADDRESS = 0xffffffffffffffff << 160;
24 |
25 | /**
26 | * @notice Helper function that packs from, to, and contract_ address to into the two slot configuration
27 | * @param from The address making the delegation
28 | * @param to The address receiving the delegation
29 | * @param contract_ The contract address associated with the delegation (optional)
30 | * @return firstPacked The firstPacked storage configured with the parameters
31 | * @return secondPacked The secondPacked storage configured with the parameters
32 | * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned
33 | */
34 | function packAddresses(address from, address to, address contract_) internal pure returns (bytes32 firstPacked, bytes32 secondPacked) {
35 | assembly {
36 | firstPacked := or(shl(64, and(contract_, CLEAN_FIRST8_BYTES_ADDRESS)), and(from, CLEAN_ADDRESS))
37 | secondPacked := or(shl(160, contract_), and(to, CLEAN_ADDRESS))
38 | }
39 | }
40 |
41 | /**
42 | * @notice Helper function that unpacks from, to, and contract_ address inside the firstPacked secondPacked storage configuration
43 | * @param firstPacked The firstPacked storage to be decoded
44 | * @param secondPacked The secondPacked storage to be decoded
45 | * @return from The address making the delegation
46 | * @return to The address receiving the delegation
47 | * @return contract_ The contract address associated with the delegation
48 | * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned
49 | */
50 | function unpackAddresses(bytes32 firstPacked, bytes32 secondPacked) internal pure returns (address from, address to, address contract_) {
51 | assembly {
52 | from := and(firstPacked, CLEAN_ADDRESS)
53 | to := and(secondPacked, CLEAN_ADDRESS)
54 | contract_ := or(shr(64, and(firstPacked, CLEAN_PACKED8_BYTES_ADDRESS)), shr(160, secondPacked))
55 | }
56 | }
57 |
58 | /**
59 | * @notice Helper function that can unpack the from or to address from their respective packed slots in the registry
60 | * @param packedSlot The slot containing the from or to address
61 | * @return unpacked The `from` or `to` address
62 | * @dev Will not work if you want to obtain the contract address, use unpackAddresses
63 | */
64 | function unpackAddress(bytes32 packedSlot) internal pure returns (address unpacked) {
65 | assembly {
66 | unpacked := and(packedSlot, CLEAN_ADDRESS)
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/singlesig/Singlesig.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.4;
3 |
4 | /// @title 1-of-1 smart contract wallet with rotatable ownership for counterfactually receiving tokens at the same address across many EVM chains
5 | contract Singlesig {
6 | address public owner;
7 | address public pendingOwner;
8 |
9 | event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
10 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
11 |
12 | /// @dev Initializes the contract setting the address provided by the deployer as the initial owner
13 | /// @dev Distinct constructor args will lead to distinct CREATE2 initialization bytecode, so no collision risk here
14 | constructor(address initialOwner) {
15 | owner = initialOwner;
16 | emit OwnershipTransferred(address(0), initialOwner);
17 | }
18 |
19 | /// @dev Throws if called by any account other than the owner
20 | modifier onlyOwner() {
21 | require(owner == msg.sender, "Ownable2Step: caller is not the owner");
22 | _;
23 | }
24 |
25 | /// @notice Executes a call with provided parameters
26 | /// @dev This method doesn't perform any sanity check of the transaction
27 | /// @param to Destination address
28 | /// @param value Native token value in wei
29 | /// @param data Data payload
30 | /// @return success Boolean flag indicating if the call succeeded
31 | function execute(address to, uint256 value, bytes memory data) public onlyOwner returns (bool success) {
32 | (success,) = to.call{value: value}(data);
33 | }
34 |
35 | /// @dev Offers to transfer ownership permissions to a new account
36 | function transferOwnership(address newOwner) external onlyOwner {
37 | pendingOwner = newOwner;
38 | emit OwnershipTransferStarted(owner, newOwner);
39 | }
40 |
41 | /// @dev The new owner accepts the ownership transfer
42 | function acceptOwnership() external {
43 | require(pendingOwner == msg.sender, "Ownable2Step: caller is not the new owner");
44 | emit OwnershipTransferred(owner, pendingOwner);
45 | owner = pendingOwner;
46 | }
47 |
48 | /// @dev Function to receive native tokens when `msg.data` is empty
49 | receive() external payable {}
50 |
51 | /// @dev Fallback function is called when `msg.data` is not empty
52 | fallback() external payable {}
53 |
54 | function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
55 | return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
56 | || interfaceId == 0x150b7a02 // ERC165 Interface ID for ERC721TokenReceiver
57 | || interfaceId == 0x4e2312e0; // ERC165 Interface ID for ERC1155TokenReceiver
58 | }
59 |
60 | function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
61 | return 0x150b7a02; //bytes4(keccak256("onERC721Received(address,uint256,bytes)"));
62 | }
63 |
64 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) {
65 | return 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
66 | }
67 |
68 | function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external pure returns (bytes4) {
69 | return 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/DelegateRegistry.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {DelegateRegistry} from "src/DelegateRegistry.sol";
6 | import {IDelegateRegistry} from "src/IDelegateRegistry.sol";
7 |
8 | contract DelegateRegistryTest is Test {
9 | DelegateRegistry public reg;
10 | bytes32 public rights = "";
11 |
12 | function setUp() public {
13 | reg = new DelegateRegistry();
14 | }
15 |
16 | function getInitHash() public pure returns (bytes32) {
17 | bytes memory bytecode = type(DelegateRegistry).creationCode;
18 | return keccak256(abi.encodePacked(bytecode));
19 | }
20 |
21 | function testInitHash() public {
22 | bytes32 initHash = getInitHash();
23 | emit log_bytes32(initHash);
24 | }
25 |
26 | function testSupportsInterface(bytes32 seed) public {
27 | bytes4 falseInterfaceId = bytes4(seed & 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF);
28 | bytes4 interfaceId = type(IDelegateRegistry).interfaceId;
29 | if (falseInterfaceId == interfaceId) falseInterfaceId = bytes4(0);
30 | assertTrue(reg.supportsInterface(interfaceId));
31 | assertFalse(reg.supportsInterface(falseInterfaceId));
32 | }
33 |
34 | function testApproveAndRevokeForAll(address vault, address delegate, address contract_, uint256 tokenId) public {
35 | vm.assume(vault != address(0) && vault != address(1));
36 | // Approve
37 | vm.startPrank(vault);
38 | reg.delegateAll(delegate, rights, true);
39 | assertTrue(reg.checkDelegateForAll(delegate, vault, rights));
40 | assertTrue(reg.checkDelegateForContract(delegate, vault, contract_, rights));
41 | assertTrue(reg.checkDelegateForERC721(delegate, vault, contract_, tokenId, rights));
42 | assertEq(reg.checkDelegateForERC20(delegate, vault, contract_, rights), type(uint256).max);
43 | assertEq(reg.checkDelegateForERC1155(delegate, vault, contract_, tokenId, rights), type(uint256).max);
44 | // Revoke
45 | reg.delegateAll(delegate, rights, false);
46 | assertFalse(reg.checkDelegateForAll(delegate, vault, rights));
47 | }
48 |
49 | function testApproveAndRevokeForContract(address vault, address delegate, address contract_, uint256 tokenId) public {
50 | vm.assume(vault != address(0) && vault != address(1));
51 | // Approve
52 | vm.startPrank(vault);
53 | reg.delegateContract(delegate, contract_, rights, true);
54 | assertTrue(reg.checkDelegateForContract(delegate, vault, contract_, rights));
55 | assertTrue(reg.checkDelegateForERC721(delegate, vault, contract_, tokenId, rights));
56 | assertEq(reg.checkDelegateForERC20(delegate, vault, contract_, rights), type(uint256).max);
57 | assertEq(reg.checkDelegateForERC1155(delegate, vault, contract_, tokenId, rights), type(uint256).max);
58 | // Revoke
59 | reg.delegateContract(delegate, contract_, rights, false);
60 | assertFalse(reg.checkDelegateForContract(delegate, vault, contract_, rights));
61 | }
62 |
63 | function testApproveAndRevokeForToken(address vault, address delegate, address contract_, uint256 tokenId) public {
64 | vm.assume(vault != address(0) && vault != address(1));
65 | // Approve
66 | vm.startPrank(vault);
67 | reg.delegateERC721(delegate, contract_, tokenId, rights, true);
68 | assertTrue(reg.checkDelegateForERC721(delegate, vault, contract_, tokenId, rights));
69 | // Revoke
70 | reg.delegateERC721(delegate, contract_, tokenId, rights, false);
71 | assertFalse(reg.checkDelegateForERC721(delegate, vault, contract_, tokenId, rights));
72 | }
73 |
74 | function testApproveAndRevokeForBalance(address vault, address delegate, address contract_, uint256 amount) public {
75 | vm.assume(vault != address(0) && vault != address(1));
76 | // Approve
77 | emit log_bytes(abi.encodePacked(amount, rights, delegate, vault, contract_));
78 | vm.startPrank(vault);
79 | reg.delegateERC20(delegate, contract_, rights, amount);
80 | assertEq(reg.checkDelegateForERC20(delegate, vault, contract_, rights), amount);
81 | // Revoke
82 | reg.delegateERC20(delegate, contract_, rights, 0);
83 | assertEq(reg.checkDelegateForERC20(delegate, vault, contract_, rights), 0);
84 | }
85 |
86 | function testApproveAndRevokeForTokenBalance(address vault, address delegate, address contract_, uint256 tokenId, uint256 amount) public {
87 | vm.assume(vault != address(0) && vault != address(1));
88 | // Approve
89 | vm.startPrank(vault);
90 | reg.delegateERC1155(delegate, contract_, tokenId, rights, amount);
91 | assertEq(reg.checkDelegateForERC1155(delegate, vault, contract_, tokenId, rights), amount);
92 | // Revoke
93 | reg.delegateERC1155(delegate, contract_, tokenId, rights, 0);
94 | assertEq(reg.checkDelegateForERC1155(delegate, vault, contract_, tokenId, rights), 0);
95 | }
96 |
97 | function testMultipleDelegationForAll(address vault, address delegate0, address delegate1) public {
98 | vm.assume(delegate0 != delegate1 && vault != address(0) && vault != address(1));
99 | vm.startPrank(vault);
100 | reg.delegateAll(delegate0, rights, true);
101 | reg.delegateAll(delegate1, rights, true);
102 | // Read
103 | IDelegateRegistry.Delegation[] memory info = reg.getOutgoingDelegations(vault);
104 | assertEq(info.length, 2);
105 | assertEq(info[0].from, vault);
106 | assertEq(info[0].to, delegate0);
107 | assertEq(info[1].from, vault);
108 | assertEq(info[1].to, delegate1);
109 | // Revoke
110 | reg.delegateAll(delegate0, rights, false);
111 | info = reg.getOutgoingDelegations(vault);
112 | assertEq(info.length, 1);
113 | }
114 |
115 | function testBatchDelegationForAll(address vault, address delegate0, address delegate1) public {
116 | vm.assume(delegate0 != delegate1 && vault != address(0) && vault != address(1));
117 | vm.startPrank(vault);
118 | bytes[] memory batchData = new bytes[](2);
119 | batchData[0] = abi.encodeWithSelector(IDelegateRegistry.delegateAll.selector, delegate0, "", true);
120 | batchData[1] = abi.encodeWithSelector(IDelegateRegistry.delegateAll.selector, delegate1, "", true);
121 | reg.multicall(batchData);
122 |
123 | IDelegateRegistry.Delegation[] memory delegations = reg.getOutgoingDelegations(vault);
124 | assertEq(delegations.length, 2);
125 | assertEq(delegations[0].from, vault);
126 | assertEq(delegations[1].from, vault);
127 | assertEq(delegations[0].to, delegate0);
128 | assertEq(delegations[1].to, delegate1);
129 | assertTrue(delegations[0].type_ == IDelegateRegistry.DelegationType.ALL);
130 | assertTrue(delegations[1].type_ == IDelegateRegistry.DelegationType.ALL);
131 | }
132 |
133 | function testDelegateEnumeration(
134 | address vault0,
135 | address vault1,
136 | address delegate0,
137 | address delegate1,
138 | address contract0,
139 | address contract1,
140 | uint256 tokenId0,
141 | uint256 tokenId1,
142 | uint256 amount0,
143 | uint256 amount1
144 | ) public {
145 | vm.assume(vault0 != address(0) && vault1 != address(0) && vault0 != address(1) && vault1 != address(1));
146 | vm.assume(vault0 != vault1 && vault0 != delegate0 && vault0 != delegate1);
147 | vm.assume(vault1 != delegate0 && vault1 != delegate1);
148 | vm.assume(delegate0 != delegate1);
149 | vm.assume(contract0 != address(0) && contract1 != address(0) && contract0 != contract1);
150 | vm.assume(tokenId0 != 0 && tokenId1 != 0 && tokenId0 != tokenId1);
151 | vm.assume(amount0 != 0 && amount1 != 0 && amount0 != amount1);
152 |
153 | // vault0 delegates all five tiers to delegate0, and all five giv to delegate1
154 | vm.startPrank(vault0);
155 | reg.delegateAll(delegate0, rights, true);
156 | reg.delegateContract(delegate0, contract0, rights, true);
157 | reg.delegateERC721(delegate0, contract0, tokenId0, rights, true);
158 | reg.delegateERC20(delegate0, contract0, rights, amount0);
159 | reg.delegateERC1155(delegate0, contract0, tokenId0, rights, amount0);
160 | reg.delegateAll(delegate1, rights, true);
161 | reg.delegateContract(delegate1, contract1, rights, true);
162 | reg.delegateERC721(delegate1, contract1, tokenId1, rights, true);
163 | reg.delegateERC20(delegate1, contract1, rights, amount1);
164 | reg.delegateERC1155(delegate1, contract1, tokenId1, rights, amount1);
165 |
166 | // vault1 delegates all five tiers to delegate0
167 | changePrank(vault1);
168 | reg.delegateAll(delegate0, rights, true);
169 | reg.delegateContract(delegate0, contract0, rights, true);
170 | reg.delegateERC721(delegate0, contract0, tokenId0, rights, true);
171 | reg.delegateERC20(delegate0, contract0, rights, amount0);
172 | reg.delegateERC1155(delegate0, contract0, tokenId0, rights, amount0);
173 |
174 | // vault0 revokes all three tiers for delegate0, check incremental decrease in delegate enumerations
175 | changePrank(vault0);
176 | // check six in total, three from vault0 and three from vault1
177 | assertEq(reg.getIncomingDelegations(delegate0).length, 10);
178 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 10);
179 | reg.delegateAll(delegate0, rights, false);
180 | assertEq(reg.getIncomingDelegations(delegate0).length, 9);
181 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 9);
182 | reg.delegateContract(delegate0, contract0, rights, false);
183 | assertEq(reg.getIncomingDelegations(delegate0).length, 8);
184 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 8);
185 | reg.delegateERC721(delegate0, contract0, tokenId0, rights, false);
186 | assertEq(reg.getIncomingDelegations(delegate0).length, 7);
187 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 7);
188 | reg.delegateERC20(delegate0, contract0, rights, 0);
189 | assertEq(reg.getIncomingDelegations(delegate0).length, 6);
190 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 6);
191 | reg.delegateERC1155(delegate0, contract0, tokenId0, rights, 0);
192 | assertEq(reg.getIncomingDelegations(delegate0).length, 5);
193 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 5);
194 |
195 | // vault0 re-delegates to delegate0
196 | changePrank(vault0);
197 | reg.delegateAll(delegate0, rights, true);
198 | reg.delegateContract(delegate0, contract0, rights, true);
199 | reg.delegateERC721(delegate0, contract0, tokenId0, rights, true);
200 | reg.delegateERC20(delegate0, contract0, rights, amount0);
201 | reg.delegateERC1155(delegate0, contract0, tokenId0, rights, amount0);
202 | assertEq(reg.getIncomingDelegations(delegate0).length, 10);
203 | assertEq(reg.getIncomingDelegations(delegate1).length, 5);
204 | assertEq(reg.getIncomingDelegationHashes(delegate0).length, 10);
205 | assertEq(reg.getIncomingDelegationHashes(delegate1).length, 5);
206 | }
207 |
208 | function testVaultEnumerations(address vault, address delegate0, address delegate1, address contract0, address contract1, uint256 tokenId, uint256 amount) public {
209 | vm.assume(vault != address(0) && vault != address(1));
210 | vm.assume(vault != delegate0 && vault != delegate1);
211 | vm.assume(delegate0 != delegate1);
212 | vm.assume(contract0 != contract1);
213 | vm.assume(amount != 0);
214 | vm.startPrank(vault);
215 | reg.delegateAll(delegate0, rights, true);
216 | reg.delegateContract(delegate0, contract0, rights, true);
217 | reg.delegateERC721(delegate0, contract1, tokenId, rights, true);
218 | reg.delegateERC20(delegate0, contract1, rights, amount);
219 | reg.delegateAll(delegate1, rights, true);
220 | reg.delegateContract(delegate1, contract0, rights, true);
221 |
222 | // Read
223 | IDelegateRegistry.Delegation[] memory vaultDelegations;
224 | vaultDelegations = reg.getOutgoingDelegations(vault);
225 | assertEq(vaultDelegations.length, 6);
226 | assertTrue(vaultDelegations[1].type_ == IDelegateRegistry.DelegationType.CONTRACT);
227 | }
228 |
229 | function _createUniqueDelegations(uint256 start, uint256 stop) internal {
230 | for (uint256 i = start; i < stop; i++) {
231 | address delegate = address(bytes20(keccak256(abi.encode("delegate", i))));
232 | address contract_ = address(bytes20(keccak256(abi.encode("contract", i))));
233 | uint256 amount = uint256(keccak256(abi.encode("amount", i)));
234 | uint256 tokenId = uint256(keccak256(abi.encode("tokenId", i)));
235 | reg.delegateAll(delegate, rights, true);
236 | reg.delegateContract(delegate, contract_, rights, true);
237 | reg.delegateERC20(delegate, contract_, rights, amount);
238 | reg.delegateERC721(delegate, contract_, tokenId, rights, true);
239 | reg.delegateERC1155(delegate, contract_, tokenId, rights, amount);
240 | }
241 | }
242 |
243 | // function testGetDelegationsGas() public {
244 | // uint256 delegationsLimit = 2600; // Actual limit is x5
245 | // _createUniqueDelegations(0, delegationsLimit);
246 | // IDelegateRegistry.Delegation[] memory vaultDelegations = reg.getOutgoingDelegations(address(this));
247 | // assertEq(vaultDelegations.length, 5 * delegationsLimit);
248 | // }
249 |
250 | // function testGetDelegationHashesGas() public {
251 | // uint256 hashesLimit = 20800; // Actual limit is x5
252 | // _createUniqueDelegations(0, hashesLimit);
253 | // bytes32[] memory vaultDelegationHashes = reg.getOutgoingDelegationHashes(address(this));
254 | // assertEq(vaultDelegationHashes.length, 5 * hashesLimit);
255 | // }
256 |
257 | function testSweep(address to, address contract_, uint256 tokenId, uint256 amount, bytes32 rights_, bool enable) public {
258 | address sc = address(0x000000dE1E80ea5a234FB5488fee2584251BC7e8);
259 | uint256 regBalanceBefore = address(reg).balance;
260 | uint256 scBalanceBefore = address(sc).balance;
261 | bytes[] memory data = new bytes[](1);
262 | data[0] = abi.encodeWithSelector(reg.delegateAll.selector, to, rights_, enable);
263 | reg.multicall{value: 0.2 ether}(data);
264 | assertEq(regBalanceBefore + 0.2 ether, address(reg).balance);
265 | reg.delegateAll{value: 0.2 ether}(to, rights_, enable);
266 | assertEq(regBalanceBefore + 0.4 ether, address(reg).balance);
267 | reg.delegateContract{value: 0.2 ether}(to, contract_, rights_, enable);
268 | assertEq(regBalanceBefore + 0.6 ether, address(reg).balance);
269 | reg.delegateERC721{value: 0.2 ether}(to, contract_, tokenId, rights_, enable);
270 | assertEq(regBalanceBefore + 0.8 ether, address(reg).balance);
271 | reg.delegateERC20{value: 0.1 ether}(to, contract_, rights_, amount);
272 | assertEq(regBalanceBefore + 0.9 ether, address(reg).balance);
273 | reg.delegateERC1155{value: 0.1 ether}(to, contract_, tokenId, rights_, amount);
274 | assertEq(regBalanceBefore + 1 ether, address(reg).balance);
275 | reg.sweep();
276 | assertEq(address(reg).balance, 0);
277 | assertEq(scBalanceBefore + 1 ether, sc.balance);
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/test/GasBenchmark.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {DelegateRegistry as Registry} from "src/DelegateRegistry.sol";
6 | import {IDelegateRegistry as IRegistry} from "src/IDelegateRegistry.sol";
7 |
8 | /// @dev for testing gas of write and consumable functions
9 | /// @dev "forge test --match-test testGas --gas-report"
10 | contract GasBenchmark is Test {
11 | Registry registry;
12 |
13 | function setUp() public {}
14 |
15 | function _createDelegations(bytes32 seed) private pure returns (IRegistry.Delegation[] memory delegations) {
16 | delegations = new IRegistry.Delegation[](5);
17 | delegations[0] = IRegistry.Delegation({
18 | type_: IRegistry.DelegationType.ALL,
19 | to: address(bytes20(keccak256(abi.encode(seed, "ALL", "delegate")))),
20 | from: address(0),
21 | rights: "",
22 | contract_: address(0),
23 | tokenId: 0,
24 | amount: 0
25 | });
26 | delegations[1] = IRegistry.Delegation({
27 | type_: IRegistry.DelegationType.CONTRACT,
28 | to: address(bytes20(keccak256(abi.encode(seed, "CONTRACT", "delegate")))),
29 | from: address(0),
30 | rights: "",
31 | contract_: address(bytes20(keccak256(abi.encode(seed, "CONTRACT", "contract_")))),
32 | tokenId: 0,
33 | amount: 0
34 | });
35 | delegations[2] = IRegistry.Delegation({
36 | type_: IRegistry.DelegationType.ERC721,
37 | to: address(bytes20(keccak256(abi.encode(seed, "ERC721", "delegate")))),
38 | from: address(0),
39 | rights: "",
40 | contract_: address(bytes20(keccak256(abi.encode(seed, "ERC721", "contract_")))),
41 | tokenId: uint256(keccak256(abi.encode(seed, "ERC721", "tokenId"))),
42 | amount: 0
43 | });
44 | delegations[3] = IRegistry.Delegation({
45 | type_: IRegistry.DelegationType.ERC20,
46 | to: address(bytes20(keccak256(abi.encode(seed, "ERC20", "delegate")))),
47 | from: address(0),
48 | rights: "",
49 | contract_: address(bytes20(keccak256(abi.encode(seed, "ERC20", "contract_")))),
50 | tokenId: 0,
51 | amount: uint256(keccak256(abi.encode(seed, "ERC20", "amount")))
52 | });
53 | delegations[4] = IRegistry.Delegation({
54 | type_: IRegistry.DelegationType.ERC1155,
55 | to: address(bytes20(keccak256(abi.encode(seed, "ERC1155", "delegate")))),
56 | from: address(0),
57 | rights: "",
58 | contract_: address(bytes20(keccak256(abi.encode(seed, "ERC1155", "contract_")))),
59 | tokenId: uint256(keccak256(abi.encode(seed, "ERC1155", "tokenId"))),
60 | amount: uint256(keccak256(abi.encode(seed, "ERC1155", "amount")))
61 | });
62 | }
63 |
64 | function testGas(address vault, bytes32 seed) public {
65 | vm.assume(vault > address(1));
66 | // Benchmark delegate all and check all
67 | registry = new Registry();
68 | IRegistry.Delegation[] memory delegations = _createDelegations(keccak256(abi.encode(seed, "delegations")));
69 | registry.delegateAll(delegations[0].to, delegations[0].rights, true);
70 | registry.checkDelegateForAll(delegations[0].to, vault, delegations[0].rights);
71 | registry.checkDelegateForAll(delegations[0].to, vault, "fakeRights");
72 | // Benchmark delegate contract and check contract
73 | registry = new Registry();
74 | registry.delegateContract(delegations[1].to, delegations[1].contract_, delegations[1].rights, true);
75 | registry.checkDelegateForContract(delegations[1].to, vault, delegations[1].contract_, delegations[1].rights);
76 | registry.checkDelegateForContract(delegations[1].to, vault, delegations[1].contract_, "fakeRights");
77 | // Benchmark delegate erc721 and check erc721
78 | registry = new Registry();
79 | registry.delegateERC721(delegations[2].to, delegations[2].contract_, delegations[2].tokenId, delegations[2].rights, true);
80 | registry.checkDelegateForERC721(delegations[2].to, vault, delegations[2].contract_, delegations[2].tokenId, delegations[2].rights);
81 | registry.checkDelegateForERC721(delegations[2].to, vault, delegations[2].contract_, delegations[2].tokenId, "fakeRights");
82 | // Benchmark delegate erc20 and check erc20
83 | registry = new Registry();
84 | registry.delegateERC20(delegations[3].to, delegations[3].contract_, delegations[3].rights, delegations[3].amount);
85 | registry.checkDelegateForERC20(delegations[3].to, vault, delegations[3].contract_, delegations[3].rights);
86 | registry.checkDelegateForERC20(delegations[3].to, vault, delegations[3].contract_, "fakeRights");
87 | // Benchmark delegate erc1155 and check erc1155
88 | registry = new Registry();
89 | registry.delegateERC1155(delegations[4].to, delegations[4].contract_, delegations[4].tokenId, delegations[4].rights, delegations[4].amount);
90 | registry.checkDelegateForERC1155(delegations[4].to, vault, delegations[4].contract_, delegations[4].tokenId, delegations[4].rights);
91 | registry.checkDelegateForERC1155(delegations[4].to, vault, delegations[4].contract_, delegations[4].tokenId, "fakeRights");
92 | // Benchmark multicall
93 | registry = new Registry();
94 | IRegistry.Delegation[] memory multicallDelegations = new IRegistry.Delegation[](5);
95 | multicallDelegations = _createDelegations(keccak256(abi.encode(seed, "multicall")));
96 | bytes[] memory data = new bytes[](5);
97 | data[0] = abi.encodeWithSelector(IRegistry.delegateAll.selector, multicallDelegations[0].to, multicallDelegations[0].rights, true);
98 | data[1] = abi.encodeWithSelector(IRegistry.delegateContract.selector, multicallDelegations[1].to, multicallDelegations[1].contract_, multicallDelegations[1].rights, true);
99 | data[2] = abi.encodeWithSelector(
100 | IRegistry.delegateERC721.selector, multicallDelegations[2].to, multicallDelegations[2].contract_, multicallDelegations[2].tokenId, multicallDelegations[2].rights, true
101 | );
102 | data[3] = abi.encodeWithSelector(
103 | IRegistry.delegateERC20.selector, multicallDelegations[3].to, multicallDelegations[3].contract_, multicallDelegations[3].amount, multicallDelegations[3].rights, true
104 | );
105 | data[4] = abi.encodeWithSelector(
106 | IRegistry.delegateERC1155.selector,
107 | multicallDelegations[4].to,
108 | multicallDelegations[4].contract_,
109 | multicallDelegations[4].tokenId,
110 | multicallDelegations[4].amount,
111 | multicallDelegations[4].rights,
112 | true
113 | );
114 | registry.multicall(data);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/test/HashBenchmark.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {IDelegateRegistry} from "src/IDelegateRegistry.sol";
6 | import {RegistryHashes} from "src/libraries/RegistryHashes.sol";
7 |
8 | /// @dev Harness that exposes registry hashes library as contract
9 | contract HashHarness {
10 | function decodeType(bytes32 hash) external pure returns (IDelegateRegistry.DelegationType) {
11 | return RegistryHashes.decodeType(hash);
12 | }
13 |
14 | function location(bytes32 hash) external pure returns (bytes32) {
15 | return RegistryHashes.location(hash);
16 | }
17 |
18 | function allHash(address from, bytes32 rights, address to) external pure returns (bytes32) {
19 | return RegistryHashes.allHash(from, rights, to);
20 | }
21 |
22 | function contractHash(address from, bytes32 rights, address to, address contract_) external pure returns (bytes32) {
23 | return RegistryHashes.contractHash(from, rights, to, contract_);
24 | }
25 |
26 | function erc721Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) external pure returns (bytes32) {
27 | return RegistryHashes.erc721Hash(from, rights, to, tokenId, contract_);
28 | }
29 |
30 | function erc20Hash(address from, bytes32 rights, address to, address contract_) external pure returns (bytes32) {
31 | return RegistryHashes.erc20Hash(from, rights, to, contract_);
32 | }
33 |
34 | function erc1155Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) external pure returns (bytes32) {
35 | return RegistryHashes.erc1155Hash(from, rights, to, tokenId, contract_);
36 | }
37 |
38 | function allLocation(address from, bytes32 rights, address to) external pure returns (bytes32) {
39 | return RegistryHashes.allLocation(from, rights, to);
40 | }
41 |
42 | function contractLocation(address from, bytes32 rights, address to, address contract_) external pure returns (bytes32) {
43 | return RegistryHashes.contractLocation(from, rights, to, contract_);
44 | }
45 |
46 | function erc721Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) external pure returns (bytes32) {
47 | return RegistryHashes.erc721Location(from, rights, to, tokenId, contract_);
48 | }
49 |
50 | function erc20Location(address from, bytes32 rights, address to, address contract_) external pure returns (bytes32) {
51 | return RegistryHashes.erc20Location(from, rights, to, contract_);
52 | }
53 |
54 | function erc1155Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) external pure returns (bytes32) {
55 | return RegistryHashes.erc1155Location(from, rights, to, tokenId, contract_);
56 | }
57 | }
58 |
59 | contract HashBenchmark is Test {
60 | HashHarness hashHarness = new HashHarness();
61 |
62 | function testHashGas(address from, bytes32 rights, address to, uint256 tokenId, address contract_, bytes32 hash) public view {
63 | hashHarness.allHash(from, rights, to);
64 | hashHarness.allLocation(from, rights, to);
65 | hashHarness.contractHash(from, rights, to, contract_);
66 | hashHarness.contractLocation(from, rights, to, contract_);
67 | hashHarness.erc721Hash(from, rights, to, tokenId, contract_);
68 | hashHarness.erc721Location(from, rights, to, tokenId, contract_);
69 | hashHarness.erc20Hash(from, rights, to, contract_);
70 | hashHarness.erc20Location(from, rights, to, contract_);
71 | hashHarness.erc1155Hash(from, rights, to, tokenId, contract_);
72 | hashHarness.erc1155Location(from, rights, to, tokenId, contract_);
73 | hashHarness.location(hash);
74 | hashHarness.decodeType(0);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/test/InitCodeHash.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {console2} from "forge-std/console2.sol";
6 | import {DelegateRegistry} from "src/DelegateRegistry.sol";
7 | import {Singlesig} from "src/singlesig/Singlesig.sol";
8 |
9 | // Singlesig inithash: 0xf911e320d18e7274491e7ab207bfff830e2926248f86c6a987668e8e72e1ed77
10 | // salt 0x000000000000000000000000000000000000000016c7768a8c7a2824b846321d => 0x000000dE1E80ea5a234FB5488fee2584251BC7e8
11 |
12 | // Registry inithash: 0x78bdba7d5e0c91d9aedc93c97bf84433daaf008a83bfe921c2d27ab77301d6d9
13 | // salt 0x0000000000000000000000000000000000000000fbe49ecfc3decb1164228b89 => 0x0000000000006DE22EeA995bE2f0511186b8e013 => 16777216
14 |
15 | contract InitCodeHashTest is Test {
16 | DelegateRegistry reg;
17 | Singlesig sig;
18 |
19 | function setUp() public {
20 | reg = new DelegateRegistry();
21 | sig = new Singlesig(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215);
22 | }
23 |
24 | function getInitHash() public pure returns (bytes32) {
25 | bytes memory initCode = type(DelegateRegistry).creationCode;
26 | // bytes memory initCode = abi.encodePacked(type(Singlesig).creationCode, abi.encode(address(0x6Ed7D526b020780f694f3c10Dfb25E1b134D3215)));
27 | // console2.logBytes(initCode);
28 |
29 | return keccak256(abi.encodePacked(initCode));
30 | }
31 |
32 | function testInitHash() public {
33 | bytes32 initHash = getInitHash();
34 | emit log_bytes32(initHash);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/RegistryHashTests.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {IDelegateRegistry as IRegistry} from "src/IDelegateRegistry.sol";
6 | import {RegistryHashes as Hashes} from "src/libraries/RegistryHashes.sol";
7 |
8 | contract RegistryHashTests is Test {
9 | /// @dev used to cross check internal constant in registry hashes with intended values
10 | function testRegistryHashConstant() public {
11 | assertEq(Hashes.EXTRACT_LAST_BYTE, type(uint8).max);
12 | assertEq(Hashes.ALL_TYPE, uint256(IRegistry.DelegationType.ALL));
13 | assertEq(Hashes.CONTRACT_TYPE, uint256(IRegistry.DelegationType.CONTRACT));
14 | assertEq(Hashes.ERC721_TYPE, uint256(IRegistry.DelegationType.ERC721));
15 | assertEq(Hashes.ERC20_TYPE, uint256(IRegistry.DelegationType.ERC20));
16 | assertEq(Hashes.ERC1155_TYPE, uint256(IRegistry.DelegationType.ERC1155));
17 | assertEq(Hashes.DELEGATION_SLOT, 0);
18 | }
19 |
20 | /// @dev used to generate random delegation type within enum range
21 | function _selectRandomType(uint256 seed) internal pure returns (IRegistry.DelegationType) {
22 | if (seed % 6 == 0) return IRegistry.DelegationType.NONE;
23 | if (seed % 6 == 1) return IRegistry.DelegationType.ALL;
24 | if (seed % 6 == 2) return IRegistry.DelegationType.CONTRACT;
25 | if (seed % 6 == 3) return IRegistry.DelegationType.ERC721;
26 | if (seed % 6 == 4) return IRegistry.DelegationType.ERC20;
27 | else return IRegistry.DelegationType.ERC1155;
28 | }
29 |
30 | /// @dev tests methods against previously used solidity methods
31 | function testRegistryHashes(bytes32 _input, uint256 seed, address from, bytes32 rights, address to, address contract_, uint256 tokenId) public {
32 | IRegistry.DelegationType _type = _selectRandomType(seed);
33 | bytes32 decodeTest = _encodeLastByteWithType(_input, _type);
34 | assertEq(uint256(Hashes.decodeType(decodeTest)), uint256(_decodeLastByteToType(decodeTest)));
35 | assertEq(Hashes.location(_input), _computeLocation(_input));
36 | assertEq(Hashes.allHash(from, rights, to), _computeAll(from, rights, to));
37 | assertEq(Hashes.allLocation(from, rights, to), _computeLocation(_computeAll(from, rights, to)));
38 | assertEq(Hashes.contractHash(from, rights, to, contract_), _computeContract(from, rights, to, contract_));
39 | assertEq(Hashes.contractLocation(from, rights, to, contract_), _computeLocation(_computeContract(from, rights, to, contract_)));
40 | assertEq(Hashes.erc721Hash(from, rights, to, tokenId, contract_), _computeERC721(from, rights, to, tokenId, contract_));
41 | assertEq(Hashes.erc721Location(from, rights, to, tokenId, contract_), _computeLocation(_computeERC721(from, rights, to, tokenId, contract_)));
42 | assertEq(Hashes.erc20Hash(from, rights, to, contract_), _computeERC20(from, rights, to, contract_));
43 | assertEq(Hashes.erc20Location(from, rights, to, contract_), _computeLocation(_computeERC20(from, rights, to, contract_)));
44 | assertEq(Hashes.erc1155Hash(from, rights, to, tokenId, contract_), _computeERC1155(from, rights, to, tokenId, contract_));
45 | assertEq(Hashes.erc1155Location(from, rights, to, tokenId, contract_), _computeLocation(_computeERC1155(from, rights, to, tokenId, contract_)));
46 | }
47 |
48 | /// @dev used to cross check that location by type method gives the same result as location(hash for type) method
49 | function testRegistryHashesLocationEquivalence(address from, bytes32 rights, address to, uint256 tokenId, address contract_) public {
50 | assertEq(Hashes.allLocation(from, rights, to), Hashes.location(Hashes.allHash(from, rights, to)));
51 | assertEq(Hashes.contractLocation(from, rights, to, contract_), Hashes.location(Hashes.contractHash(from, rights, to, contract_)));
52 | assertEq(Hashes.erc721Location(from, rights, to, tokenId, contract_), Hashes.location(Hashes.erc721Hash(from, rights, to, tokenId, contract_)));
53 | assertEq(Hashes.erc20Location(from, rights, to, contract_), Hashes.location(Hashes.erc20Hash(from, rights, to, contract_)));
54 | assertEq(Hashes.erc1155Location(from, rights, to, tokenId, contract_), Hashes.location(Hashes.erc1155Hash(from, rights, to, tokenId, contract_)));
55 | }
56 |
57 | /// @dev tests for storage collisions between hashes, only holding from != notFrom as a constant
58 | function testRegistryHashesForStorageCollisions(
59 | address from,
60 | bytes32 rights,
61 | address to,
62 | uint256 tokenId,
63 | address contract_,
64 | address notFrom,
65 | bytes32 searchRights,
66 | address searchTo,
67 | uint256 searchTokenId,
68 | address searchContract_
69 | ) public {
70 | vm.assume(from != notFrom);
71 | bytes32[] memory uniqueHashes = new bytes32[](10);
72 | uniqueHashes[0] = Hashes.allLocation(from, rights, to);
73 | uniqueHashes[1] = Hashes.allLocation(notFrom, searchRights, searchTo);
74 | uniqueHashes[2] = Hashes.contractLocation(from, rights, to, contract_);
75 | uniqueHashes[3] = Hashes.contractLocation(notFrom, searchRights, searchTo, searchContract_);
76 | uniqueHashes[4] = Hashes.erc721Location(from, rights, to, tokenId, contract_);
77 | uniqueHashes[5] = Hashes.erc721Location(notFrom, searchRights, searchTo, searchTokenId, searchContract_);
78 | uniqueHashes[6] = Hashes.erc20Location(from, rights, to, contract_);
79 | uniqueHashes[7] = Hashes.erc20Location(notFrom, searchRights, searchTo, searchContract_);
80 | uniqueHashes[8] = Hashes.erc1155Location(from, rights, to, tokenId, contract_);
81 | uniqueHashes[9] = Hashes.erc1155Location(notFrom, searchRights, searchTo, searchTokenId, searchContract_);
82 | for (uint256 i = 0; i < uniqueHashes.length; i++) {
83 | for (uint256 j = 0; j < uniqueHashes.length; j++) {
84 | if (j != i) assertTrue(uniqueHashes[i] != uniqueHashes[j]);
85 | else assertEq(uniqueHashes[i], uniqueHashes[j]);
86 | }
87 | }
88 | }
89 |
90 | /// @dev tests for collisions between the types, additionally searches for collisions by holding from != notFrom constant and fuzzes variations of the other
91 | /// parameters
92 | function testRegistryHashesForTypeCollisions(
93 | address from,
94 | bytes32 rights,
95 | address to,
96 | uint256 tokenId,
97 | address contract_,
98 | address notFrom,
99 | bytes32 searchRights,
100 | address searchTo,
101 | uint256 searchTokenId,
102 | address searchContract_
103 | ) public {
104 | vm.assume(from != notFrom);
105 | bytes32[] memory uniqueHashes = new bytes32[](10);
106 | uniqueHashes[0] = Hashes.allHash(from, rights, to);
107 | uniqueHashes[1] = Hashes.allHash(notFrom, searchRights, searchTo);
108 | uniqueHashes[2] = Hashes.contractHash(from, rights, to, contract_);
109 | uniqueHashes[3] = Hashes.contractHash(notFrom, searchRights, searchTo, searchContract_);
110 | uniqueHashes[4] = Hashes.erc721Hash(from, rights, to, tokenId, contract_);
111 | uniqueHashes[5] = Hashes.erc721Hash(notFrom, searchRights, searchTo, searchTokenId, searchContract_);
112 | uniqueHashes[6] = Hashes.erc20Hash(from, rights, to, contract_);
113 | uniqueHashes[7] = Hashes.erc20Hash(notFrom, searchRights, searchTo, searchContract_);
114 | uniqueHashes[8] = Hashes.erc1155Hash(from, rights, to, tokenId, contract_);
115 | uniqueHashes[9] = Hashes.erc1155Hash(notFrom, searchRights, searchTo, searchTokenId, searchContract_);
116 | for (uint256 i = 0; i < uniqueHashes.length; i++) {
117 | for (uint256 j = 0; j < uniqueHashes.length; j++) {
118 | if (j != i) assertTrue(uniqueHashes[i] != uniqueHashes[j]);
119 | else assertEq(uniqueHashes[i], uniqueHashes[j]);
120 | }
121 | }
122 | }
123 |
124 | /// @dev Test for registry hash functions that could be impacted by incorrect inputs
125 | function testRegistryHashesLargeInputs(uint256 from, bytes32 rights, uint256 to, uint256 tokenId, uint256 contract_) public {
126 | uint256 minSize = type(uint160).max;
127 | vm.assume(from > minSize && to > minSize && contract_ > minSize);
128 | // Create address types from large inputs
129 | address largeFrom;
130 | address cleanedFrom;
131 | address largeTo;
132 | address cleanedTo;
133 | address largeContract;
134 | address cleanedContract;
135 | assembly {
136 | largeFrom := from
137 | largeTo := to
138 | largeContract := contract_
139 | cleanedFrom := shr(96, shl(96, from))
140 | cleanedTo := shr(96, shl(96, to))
141 | cleanedContract := shr(96, shl(96, contract_))
142 | }
143 | // Assert that hashes and locations of cleaned & not cleaned still give the same output
144 | assertEq(Hashes.allHash(largeFrom, rights, largeTo), Hashes.allHash(cleanedFrom, rights, cleanedTo));
145 | assertEq(Hashes.allLocation(largeFrom, rights, largeTo), Hashes.allLocation(cleanedFrom, rights, cleanedTo));
146 | assertEq(Hashes.contractHash(largeFrom, rights, largeTo, largeContract), Hashes.contractHash(cleanedFrom, rights, cleanedTo, cleanedContract));
147 | assertEq(Hashes.contractLocation(largeFrom, rights, largeTo, largeContract), Hashes.contractLocation(cleanedFrom, rights, cleanedTo, cleanedContract));
148 | assertEq(Hashes.erc721Hash(largeFrom, rights, largeTo, tokenId, largeContract), Hashes.erc721Hash(cleanedFrom, rights, cleanedTo, tokenId, cleanedContract));
149 | assertEq(Hashes.erc721Location(largeFrom, rights, largeTo, tokenId, largeContract), Hashes.erc721Location(cleanedFrom, rights, cleanedTo, tokenId, cleanedContract));
150 | assertEq(Hashes.erc20Hash(largeFrom, rights, largeTo, largeContract), Hashes.erc20Hash(cleanedFrom, rights, cleanedTo, cleanedContract));
151 | assertEq(Hashes.erc20Location(largeFrom, rights, largeTo, largeContract), Hashes.erc20Location(cleanedFrom, rights, cleanedTo, cleanedContract));
152 | assertEq(Hashes.erc1155Hash(largeFrom, rights, largeTo, tokenId, largeContract), Hashes.erc1155Hash(cleanedFrom, rights, cleanedTo, tokenId, cleanedContract));
153 | assertEq(Hashes.erc1155Location(largeFrom, rights, largeTo, tokenId, largeContract), Hashes.erc1155Location(cleanedFrom, rights, cleanedTo, tokenId, cleanedContract));
154 | }
155 |
156 | /// @dev internal functions of the original registry hash specification to test optimized methods work as intended
157 | function _computeAll(address from, bytes32 rights, address to) internal pure returns (bytes32) {
158 | return _encodeLastByteWithType(keccak256(abi.encodePacked(rights, from, to)), IRegistry.DelegationType.ALL);
159 | }
160 |
161 | function _computeContract(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32) {
162 | return _encodeLastByteWithType(keccak256(abi.encodePacked(rights, from, to, contract_)), IRegistry.DelegationType.CONTRACT);
163 | }
164 |
165 | function _computeERC721(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32) {
166 | return _encodeLastByteWithType(keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)), IRegistry.DelegationType.ERC721);
167 | }
168 |
169 | function _computeERC20(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32) {
170 | return _encodeLastByteWithType(keccak256(abi.encodePacked(rights, from, to, contract_)), IRegistry.DelegationType.ERC20);
171 | }
172 |
173 | function _computeERC1155(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32) {
174 | return _encodeLastByteWithType(keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)), IRegistry.DelegationType.ERC1155);
175 | }
176 |
177 | function _encodeLastByteWithType(bytes32 _input, IRegistry.DelegationType _type) internal pure returns (bytes32) {
178 | return bytes32((uint256(_input) << 8) | uint256(_type));
179 | }
180 |
181 | function _decodeLastByteToType(bytes32 _input) internal pure returns (IRegistry.DelegationType) {
182 | return IRegistry.DelegationType(uint8(uint256(_input) & 0xFF));
183 | }
184 |
185 | function _computeLocation(bytes32 hash) internal pure returns (bytes32 location) {
186 | location = keccak256(abi.encode(hash, 0)); // delegations mapping is at slot 0
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/test/RegistryOpsTests.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {RegistryOps as Ops} from "src/libraries/RegistryOps.sol";
6 |
7 | contract RegistryOpsTests is Test {
8 | function _brutalizeBool(bool x) internal view returns (bool result) {
9 | assembly {
10 | mstore(0x00, gas())
11 | result := mul(iszero(iszero(x)), shl(128, keccak256(0x00, 0x20)))
12 | }
13 | }
14 |
15 | function _brutalizeUint32(uint32 x) internal view returns (uint32 result) {
16 | assembly {
17 | mstore(0x00, gas())
18 | result := or(x, shl(32, keccak256(0x00, 0x20)))
19 | }
20 | }
21 |
22 | function testMaxDifferential(uint256 x, uint256 y) public {
23 | assertEq(Ops.max(x, y), x > y ? x : y);
24 | }
25 |
26 | function testMaxDifferential(uint32 x, uint32 y) public {
27 | assertEq(Ops.max(_brutalizeUint32(x), _brutalizeUint32(y)), x > y ? x : y);
28 | }
29 |
30 | function testAndDifferential(bool x, bool y) public {
31 | assertEq(Ops.and(_brutalizeBool(x), _brutalizeBool(y)), x && y);
32 | }
33 |
34 | function testOrDifferential(bool x, bool y) public {
35 | assertEq(Ops.or(_brutalizeBool(x), _brutalizeBool(y)), x || y);
36 | }
37 |
38 | function testTruthyness(uint256 x, uint256 y) public {
39 | bool xCasted;
40 | bool yCasted;
41 | assembly {
42 | xCasted := x
43 | yCasted := y
44 | }
45 | assertEq(xCasted, x != 0);
46 | assertTrue(xCasted == (x != 0));
47 | assertEq(Ops.or(xCasted, yCasted), x != 0 || y != 0);
48 | assertTrue(Ops.or(xCasted, yCasted) == (x != 0 || y != 0));
49 | if (Ops.or(xCasted, yCasted)) if (!(x != 0 || y != 0)) revert();
50 | if (x != 0 || y != 0) if (!Ops.or(xCasted, yCasted)) revert();
51 | assertEq(Ops.and(xCasted, yCasted), x != 0 && y != 0);
52 | assertTrue(Ops.and(xCasted, yCasted) == (x != 0 && y != 0));
53 | if (Ops.and(xCasted, yCasted)) if (!(x != 0 && y != 0)) revert();
54 | if (x != 0 && y != 0) if (!Ops.and(xCasted, yCasted)) revert();
55 | }
56 |
57 | function testTruthyness(bool x, bool y) public {
58 | bool xCasted;
59 | bool yCasted;
60 | assembly {
61 | mstore(0x00, gas())
62 | xCasted := mul(iszero(iszero(x)), shl(128, keccak256(0x00, 0x20)))
63 | mstore(0x00, gas())
64 | yCasted := mul(iszero(iszero(y)), shl(128, keccak256(0x00, 0x20)))
65 | }
66 | assertEq(x, xCasted);
67 | assertEq(y, yCasted);
68 | assembly {
69 | if and(0xff, xCasted) { revert(0x00, 0x00) }
70 | if and(0xff, yCasted) { revert(0x00, 0x00) }
71 | }
72 | assertEq(Ops.or(xCasted, yCasted), x || y);
73 | assertTrue(Ops.or(xCasted, yCasted) == (x || y));
74 | if (Ops.or(xCasted, yCasted)) if (!(x || y)) revert();
75 | if (x || y) if (!Ops.or(xCasted, yCasted)) revert();
76 | assertEq(Ops.and(xCasted, yCasted), x && y);
77 | assertTrue(Ops.and(xCasted, yCasted) == (x && y));
78 | if (Ops.and(xCasted, yCasted)) if (!(x && y)) revert();
79 | if (x && y) if (!Ops.and(xCasted, yCasted)) revert();
80 | }
81 |
82 | function testTruthyness(bool x) public {
83 | bool casted;
84 | if (casted) revert();
85 | assertEq(casted, false);
86 | assertTrue(casted == false);
87 | assembly {
88 | if x {
89 | mstore(0x00, gas())
90 | casted := mul(iszero(iszero(x)), shl(128, keccak256(0x00, 0x20)))
91 | }
92 | }
93 | assertEq(x, casted);
94 | assertTrue(x == casted);
95 | if (x) if (!casted) revert();
96 | if (casted) if (!x) revert();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/test/RegistryStorageTests.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {RegistryStorage as Storage} from "src/libraries/RegistryStorage.sol";
6 | import {RegistryHarness as Harness} from "./tools/RegistryHarness.sol";
7 |
8 | contract RegistryStorageTests is Test {
9 | Harness harness;
10 |
11 | function setUp() public {
12 | harness = new Harness();
13 | }
14 |
15 | /// @dev Check that storage positions match up with the expect form of the delegations array
16 | function testStoragePositions() public {
17 | assertEq(Storage.POSITIONS_FIRST_PACKED, 0);
18 | assertEq(Storage.POSITIONS_SECOND_PACKED, 1);
19 | assertEq(Storage.POSITIONS_RIGHTS, 2);
20 | assertEq(Storage.POSITIONS_TOKEN_ID, 3);
21 | assertEq(Storage.POSITIONS_AMOUNT, 4);
22 | }
23 |
24 | /// @dev Check that storage library constants are as intended
25 | function testStorageConstants() public {
26 | assertEq(Storage.CLEAN_ADDRESS, uint256(type(uint160).max));
27 | assertEq(Storage.CLEAN_FIRST8_BYTES_ADDRESS, uint256(type(uint64).max) << 96);
28 | assertEq(Storage.CLEAN_PACKED8_BYTES_ADDRESS, uint256(type(uint64).max) << 160);
29 | assertEq(Storage.DELEGATION_EMPTY, address(0));
30 | assertEq(Storage.DELEGATION_REVOKED, address(1));
31 | }
32 |
33 | /// @dev Check that pack addresses works as intended
34 | function testPackAddresses(address from, address to, address contract_) public {
35 | (bytes32 firstPacked, bytes32 secondPacked) = Storage.packAddresses(from, to, contract_);
36 | assertEq(from, address(uint160(uint256(firstPacked))));
37 | assertEq(to, address(uint160(uint256(secondPacked))));
38 | // Check that there is 4 bytes of zeros at the start of first packed
39 | assertEq(0, uint256(firstPacked) >> 224);
40 | // Check contract is stored correctly
41 | assertEq(uint256(uint160(contract_)) >> 96, uint256(firstPacked) >> 160);
42 | assertEq((uint256(uint160(contract_)) << 160) >> 160, uint256(secondPacked) >> 160);
43 | // Check that unpackAddresses inverts correctly
44 | (address checkFrom, address checkTo, address checkContract_) = Storage.unpackAddresses(firstPacked, secondPacked);
45 | assertEq(from, checkFrom);
46 | assertEq(to, checkTo);
47 | assertEq(contract_, checkContract_);
48 | // Check that unpackAddress inverts correctly
49 | (checkFrom) = Storage.unpackAddress(firstPacked);
50 | (checkTo) = Storage.unpackAddress(secondPacked);
51 | assertEq(checkFrom, from);
52 | assertEq(checkTo, to);
53 | }
54 |
55 | function testPackAddressesLargeInputs(uint256 from, uint256 to, uint256 contract_) public {
56 | uint256 minSize = type(uint160).max;
57 | vm.assume(from > minSize && to > minSize && contract_ > minSize);
58 | address largeFrom;
59 | address largeTo;
60 | address largeContract_;
61 | assembly {
62 | largeFrom := from
63 | largeTo := to
64 | largeContract_ := contract_
65 | }
66 | uint256 testLargeFrom;
67 | uint256 testLargeTo;
68 | uint256 testLargeContract_;
69 | assembly {
70 | testLargeFrom := largeFrom
71 | testLargeTo := largeTo
72 | testLargeContract_ := largeContract_
73 | }
74 | assertEq(testLargeFrom, from);
75 | assertEq(testLargeTo, to);
76 | assertEq(testLargeContract_, contract_);
77 | bytes32 firstPacked;
78 | bytes32 secondPacked;
79 | (firstPacked, secondPacked) = Storage.packAddresses(largeFrom, largeTo, largeContract_);
80 | // Check that there is 4 bytes of zeros at the start of first packed
81 | assertEq(0, uint256(firstPacked) >> 224);
82 | // Check that large numbers do not match
83 | assertFalse(uint160(uint256(firstPacked)) == from);
84 | assertFalse(uint160(uint256(secondPacked)) == to);
85 | // Check that large numbers were correctly cleaned
86 | assertEq(uint160(uint256(firstPacked)), uint160(from));
87 | assertEq(uint160(uint256(secondPacked)), uint160(to));
88 | // unpackAddress and check they do not equal inputs
89 | (largeFrom, largeTo, largeContract_) = Storage.unpackAddresses(firstPacked, secondPacked);
90 |
91 | assembly {
92 | testLargeFrom := largeFrom
93 | testLargeTo := largeTo
94 | testLargeContract_ := largeContract_
95 | }
96 | // Assert that clean unpacked does not equal large inputs
97 | assertFalse(from == testLargeFrom);
98 | assertFalse(to == testLargeTo);
99 | assertFalse(contract_ == testLargeContract_);
100 | // Assert that clean unpacked matches cleaned inputs
101 | assertEq(address(uint160(from)), largeFrom);
102 | assertEq(address(uint160(to)), largeTo);
103 | assertEq(address(uint160(contract_)), largeContract_);
104 | // unpackAddress and check they do not equal inputs
105 | (largeFrom) = Storage.unpackAddress(firstPacked);
106 | (largeTo) = Storage.unpackAddress(secondPacked);
107 | assembly {
108 | testLargeFrom := largeFrom
109 | testLargeTo := largeTo
110 | }
111 | // Assert that clean unpacked does not equal large inputs
112 | assertFalse(from == testLargeFrom);
113 | assertFalse(to == testLargeTo);
114 | // Assert that clean unpacked matches cleaned inputs
115 | assertEq(address(uint160(from)), largeFrom);
116 | assertEq(address(uint160(to)), largeTo);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/test/examples/Airdrop.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 | import {console2} from "forge-std/console2.sol";
6 |
7 | import {Merkle} from "murky/Merkle.sol";
8 | import {Airdrop} from "src/examples/Airdrop.sol";
9 | import {DelegateRegistry} from "src/DelegateRegistry.sol";
10 | import {Math} from "openzeppelin/utils/math/Math.sol";
11 |
12 | contract AirdropTest is Test {
13 | Merkle public merkle;
14 |
15 | DelegateRegistry public registry;
16 |
17 | struct AirdropRecord {
18 | address receiver;
19 | uint256 amount;
20 | }
21 |
22 | uint256 public constant MAX_AIRDROP_SIZE = 100;
23 |
24 | uint256 public constant MAX_AMOUNT = 2 ** 200;
25 |
26 | Airdrop public airdrop;
27 |
28 | AirdropRecord[] public airdropData;
29 |
30 | bytes32[] public airdropHashes;
31 |
32 | bytes32 public merkleRoot;
33 |
34 | bytes32 public acceptableRight;
35 |
36 | struct Delegate {
37 | address delegate;
38 | uint256 allowance;
39 | bytes32 rights;
40 | }
41 |
42 | Delegate[] public delegateData;
43 |
44 | function setUp() public {
45 | merkle = new Merkle();
46 | registry = new DelegateRegistry();
47 | acceptableRight = "airdrop";
48 | }
49 |
50 | function _createAirdrop(uint256 addressSeed, uint256 amountSeed, uint256 n) internal {
51 | for (uint256 i = 0; i < n; i++) {
52 | (,, bytes32 data, AirdropRecord memory record) = _generateAirdropRecord(addressSeed, amountSeed, i);
53 | // Append to list
54 | airdropHashes.push(data);
55 | // Add to airdrop mapping
56 | airdropData.push(record);
57 | }
58 | merkleRoot = merkle.getRoot(airdropHashes);
59 | }
60 |
61 | function _generateAirdropRecord(uint256 addressSeed, uint256 amountSeed, uint256 i)
62 | internal
63 | pure
64 | returns (uint256 amount, address receiver, bytes32 data, AirdropRecord memory record)
65 | {
66 | amount = (uint256(keccak256(abi.encode(amountSeed, i))) % MAX_AMOUNT);
67 | if (amount == 0) amount += 1;
68 | receiver = address(bytes20(keccak256(abi.encode(addressSeed, i))));
69 | data = keccak256(abi.encodePacked(receiver, amount));
70 | record = AirdropRecord({receiver: receiver, amount: amount});
71 | }
72 |
73 | // function testCreateAirdrop(uint256 addressSeed, uint256 amountSeed, uint256 n, uint256 x) public {
74 | // vm.assume(n > 1 && n < MAX_AIRDROP_SIZE);
75 | // _createAirdrop(addressSeed, amountSeed, n);
76 | // // Test random value
77 | // vm.assume(x < n);
78 | // (uint256 amount, address receiver,,) = _generateAirdropRecord(addressSeed, amountSeed, x);
79 | // // Load struct and data from storage
80 | // AirdropRecord memory record = airdropData[x];
81 | // bytes32 data = keccak256(abi.encodePacked(receiver, amount));
82 | // assertEq(amount, record.amount);
83 | // assertEq(receiver, record.receiver);
84 | // assertEq(data, airdropHashes[x]);
85 | // // Generate proof and verify
86 | // bytes32[] memory proof = merkle.getProof(airdropHashes, x);
87 | // assertTrue(merkle.verifyProof(merkleRoot, proof, data));
88 | // }
89 |
90 | function testAirdropWithoutDelegate(uint256 addressSeed, uint256 amountSeed, uint256 n, address referenceToken) public {
91 | vm.assume(referenceToken != address(0));
92 | vm.assume(n > 1 && n < MAX_AIRDROP_SIZE && addressSeed != amountSeed);
93 | _createAirdrop(addressSeed, amountSeed, n);
94 | // Calculate total tokens to mint
95 | uint256 totalSupply_;
96 | for (uint256 i; i < n; i++) {
97 | totalSupply_ += airdropData[i].amount;
98 | }
99 | // Create airdrop token
100 | airdrop = new Airdrop(address(registry), referenceToken, acceptableRight, totalSupply_, merkleRoot);
101 | // Check data is stored correctly in token
102 | assertEq(address(registry), address(airdrop.delegateRegistry()));
103 | assertEq(merkleRoot, airdrop.merkleRoot());
104 | assertEq(referenceToken, airdrop.referenceToken());
105 | // Test that total supply is expected
106 | assertEq(totalSupply_, airdrop.balanceOf(address(airdrop)));
107 | // Try to claim with bogus proof
108 | for (uint256 i = 0; i < n; i++) {
109 | (uint256 bogusAmount, address bogusReceiver,,) = _generateAirdropRecord(amountSeed, addressSeed, i);
110 | bytes32[] memory proof = merkle.getProof(airdropHashes, i);
111 | vm.startPrank(bogusReceiver);
112 | vm.expectRevert("Invalid Proof");
113 | airdrop.claim(bogusReceiver, bogusAmount, bogusAmount, proof);
114 | vm.stopPrank();
115 | }
116 | // Claim airdrop for every receiver
117 | for (uint256 i = 0; i < n; i++) {
118 | address claimant = airdropData[i].receiver;
119 | uint256 amount = airdropData[i].amount;
120 | bytes32[] memory proof = merkle.getProof(airdropHashes, i);
121 | vm.startPrank(claimant);
122 | airdrop.claim(claimant, amount, amount, proof);
123 | // Claim again to ensure accounting is working
124 | airdrop.claim(claimant, amount, amount, proof);
125 | vm.stopPrank();
126 | // Check that tokens are received
127 | assertEq(amount, airdrop.balanceOf(claimant));
128 | // Check that claimed mapping is updated
129 | assertEq(amount, airdrop.claimed(claimant));
130 | }
131 | // Verify that contract no longer has any tokens
132 | assertEq(0, airdrop.balanceOf(address(airdrop)));
133 | }
134 |
135 | function _createDelegates(uint256 delegateSeed, uint256 allowanceSeed, uint256 n) internal {
136 | for (uint256 i = 0; i < n; i++) {
137 | uint256 allowance = uint256(keccak256(abi.encode(allowanceSeed, i))) % MAX_AMOUNT;
138 | if (allowance == 0) allowance += 1;
139 | address delegate = address(bytes20(keccak256(abi.encode(delegateSeed, i))));
140 | bytes32 rights = allowance % 2 == 0 ? bytes32(0) : acceptableRight;
141 | delegateData.push(Delegate({delegate: delegate, allowance: allowance, rights: rights}));
142 | }
143 | }
144 |
145 | function testAirdropWithDelegate(uint256 addressSeed, uint256 amountSeed, uint256 n, address referenceToken, uint256 delegateSeed, uint256 allowanceSeed) public {
146 | vm.assume(referenceToken != address(0));
147 | vm.assume(n > 1 && n < MAX_AIRDROP_SIZE && addressSeed != amountSeed && addressSeed != delegateSeed && addressSeed != allowanceSeed);
148 | vm.assume(amountSeed != delegateSeed && amountSeed != allowanceSeed);
149 | vm.assume(delegateSeed != allowanceSeed);
150 | _createAirdrop(addressSeed, amountSeed, n);
151 | // Calculate total tokens to mint
152 | uint256 totalSupply_;
153 | for (uint256 i; i < n; i++) {
154 | totalSupply_ += airdropData[i].amount;
155 | }
156 | // Create airdrop token
157 | airdrop = new Airdrop(address(registry), referenceToken, acceptableRight, totalSupply_, merkleRoot);
158 | // Create delegates
159 | _createDelegates(delegateSeed, allowanceSeed, n);
160 | // Try to claim with delegate
161 | // Try to claim every airdrop with delegate
162 | for (uint256 i = 0; i < n; i++) {
163 | address claimant = airdropData[i].receiver;
164 | uint256 amount = airdropData[i].amount;
165 | bytes32[] memory proof = merkle.getProof(airdropHashes, i);
166 | vm.startPrank(delegateData[i].delegate);
167 | vm.expectRevert("Insufficient Delegation");
168 | airdrop.claim(claimant, amount, amount, proof);
169 | vm.stopPrank();
170 | }
171 | // Delegate and claim airdrop
172 | for (uint256 i = 0; i < n; i++) {
173 | // Delegate
174 | vm.startPrank(airdropData[i].receiver);
175 | registry.delegateERC20(delegateData[i].delegate, referenceToken, delegateData[i].rights, delegateData[i].allowance);
176 | vm.stopPrank();
177 | // Delegate claims airdrop
178 | vm.startPrank(delegateData[i].delegate);
179 | bytes32[] memory proof = merkle.getProof(airdropHashes, i);
180 | airdrop.claim(airdropData[i].receiver, airdropData[i].amount, airdropData[i].amount, proof);
181 | vm.stopPrank();
182 | uint256 claimed = Math.min(delegateData[i].allowance, airdropData[i].amount);
183 | // Check that claimed is as expected
184 | assertEq(claimed, airdrop.claimed(airdropData[i].receiver));
185 | // Check that beneficiary claimed is as expected
186 | assertEq(claimed, airdrop.delegateClaimed(airdropData[i].receiver, delegateData[i].delegate));
187 | // Expect that token balance is claimed
188 | assertEq(claimed, airdrop.balanceOf(delegateData[i].delegate));
189 | // If claimed is airdrop amount, delegate tries to claim again but they receive no further tokens
190 | if (claimed == airdropData[i].amount) {
191 | vm.startPrank(delegateData[i].delegate);
192 | airdrop.claim(airdropData[i].receiver, airdropData[i].amount, airdropData[i].amount, proof);
193 | vm.stopPrank();
194 | }
195 | // Otherwise expect insufficient delegation error on further claim attempts
196 | else {
197 | vm.startPrank(delegateData[i].delegate);
198 | vm.expectRevert("Insufficient Delegation");
199 | airdrop.claim(airdropData[i].receiver, airdropData[i].amount, airdropData[i].amount, proof);
200 | vm.stopPrank();
201 | }
202 | // Check that claimed amounts are still the same for both cases
203 | assertEq(claimed, airdrop.claimed(airdropData[i].receiver));
204 | assertEq(claimed, airdrop.delegateClaimed(airdropData[i].receiver, delegateData[i].delegate));
205 | assertEq(claimed, airdrop.balanceOf(delegateData[i].delegate));
206 | // Get vault to claim remaining tokens
207 | uint256 remainingClaim = airdropData[i].amount - airdrop.claimed(airdropData[i].receiver);
208 | vm.startPrank(airdropData[i].receiver);
209 | airdrop.claim(airdropData[i].receiver, airdropData[i].amount, airdropData[i].amount, proof);
210 | vm.stopPrank();
211 | // Check balances for vault and delegate (again)
212 | assertEq(claimed + remainingClaim, airdrop.claimed(airdropData[i].receiver));
213 | assertEq(claimed, airdrop.delegateClaimed(airdropData[i].receiver, delegateData[i].delegate));
214 | assertEq(claimed, airdrop.balanceOf(delegateData[i].delegate));
215 | assertEq(remainingClaim, airdrop.balanceOf(airdropData[i].receiver));
216 | }
217 | // Verify that contract no longer has any tokens
218 | assertEq(0, airdrop.balanceOf(address(airdrop)));
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/test/examples/IPLicenseCheck.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {Test} from "forge-std/Test.sol";
5 |
6 | import {IPLicenseCheck} from "src/examples/IPLicenseCheck.sol";
7 | import {DelegateRegistry} from "src/DelegateRegistry.sol";
8 | import {ERC721} from "openzeppelin/token/ERC721/ERC721.sol";
9 |
10 | contract NFT is ERC721 {
11 | constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {
12 | _mint(msg.sender, 1);
13 | }
14 | }
15 |
16 | contract IPLicenseCheckTest is Test {
17 | DelegateRegistry public registry;
18 | IPLicenseCheck public ipLicenseCheck;
19 | ERC721 public nft;
20 |
21 | function setUp() public {
22 | registry = new DelegateRegistry();
23 | ipLicenseCheck = new IPLicenseCheck(address(registry));
24 | }
25 |
26 | function testCheckForIPLicense(address wallet, address vault, address fVault, bytes32 ipLicense, bytes32 fIpLicense) public {
27 | vm.assume(vault > address(1));
28 | vm.assume(wallet != vault && wallet != fVault && fVault != vault);
29 | vm.assume(ipLicense != fIpLicense);
30 | // Create nft
31 | vm.startPrank(vault);
32 | nft = new NFT("nft", "nft");
33 | vm.stopPrank();
34 | // Delegate IP license to wallet for vault and fVault
35 | vm.startPrank(vault);
36 | registry.delegateERC721(wallet, address(nft), 1, ipLicense, true);
37 | vm.stopPrank();
38 | // Check false if vault doesn't own NFT
39 | assertFalse(ipLicenseCheck.checkForIPLicense(wallet, fVault, address(nft), 1, ipLicense));
40 | // Check false if wallet has a different license
41 | if (ipLicense != "") assertFalse(ipLicenseCheck.checkForIPLicense(wallet, vault, address(nft), 1, fIpLicense));
42 | else assertTrue(ipLicenseCheck.checkForIPLicense(wallet, vault, address(nft), 1, fIpLicense));
43 | // Check true if vault has nft and wallet has license
44 | assertTrue(ipLicenseCheck.checkForIPLicense(wallet, vault, address(nft), 1, ipLicense));
45 | }
46 |
47 | function testCheckForIPLicenseFromLicensor(address wallet, address vault, address licensor, address fLicensor, address fVault, bytes32 ipLicense, bytes32 fIpLicense) public {
48 | vm.assume(vault > address(1) && licensor > address(1));
49 | vm.assume(wallet != vault && wallet != licensor && wallet != fVault && wallet != fLicensor);
50 | vm.assume(licensor != vault && licensor != fVault && licensor != fLicensor);
51 | vm.assume(fVault != vault && fVault != fLicensor);
52 | vm.assume(fLicensor != vault);
53 | vm.assume(ipLicense != fIpLicense && ipLicense != "ip licensor" && fIpLicense != "ip licensor");
54 | // Create nft
55 | vm.startPrank(vault);
56 | nft = new NFT("nft", "nft");
57 | vm.stopPrank();
58 | // Delegate licensor rights to licensor for vault and IP license to wallet for licensor
59 | vm.startPrank(vault);
60 | registry.delegateERC721(licensor, address(nft), 1, "ip licensor", true);
61 | vm.stopPrank();
62 | vm.startPrank(licensor);
63 | registry.delegateERC721(wallet, address(nft), 1, ipLicense, true);
64 | vm.stopPrank();
65 | // Check false if vault doesn't own NFT
66 | assertFalse(ipLicenseCheck.checkForIPLicenseFromLicensor(wallet, licensor, fVault, address(nft), 1, ipLicense));
67 | // Check false if licensor doesn't have licensor rights
68 | assertFalse(ipLicenseCheck.checkForIPLicenseFromLicensor(wallet, fLicensor, vault, address(nft), 1, ipLicense));
69 | // Check false if wallet has a different license
70 | if (ipLicense != "") assertFalse(ipLicenseCheck.checkForIPLicenseFromLicensor(wallet, licensor, vault, address(nft), 1, fIpLicense));
71 | else assertTrue(ipLicenseCheck.checkForIPLicenseFromLicensor(wallet, licensor, vault, address(nft), 1, fIpLicense));
72 | // Check true if vault has nft, licensor has licensor rights, and wallet has license
73 | assertTrue(ipLicenseCheck.checkForIPLicenseFromLicensor(wallet, licensor, vault, address(nft), 1, ipLicense));
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/test/tools/RegistryHarness.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: CC0-1.0
2 | pragma solidity ^0.8.21;
3 |
4 | import {DelegateRegistry} from "src/DelegateRegistry.sol";
5 |
6 | /// @dev harness contract that exposes internal registry methods as external ones
7 | contract RegistryHarness is DelegateRegistry {
8 | constructor() {
9 | delegations[0][0] = 0;
10 | }
11 |
12 | bytes32[] temporaryStorage;
13 |
14 | function exposedDelegations(bytes32 hash) external view returns (bytes32[5] memory) {
15 | return delegations[hash];
16 | }
17 |
18 | function exposedOutgoingDelegationHashes(address vault) external view returns (bytes32[] memory) {
19 | return outgoingDelegationHashes[vault];
20 | }
21 |
22 | function exposedIncomingDelegationHashes(address delegate) external view returns (bytes32[] memory) {
23 | return incomingDelegationHashes[delegate];
24 | }
25 |
26 | function exposedPushDelegationHashes(address from, address to, bytes32 delegationHash) external {
27 | _pushDelegationHashes(from, to, delegationHash);
28 | }
29 |
30 | function exposedWriteDelegation(bytes32 location, uint256 position, bytes32 data) external {
31 | _writeDelegation(location, position, data);
32 | }
33 |
34 | function exposedWriteDelegation(bytes32 location, uint256 position, uint256 data) external {
35 | _writeDelegation(location, position, data);
36 | }
37 |
38 | function exposedWriteDelegationAddresses(bytes32 location, address from, address to, address contract_) external {
39 | _writeDelegationAddresses(location, from, to, contract_);
40 | }
41 |
42 | function exposedGetValidDelegationsFromHashes(bytes32[] calldata hashes) external returns (Delegation[] memory delegations_) {
43 | temporaryStorage = hashes;
44 | return _getValidDelegationsFromHashes(temporaryStorage);
45 | }
46 |
47 | function exposedGetValidDelegationHashesFromHashes(bytes32[] calldata hashes) external returns (bytes32[] memory validHashes) {
48 | temporaryStorage = hashes;
49 | return _getValidDelegationHashesFromHashes(temporaryStorage);
50 | }
51 |
52 | function exposedLoadDelegationBytes32(bytes32 location, uint256 position) external view returns (bytes32 data) {
53 | return _loadDelegationBytes32(location, position);
54 | }
55 |
56 | function exposedLoadDelegationUint(bytes32 location, uint256 position) external view returns (uint256 data) {
57 | return _loadDelegationUint(location, position);
58 | }
59 |
60 | function exposedLoadFrom(bytes32 location) external view returns (address from) {
61 | return _loadFrom(location);
62 | }
63 |
64 | function exposedValidateFrom(bytes32 location, address from) external view returns (bool) {
65 | return _validateFrom(location, from);
66 | }
67 |
68 | function exposedLoadDelegationAddresses(bytes32 location) external view returns (address from, address to, address contract_) {
69 | return _loadDelegationAddresses(location);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------