├── .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 | [PASS] testGas(address,bytes32) (runs: 256, μ: 13573356, ~: 13573452) 5 | Test result: ok. 1 passed; 0 failed; 0 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: 1 tests passed, 0 failed, 0 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 | [PASS] testHashGas(address,bytes32,address,uint256,address,bytes32) (runs: 256, μ: 19906, ~: 19906) 5 | Test result: ok. 1 passed; 0 failed; 0 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: 1 tests passed, 0 failed, 0 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 | --------------------------------------------------------------------------------