├── .gas-snapshot ├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── foundry.toml ├── package-lock.json ├── package.json ├── src ├── OperatorFilterer.sol └── example │ ├── ExampleERC1155.sol │ ├── ExampleERC721.sol │ ├── ExampleERC721A.sol │ ├── ExampleSoladyERC1155.sol │ ├── ExampleSoladyERC721.sol │ └── upgradeable │ ├── ExampleERC1155Upgradeable.sol │ ├── ExampleERC721AUpgradeable.sol │ ├── ExampleERC721Upgradeable.sol │ ├── ExampleSoladyERC1155Upgradeable.sol │ └── ExampleSoladyERC721Upgradeable.sol └── test ├── BaseRegistryTest.sol ├── DefaultOperatorFilterer.t.sol ├── OperatorFilterRegistry.t.sol ├── OperatorFilterer.t.sol ├── OwnedRegistrant.t.sol ├── PriorityOperatorFilterer.t.sol ├── example ├── ExampleERC1155.t.sol ├── ExampleERC721.t.sol ├── ExampleERC721A.t.sol ├── ExampleSoladyERC1155.t.sol ├── ExampleSoladyERC721.t.sol └── upgradeable │ ├── ExampleERC1155Upgradeable.t.sol │ ├── ExampleERC721AUpgradeable.t.sol │ ├── ExampleERC721Upgradeable.t.sol │ ├── ExampleSoladyERC1155Upgradeable.t.sol │ └── ExampleSoladyERC721Upgradeable.t.sol └── helpers ├── DefaultFilterer.sol ├── Filterer.sol └── PriorityFilterer.sol /.gas-snapshot: -------------------------------------------------------------------------------- 1 | DefaultOperatorFiltererTest:testFilter() (gas: 40715) 2 | ExampleERC1155Test:testExcludeApprovals() (gas: 101837) 3 | ExampleERC1155Test:testExclusionExceptionDoesNotApplyToOperators() (gas: 133742) 4 | ExampleERC1155Test:testFilter() (gas: 42790) 5 | ExampleERC1155Test:testOwnersNotExcluded() (gas: 106272) 6 | ExampleERC1155Test:testOwnersNotExcludedBatch() (gas: 109557) 7 | ExampleERC1155Test:testRepeatRegistrationOk() (gas: 6002017) 8 | ExampleERC1155Test:testSetOperatorFilteringEnabled() (gas: 6334428) 9 | ExampleERC1155Test:testSupportsInterface() (gas: 10798) 10 | ExampleERC1155UpgradeableTest:testExcludeApprovals() (gas: 101840) 11 | ExampleERC1155UpgradeableTest:testExclusionExceptionDoesNotApplyToOperators() (gas: 133824) 12 | ExampleERC1155UpgradeableTest:testFilter() (gas: 43043) 13 | ExampleERC1155UpgradeableTest:testOwnersNotExcluded() (gas: 106349) 14 | ExampleERC1155UpgradeableTest:testOwnersNotExcludedBatch() (gas: 109654) 15 | ExampleERC1155UpgradeableTest:testRepeatRegistrationOk() (gas: 6009575) 16 | ExampleERC1155UpgradeableTest:testSetOperatorFilteringEnabled() (gas: 6341862) 17 | ExampleERC1155UpgradeableTest:testSupportsInterface() (gas: 10853) 18 | ExampleERC1155UpgradeableTest:testUpgradeable() (gas: 2006067) 19 | ExampleERC721ATest:testExcludeApprovals() (gas: 149574) 20 | ExampleERC721ATest:testExclusionExceptionDoesNotApplyToOperators() (gas: 173671) 21 | ExampleERC721ATest:testFilter() (gas: 55618) 22 | ExampleERC721ATest:testOwnersNotExcluded() (gas: 164912) 23 | ExampleERC721ATest:testOwnersNotExcludedSafeTransfer() (gas: 202892) 24 | ExampleERC721ATest:testRepeatRegistrationOk() (gas: 4955278) 25 | ExampleERC721ATest:testSetOperatorFilteringEnabled() (gas: 5287637) 26 | ExampleERC721ATest:testSupportsInterface() (gas: 10587) 27 | ExampleERC721AUpgradeableTest:testExcludeApprovals() (gas: 149607) 28 | ExampleERC721AUpgradeableTest:testExclusionExceptionDoesNotApplyToOperators() (gas: 173769) 29 | ExampleERC721AUpgradeableTest:testFilter() (gas: 55839) 30 | ExampleERC721AUpgradeableTest:testOwnersNotExcluded() (gas: 165066) 31 | ExampleERC721AUpgradeableTest:testOwnersNotExcludedSafeTransfer() (gas: 203476) 32 | ExampleERC721AUpgradeableTest:testRepeatRegistrationOk() (gas: 4965771) 33 | ExampleERC721AUpgradeableTest:testSetOperatorFilteringEnabled() (gas: 5298050) 34 | ExampleERC721AUpgradeableTest:testSupportsInterface() (gas: 10642) 35 | ExampleERC721AUpgradeableTest:testUpgradeable() (gas: 2574353) 36 | ExampleERC721Test:testExcludeApprovals() (gas: 127911) 37 | ExampleERC721Test:testExclusionExceptionDoesNotApplyToOperators() (gas: 152018) 38 | ExampleERC721Test:testFilter() (gas: 55466) 39 | ExampleERC721Test:testOwnersNotExcluded() (gas: 124061) 40 | ExampleERC721Test:testOwnersNotExcludedSafeTransfer() (gas: 162314) 41 | ExampleERC721Test:testRepeatRegistrationOk() (gas: 5901253) 42 | ExampleERC721Test:testSetOperatorFilteringEnabled() (gas: 6233634) 43 | ExampleERC721Test:testSupportsInterface() (gas: 10748) 44 | ExampleERC721UpgradeableTest:testExcludeApprovals() (gas: 127947) 45 | ExampleERC721UpgradeableTest:testExclusionExceptionDoesNotApplyToOperators() (gas: 152119) 46 | ExampleERC721UpgradeableTest:testFilter() (gas: 55778) 47 | ExampleERC721UpgradeableTest:testOwnersNotExcluded() (gas: 124151) 48 | ExampleERC721UpgradeableTest:testOwnersNotExcludedSafeTransfer() (gas: 162497) 49 | ExampleERC721UpgradeableTest:testRepeatRegistrationOk() (gas: 5910805) 50 | ExampleERC721UpgradeableTest:testSetOperatorFilteringEnabled() (gas: 6243128) 51 | ExampleERC721UpgradeableTest:testSupportsInterface() (gas: 10803) 52 | ExampleERC721UpgradeableTest:testUpgradeable() (gas: 1918720) 53 | ExampleSoladyERC1155Test:testExcludeApprovals() (gas: 101105) 54 | ExampleSoladyERC1155Test:testExclusionExceptionDoesNotApplyToOperators() (gas: 132656) 55 | ExampleSoladyERC1155Test:testFilter() (gas: 41978) 56 | ExampleSoladyERC1155Test:testOwnersNotExcluded() (gas: 104537) 57 | ExampleSoladyERC1155Test:testOwnersNotExcludedBatch() (gas: 107214) 58 | ExampleSoladyERC1155Test:testRepeatRegistrationOk() (gas: 5809809) 59 | ExampleSoladyERC1155Test:testSetOperatorFilteringEnabled() (gas: 6142150) 60 | ExampleSoladyERC1155Test:testSupportsInterface() (gas: 10008) 61 | ExampleSoladyERC1155UpgradeableTest:testExcludeApprovals() (gas: 101127) 62 | ExampleSoladyERC1155UpgradeableTest:testExclusionExceptionDoesNotApplyToOperators() (gas: 132757) 63 | ExampleSoladyERC1155UpgradeableTest:testFilter() (gas: 42208) 64 | ExampleSoladyERC1155UpgradeableTest:testOwnersNotExcluded() (gas: 104627) 65 | ExampleSoladyERC1155UpgradeableTest:testOwnersNotExcludedBatch() (gas: 107304) 66 | ExampleSoladyERC1155UpgradeableTest:testRepeatRegistrationOk() (gas: 5825015) 67 | ExampleSoladyERC1155UpgradeableTest:testSetOperatorFilteringEnabled() (gas: 6157276) 68 | ExampleSoladyERC1155UpgradeableTest:testSupportsInterface() (gas: 10063) 69 | ExampleSoladyERC1155UpgradeableTest:testUpgradeable() (gas: 1262318) 70 | ExampleSoladyERC721Test:testExcludeApprovals() (gas: 127369) 71 | ExampleSoladyERC721Test:testExclusionExceptionDoesNotApplyToOperators() (gas: 151307) 72 | ExampleSoladyERC721Test:testFilter() (gas: 55381) 73 | ExampleSoladyERC721Test:testOwnersNotExcluded() (gas: 122430) 74 | ExampleSoladyERC721Test:testOwnersNotExcludedSafeTransfer() (gas: 158495) 75 | ExampleSoladyERC721Test:testRepeatRegistrationOk() (gas: 5755719) 76 | ExampleSoladyERC721Test:testSetOperatorFilteringEnabled() (gas: 6088048) 77 | ExampleSoladyERC721Test:testSupportsInterface() (gas: 9843) 78 | ExampleSoladyERC721UpgradeableTest:testExcludeApprovals() (gas: 127427) 79 | ExampleSoladyERC721UpgradeableTest:testExclusionExceptionDoesNotApplyToOperators() (gas: 151452) 80 | ExampleSoladyERC721UpgradeableTest:testFilter() (gas: 55649) 81 | ExampleSoladyERC721UpgradeableTest:testOwnersNotExcluded() (gas: 122542) 82 | ExampleSoladyERC721UpgradeableTest:testOwnersNotExcludedSafeTransfer() (gas: 158612) 83 | ExampleSoladyERC721UpgradeableTest:testRepeatRegistrationOk() (gas: 5765014) 84 | ExampleSoladyERC721UpgradeableTest:testSetOperatorFilteringEnabled() (gas: 6097330) 85 | ExampleSoladyERC721UpgradeableTest:testSupportsInterface() (gas: 9898) 86 | ExampleSoladyERC721UpgradeableTest:testUpgradeable() (gas: 1152536) 87 | OperatorFilterRegistryTest:testCodeHashOf() (gas: 8637) 88 | OperatorFilterRegistryTest:testCopyEntriesOf() (gas: 564491) 89 | OperatorFilterRegistryTest:testCopyEntriesOf_CannotUpdateWhileSubscribed() (gas: 271851) 90 | OperatorFilterRegistryTest:testCopyEntriesOf_NotRegistered() (gas: 11119) 91 | OperatorFilterRegistryTest:testCopyEntriesOf_NotRegistered_registrant() (gas: 36107) 92 | OperatorFilterRegistryTest:testCopyEntriesOf_OnlyAddressOrOwner() (gas: 68264) 93 | OperatorFilterRegistryTest:testCopyEntriesOf_cannotCopySelf() (gas: 31339) 94 | OperatorFilterRegistryTest:testFilteredCodeHashAt_subscription() (gas: 200540) 95 | OperatorFilterRegistryTest:testFilteredCodeHashes_subscription() (gas: 205573) 96 | OperatorFilterRegistryTest:testFilteredOperatorAt_subscription() (gas: 199936) 97 | OperatorFilterRegistryTest:testFilteredOperators_subscription() (gas: 205118) 98 | OperatorFilterRegistryTest:testIsCodeHashFiltered_subscription() (gas: 203913) 99 | OperatorFilterRegistryTest:testIsCodeHashOfFiltered() (gas: 111962) 100 | OperatorFilterRegistryTest:testIsCodeHashOfFiltered_subscription() (gas: 208499) 101 | OperatorFilterRegistryTest:testIsOperatorAllowed() (gas: 189902) 102 | OperatorFilterRegistryTest:testIsOperatorAllowed_NotRegistered() (gas: 10168) 103 | OperatorFilterRegistryTest:testIsOperatorAllowed_subscription() (gas: 286355) 104 | OperatorFilterRegistryTest:testIsOperatorFiltered_subscription() (gas: 205174) 105 | OperatorFilterRegistryTest:testIsRegistered() (gas: 35869) 106 | OperatorFilterRegistryTest:testOnlyAddressOrOwner() (gas: 25016) 107 | OperatorFilterRegistryTest:testRegisterAndCopyEntries() (gas: 338618) 108 | OperatorFilterRegistryTest:testRegisterAndCopyEntries_AlreadyRegistered() (gas: 33665) 109 | OperatorFilterRegistryTest:testRegisterAndCopyEntries_CannotCopyFromSelf() (gas: 31735) 110 | OperatorFilterRegistryTest:testRegisterAndCopyEntries_NotRegistered() (gas: 15036) 111 | OperatorFilterRegistryTest:testRegisterAndCopyEntries_OnlyAddressOrOwner() (gas: 16458) 112 | OperatorFilterRegistryTest:testRegisterAndSubscribe() (gas: 136711) 113 | OperatorFilterRegistryTest:testRegisterAndSubscribe_AlreadyRegistered() (gas: 33546) 114 | OperatorFilterRegistryTest:testRegisterAndSubscribe_CannotRegisterToSelf() (gas: 9225) 115 | OperatorFilterRegistryTest:testRegisterAndSubscribe_CannotSubscribeToRegistrantWithSubscription() (gas: 132544) 116 | OperatorFilterRegistryTest:testRegisterAndSubscribe_NotRegistered() (gas: 14972) 117 | OperatorFilterRegistryTest:testRegisterAndSubscribe_OnlyAddressOrOwner() (gas: 18070) 118 | OperatorFilterRegistryTest:testRegister_alreadyRegistered() (gas: 31770) 119 | OperatorFilterRegistryTest:testRegister_constructor() (gas: 38556) 120 | OperatorFilterRegistryTest:testRegister_onlyAddressOrOwner() (gas: 16341) 121 | OperatorFilterRegistryTest:testSubscribe() (gas: 137360) 122 | OperatorFilterRegistryTest:testSubscribe_AlreadySubscribed() (gas: 129709) 123 | OperatorFilterRegistryTest:testSubscribe_CannotSubscribeToRegistrantWithSubscription() (gas: 155299) 124 | OperatorFilterRegistryTest:testSubscribe_CannotSubscribeToSelf() (gas: 31688) 125 | OperatorFilterRegistryTest:testSubscribe_CannotSubscribeToZeroAddress() (gas: 31686) 126 | OperatorFilterRegistryTest:testSubscribe_OnlyAddressOrOwner() (gas: 43171) 127 | OperatorFilterRegistryTest:testSubscribe_SubscriptionNotRegistered() (gas: 36084) 128 | OperatorFilterRegistryTest:testSubscribe_notRegistered() (gas: 36227) 129 | OperatorFilterRegistryTest:testSubscribe_removeOldSubscription() (gas: 189656) 130 | OperatorFilterRegistryTest:testSubscriptionOf_notRegistered() (gas: 9252) 131 | OperatorFilterRegistryTest:testUnregister() (gas: 108236) 132 | OperatorFilterRegistryTest:testUnsubscribe() (gas: 106940) 133 | OperatorFilterRegistryTest:testUnsubscribe_NotRegistered() (gas: 9319) 134 | OperatorFilterRegistryTest:testUnsubscribe_NotSubscribed() (gas: 31878) 135 | OperatorFilterRegistryTest:testUnsubscribe_copyExistingEntries() (gas: 362163) 136 | OperatorFilterRegistryTest:testUnsubscribe_notRegistered() (gas: 9211) 137 | OperatorFilterRegistryTest:testUnsubscribe_onlyAddressOrOwner() (gas: 138601) 138 | OperatorFilterRegistryTest:testUpdateCodeHash() (gas: 105421) 139 | OperatorFilterRegistryTest:testUpdateCodeHash_CannotFilterEOAs() (gas: 31652) 140 | OperatorFilterRegistryTest:testUpdateCodeHash_CannotUpdateWhileSubscribed() (gas: 128209) 141 | OperatorFilterRegistryTest:testUpdateCodeHash_CodeHashAlreadyFiltered() (gas: 102577) 142 | OperatorFilterRegistryTest:testUpdateCodeHash_CodeHashNotFiltered() (gas: 34532) 143 | OperatorFilterRegistryTest:testUpdateCodeHash_NotRegistered() (gas: 9413) 144 | OperatorFilterRegistryTest:testUpdateCodeHash_OnlyAddressOrOwner() (gas: 16414) 145 | OperatorFilterRegistryTest:testUpdateCodeHash_unfilter() (gas: 83795) 146 | OperatorFilterRegistryTest:testUpdateCodeHashes() (gas: 150221) 147 | OperatorFilterRegistryTest:testUpdateCodeHashes_CannotFilterEOAs() (gas: 99620) 148 | OperatorFilterRegistryTest:testUpdateCodeHashes_CannotUpdateWhileSubscribed() (gas: 129156) 149 | OperatorFilterRegistryTest:testUpdateCodeHashes_CodeHashAlreadyFiltered() (gas: 151687) 150 | OperatorFilterRegistryTest:testUpdateCodeHashes_CodeHashNotFiltered() (gas: 35561) 151 | OperatorFilterRegistryTest:testUpdateCodeHashes_OnlyAddressOrOwner() (gas: 17168) 152 | OperatorFilterRegistryTest:testUpdateCodeHashes_notRegistered() (gas: 10199) 153 | OperatorFilterRegistryTest:testUpdateCodeHashes_unfilter() (gas: 127646) 154 | OperatorFilterRegistryTest:testUpdateOperator() (gas: 112458) 155 | OperatorFilterRegistryTest:testUpdateOperator_AddressAlreadyFiltered() (gas: 107890) 156 | OperatorFilterRegistryTest:testUpdateOperator_AddressNotFiltered() (gas: 38041) 157 | OperatorFilterRegistryTest:testUpdateOperator_CannotUpdateWhileSubscribed() (gas: 130003) 158 | OperatorFilterRegistryTest:testUpdateOperator_OnlyAddressOrOwner() (gas: 18185) 159 | OperatorFilterRegistryTest:testUpdateOperator_notRegistered() (gas: 11174) 160 | OperatorFilterRegistryTest:testUpdateOperator_unfilter() (gas: 89497) 161 | OperatorFilterRegistryTest:testUpdateOperators() (gas: 161134) 162 | OperatorFilterRegistryTest:testUpdateOperators_AddressAlreadyFiltered() (gas: 160489) 163 | OperatorFilterRegistryTest:testUpdateOperators_AddressNotFiltered() (gas: 40756) 164 | OperatorFilterRegistryTest:testUpdateOperators_CannotUpdateWhileSubscribed() (gas: 132458) 165 | OperatorFilterRegistryTest:testUpdateOperators_OnlyAddressOrOwner() (gas: 18782) 166 | OperatorFilterRegistryTest:testUpdateOperators_notRegistered() (gas: 13599) 167 | OperatorFilterRegistryTest:testUpdateOperators_unfilter() (gas: 133965) 168 | OperatorFiltererTest:testConstructor() (gas: 87287) 169 | OperatorFiltererTest:testConstructor_copy() (gas: 238316) 170 | OperatorFiltererTest:testConstructor_subscribe() (gas: 157548) 171 | OperatorFiltererTest:testConstructory_noSubscribeOrCopy() (gas: 308570) 172 | OperatorFiltererTest:testFilter() (gas: 40886) 173 | OperatorFiltererTest:testFilterWithMsgSenderGas() (gas: 5589) 174 | OperatorFiltererTest:testFilterWithMsgSenderOriginalGas() (gas: 8268) 175 | OperatorFiltererTest:testFilterWithOperatorGas() (gas: 18132) 176 | OperatorFiltererTest:testFilterWithOperatorOriginalGas() (gas: 18498) 177 | OperatorFiltererTest:testRegisterNonExistentRegistryDoesNotRevert() (gas: 103746) 178 | OperatorFiltererTest:testRegistryNotDeployedDoesNotRevert() (gas: 278432) 179 | PriorityOperatorFiltererTest:testPriorityFilter() (gas: 100485) 180 | PriorityOperatorFiltererTest:testPriorityFilterWithMsgSenderGas() (gas: 5568) 181 | PriorityOperatorFiltererTest:testPriorityFilterWithOperatorGas() (gas: 18085) 182 | PriorityOperatorFiltererTest:testPriorityFilterWithPriorityOperatorGas() (gas: 12670) -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Describe the changes made in your pull request here. 4 | 5 | ## Checklist 6 | 7 | Ensure you completed **all of the steps** below before submitting your pull request: 8 | 9 | - [ ] Ran `forge fmt`? 10 | - [ ] Ran `forge snapshot`? 11 | - [ ] Ran `forge test`? 12 | 13 | _Pull requests with an incomplete checklist will be thrown out._ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | name: Forge Testing 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | profile: [via-ir,min-solc,min-solc-via-ir,intense-0,intense-1] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install Foundry 18 | uses: onbjerg/foundry-toolchain@v1 19 | with: 20 | version: nightly 21 | 22 | - name: Install Dependencies 23 | run: forge install 24 | 25 | - name: Run Lint Check 26 | run: forge fmt --check 27 | 28 | - name: Run Tests with ${{ matrix.profile }} 29 | run: FOUNDRY_PROFILE=${{ matrix.profile }} forge test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS files 2 | node_modules/ 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | # Hardhat files 8 | cache 9 | artifacts 10 | 11 | cache/ 12 | out/ 13 | 14 | # Ignore Environment Variables! 15 | .env 16 | .env.prod 17 | 18 | # Ignore all vscode settings 19 | .vscode/ 20 | 21 | # Ignore flattened files 22 | flattened.txt 23 | 24 | broadcast 25 | 26 | # Coverage 27 | lcov.info 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/openzeppelin-contracts"] 2 | path = lib/openzeppelin-contracts 3 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 4 | [submodule "lib/openzeppelin-contracts-upgradeable"] 5 | path = lib/openzeppelin-contracts-upgradeable 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 7 | [submodule "lib/operator-filter-registry"] 8 | path = lib/operator-filter-registry 9 | url = https://github.com/ProjectOpenSea/operator-filter-registry 10 | [submodule "lib/forge-std"] 11 | path = lib/forge-std 12 | url = https://github.com/foundry-rs/forge-std 13 | [submodule "lib/erc721a"] 14 | path = lib/erc721a 15 | url = https://github.com/chiru-labs/erc721a 16 | [submodule "lib/erc721a-upgradeable"] 17 | path = lib/erc721a-upgradeable 18 | url = https://github.com/chiru-labs/erc721a-upgradeable 19 | [submodule "lib/solady"] 20 | path = lib/solady 21 | url = https://github.com/vectorized/solady 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vectorized.eth. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClosedSea 🚪 2 | 3 | [![NPM][npm-shield]][npm-url] 4 | [![CI][ci-shield]][ci-url] 5 | [![MIT License][license-shield]][license-url] 6 | 7 | > **📢 The OpenSea deadline for newly published contracts to include operator filtering has been extended to 2nd Jan 2023.** 8 | > 9 | > **However, there is a new requirement that all contracts published from then onwards MUST include the ERC2981 royalty standard.** 10 | > 11 | > **Please refer to the examples, updated for your convenience.** 12 | 13 | ## About the Project 14 | 15 | Gas optimized and flexible version of OpenSea's Mandatory Operator Filterer for royalties. 16 | 17 | ## Features 18 | 19 | - Modifiers can be toggled on / off efficiently. 20 | 21 | - Gas optimized. Saves 300+ gas on transfers. 22 | 23 | - Keeps your clients and some marketplaces happy. 24 | 25 | ## Installation 26 | 27 | To install with [**Foundry**](https://github.com/gakonst/foundry): 28 | 29 | ```sh 30 | forge install vectorized/closedsea 31 | ``` 32 | 33 | To install with [**Hardhat**](https://github.com/nomiclabs/hardhat) or [**Truffle**](https://github.com/trufflesuite/truffle): 34 | 35 | ```sh 36 | npm install closedsea 37 | ``` 38 | 39 | ## Contracts 40 | 41 | ```ml 42 | src 43 | ├─ OperatorFilterer.sol — "Operator Filterer for regular and upgradeable contracts" 44 | └─ example 45 | ├─ ExampleERC721A.sol — "ERC721A example" 46 | ├─ ExampleERC721.sol — "ERC721 example" 47 | ├─ ExampleERC1155.sol — "ERC1155 example" 48 | └─ upgradeable 49 | ├─ ExampleERC721AUpgradeable.sol — "ERC721A upgradeable example" 50 | ├─ ExampleERC721Upgradeable.sol — "ERC721 upgradeable example" 51 | └─ ExampleERC1155Upgradeable.sol — "ERC1155 upgradeable example" 52 | ``` 53 | 54 | ## Examples 55 | 56 | | Type | Contract | 57 | |---|---| 58 | | ERC721A | [`src/example/ExampleERC721A.sol`](./src/example/ExampleERC721A.sol) | 59 | | ERC721 | [`src/example/ExampleERC721.sol`](./src/example/ExampleERC721.sol) | 60 | | ERC1155 | [`src/example/ExampleERC1155.sol`](./src/example/ExampleERC1155.sol) | 61 | | ERC721AUpgradeable | [`src/example/upgradeable/ExampleERC721AUpgradeable.sol`](./src/example/upgradeable/ExampleERC721AUpgradeable.sol) | 62 | | ERC721Upgradeable | [`src/example/upgradeable/ExampleERC721Upgradeable.sol`](./src/example/upgradeable/ExampleERC721Upgradeable.sol) | 63 | | ERC1155Upgradeable | [`src/example/upgradeable/ExampleERC1155Upgradeable.sol`](./src/example/upgradeable/ExampleERC1155Upgradeable.sol) | 64 | 65 | ## API 66 | 67 | ### `_registerForOperatorFiltering` 68 | ```solidity 69 | function _registerForOperatorFiltering( 70 | address subscriptionOrRegistrantToCopy, 71 | bool subscribe 72 | ) internal virtual 73 | ```` 74 | Registration function that can be called in an initializer, anywhere. 75 | 76 | Can be called repeatedly without issues. 77 | 78 | To subscribe to the [default OpenSea curated block list](https://github.com/ProjectOpenSea/operator-filter-registry/#deployments), simply use `_registerForOperatorFiltering()`, without arguments. 79 | 80 | ### `onlyAllowedOperator` 81 | ```solidity 82 | modifier onlyAllowedOperator(address from) virtual 83 | ``` 84 | Modifier to guard a function and revert if `from` is a blocked operator. 85 | 86 | ### `onlyAllowedOperatorApproval` 87 | ```solidity 88 | modifier onlyAllowedOperatorApproval(address operator) virtual 89 | ``` 90 | Modifier to guard a function from approving a blocked operator. 91 | 92 | ### `_operatorFilteringEnabled` 93 | ```solidity 94 | function _operatorFilteringEnabled() internal view virtual returns (bool) 95 | ``` 96 | For deriving contracts to override, so that operator filtering can be turned on / off. 97 | 98 | Returns true by default. 99 | 100 | ### `_isPriorityOperator` 101 | ```solidity 102 | function _isPriorityOperator(address operator) internal view virtual returns (bool) 103 | ``` 104 | For deriving contracts to override, so that preferred marketplaces can skip operator filtering, helping users save gas. 105 | 106 | Returns false for all inputs by default. 107 | 108 | ## Safety 109 | 110 | We have verified with OpenSea engineers that ClosedSea is fully compatible with their royalties enforcement system, as of Nov 18th 2022. 111 | 112 | Nevertheless, this codebase is provided on an "as is" and "as available" basis. 113 | 114 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase. 115 | 116 | ## Acknowledgements 117 | 118 | This repository is inspired by and directly modified from: 119 | 120 | - [operator-filter-registry](https://github.com/ProjectOpenSea/operator-filter-registry) 121 | 122 | [npm-shield]: https://img.shields.io/npm/v/closedsea.svg 123 | [npm-url]: https://www.npmjs.com/package/closedsea 124 | 125 | [ci-shield]: https://img.shields.io/github/actions/workflow/status/vectorized/closedsea/ci.yml?label=build&branch=main 126 | [ci-url]: https://github.com/vectorized/closedsea/actions/workflows/ci.yml 127 | 128 | [license-shield]: https://img.shields.io/badge/License-MIT-green.svg 129 | [license-url]: https://github.com/vectorized/closedsea/blob/main/LICENSE.txt 130 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | # Foundry Configuration File 2 | # Default definitions: https://github.com/gakonst/foundry/blob/b7917fa8491aedda4dd6db53fbb206ea233cd531/config/src/lib.rs#L782 3 | # See more config options at: https://github.com/gakonst/foundry/tree/master/config 4 | 5 | # The Default Profile 6 | [profile.default] 7 | solc_version = '0.8.17' 8 | auto_detect_solc = false 9 | optimizer = true 10 | optimizer_runs = 1_000 11 | gas_limit = 100_000_000 # ETH is 30M, but we use a higher value. 12 | remappings = [ 13 | "forge-std=lib/forge-std/src/", 14 | "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", 15 | "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/", 16 | "erc721a=lib/erc721a/contracts/", 17 | "erc721a-upgradeable=lib/erc721a-upgradeable/contracts/", 18 | "operator-filter-registry/=lib/operator-filter-registry/src/", 19 | "solady/=lib/solady/src/" 20 | ] 21 | 22 | [fmt] 23 | line_length = 100 # While we allow up to 120, we lint at 100 for readability. 24 | 25 | [profile.default.fuzz] 26 | runs = 256 27 | 28 | [profile.intense-0.fuzz] 29 | runs = 5_000 30 | 31 | [profile.intense-1.fuzz] 32 | seed = 0x12345678 33 | runs = 5_000 34 | 35 | [profile.via-ir] 36 | via_ir = true 37 | 38 | [profile.via-ir.fuzz] 39 | runs = 1_000 40 | 41 | [profile.min-solc] 42 | solc_version = '0.8.13' 43 | 44 | [profile.min-solc.fuzz] 45 | runs = 1_000 46 | 47 | [profile.min-solc-via-ir] 48 | via_ir = true 49 | solc_version = '0.8.13' 50 | 51 | [profile.min-solc-via-ir.fuzz] 52 | runs = 1_000 53 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "closedsea", 3 | "version": "0.0.13", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "closedsea", 9 | "version": "0.0.13", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "closedsea", 3 | "license": "MIT", 4 | "version": "1.0.0", 5 | "description": "Optimized and flexible version of OpenSea's operator filterer", 6 | "files": [ 7 | "src/**/*.sol" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vectorized/closedsea.git" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OperatorFilterer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Optimized and flexible operator filterer to abide to OpenSea's 5 | /// mandatory on-chain royalty enforcement in order for new collections to 6 | /// receive royalties. 7 | /// For more information, see: 8 | /// See: https://github.com/ProjectOpenSea/operator-filter-registry 9 | abstract contract OperatorFilterer { 10 | /// @dev The default OpenSea operator blocklist subscription. 11 | address internal constant _DEFAULT_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6; 12 | 13 | /// @dev The OpenSea operator filter registry. 14 | address internal constant _OPERATOR_FILTER_REGISTRY = 0x000000000000AAeB6D7670E522A718067333cd4E; 15 | 16 | /// @dev Registers the current contract to OpenSea's operator filter, 17 | /// and subscribe to the default OpenSea operator blocklist. 18 | /// Note: Will not revert nor update existing settings for repeated registration. 19 | function _registerForOperatorFiltering() internal virtual { 20 | _registerForOperatorFiltering(_DEFAULT_SUBSCRIPTION, true); 21 | } 22 | 23 | /// @dev Registers the current contract to OpenSea's operator filter. 24 | /// Note: Will not revert nor update existing settings for repeated registration. 25 | function _registerForOperatorFiltering(address subscriptionOrRegistrantToCopy, bool subscribe) 26 | internal 27 | virtual 28 | { 29 | /// @solidity memory-safe-assembly 30 | assembly { 31 | let functionSelector := 0x7d3e3dbe // `registerAndSubscribe(address,address)`. 32 | 33 | // Clean the upper 96 bits of `subscriptionOrRegistrantToCopy` in case they are dirty. 34 | subscriptionOrRegistrantToCopy := shr(96, shl(96, subscriptionOrRegistrantToCopy)) 35 | 36 | for {} iszero(subscribe) {} { 37 | if iszero(subscriptionOrRegistrantToCopy) { 38 | functionSelector := 0x4420e486 // `register(address)`. 39 | break 40 | } 41 | functionSelector := 0xa0af2903 // `registerAndCopyEntries(address,address)`. 42 | break 43 | } 44 | // Store the function selector. 45 | mstore(0x00, shl(224, functionSelector)) 46 | // Store the `address(this)`. 47 | mstore(0x04, address()) 48 | // Store the `subscriptionOrRegistrantToCopy`. 49 | mstore(0x24, subscriptionOrRegistrantToCopy) 50 | // Register into the registry. 51 | if iszero(call(gas(), _OPERATOR_FILTER_REGISTRY, 0, 0x00, 0x44, 0x00, 0x04)) { 52 | // If the function selector has not been overwritten, 53 | // it is an out-of-gas error. 54 | if eq(shr(224, mload(0x00)), functionSelector) { 55 | // To prevent gas under-estimation. 56 | revert(0, 0) 57 | } 58 | } 59 | // Restore the part of the free memory pointer that was overwritten, 60 | // which is guaranteed to be zero, because of Solidity's memory size limits. 61 | mstore(0x24, 0) 62 | } 63 | } 64 | 65 | /// @dev Modifier to guard a function and revert if the caller is a blocked operator. 66 | modifier onlyAllowedOperator(address from) virtual { 67 | if (from != msg.sender) { 68 | if (!_isPriorityOperator(msg.sender)) { 69 | if (_operatorFilteringEnabled()) _revertIfBlocked(msg.sender); 70 | } 71 | } 72 | _; 73 | } 74 | 75 | /// @dev Modifier to guard a function from approving a blocked operator.. 76 | modifier onlyAllowedOperatorApproval(address operator) virtual { 77 | if (!_isPriorityOperator(operator)) { 78 | if (_operatorFilteringEnabled()) _revertIfBlocked(operator); 79 | } 80 | _; 81 | } 82 | 83 | /// @dev Helper function that reverts if the `operator` is blocked by the registry. 84 | function _revertIfBlocked(address operator) private view { 85 | /// @solidity memory-safe-assembly 86 | assembly { 87 | // Store the function selector of `isOperatorAllowed(address,address)`, 88 | // shifted left by 6 bytes, which is enough for 8tb of memory. 89 | // We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL). 90 | mstore(0x00, 0xc6171134001122334455) 91 | // Store the `address(this)`. 92 | mstore(0x1a, address()) 93 | // Store the `operator`. 94 | mstore(0x3a, operator) 95 | 96 | // `isOperatorAllowed` always returns true if it does not revert. 97 | if iszero(staticcall(gas(), _OPERATOR_FILTER_REGISTRY, 0x16, 0x44, 0x00, 0x00)) { 98 | // Bubble up the revert if the staticcall reverts. 99 | returndatacopy(0x00, 0x00, returndatasize()) 100 | revert(0x00, returndatasize()) 101 | } 102 | 103 | // We'll skip checking if `from` is inside the blacklist. 104 | // Even though that can block transferring out of wrapper contracts, 105 | // we don't want tokens to be stuck. 106 | 107 | // Restore the part of the free memory pointer that was overwritten, 108 | // which is guaranteed to be zero, if less than 8tb of memory is used. 109 | mstore(0x3a, 0) 110 | } 111 | } 112 | 113 | /// @dev For deriving contracts to override, so that operator filtering 114 | /// can be turned on / off. 115 | /// Returns true by default. 116 | function _operatorFilteringEnabled() internal view virtual returns (bool) { 117 | return true; 118 | } 119 | 120 | /// @dev For deriving contracts to override, so that preferred marketplaces can 121 | /// skip operator filtering, helping users save gas. 122 | /// Returns false for all inputs by default. 123 | function _isPriorityOperator(address) internal view virtual returns (bool) { 124 | return false; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/example/ExampleERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC1155} from "openzeppelin-contracts/token/ERC1155/ERC1155.sol"; 5 | import {OperatorFilterer} from "../OperatorFilterer.sol"; 6 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 7 | import {IERC2981, ERC2981} from "openzeppelin-contracts/token/common/ERC2981.sol"; 8 | 9 | /** 10 | * @title ExampleERC1155 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleERC1155 is ERC1155, OperatorFilterer, Ownable, ERC2981 { 17 | bool public operatorFilteringEnabled; 18 | 19 | constructor() ERC1155("") { 20 | _registerForOperatorFiltering(); 21 | operatorFilteringEnabled = true; 22 | 23 | // Set royalty receiver to the contract creator, 24 | // at 5% (default denominator is 10000). 25 | _setDefaultRoyalty(msg.sender, 500); 26 | } 27 | 28 | function setApprovalForAll(address operator, bool approved) 29 | public 30 | override 31 | onlyAllowedOperatorApproval(operator) 32 | { 33 | super.setApprovalForAll(operator, approved); 34 | } 35 | 36 | function safeTransferFrom( 37 | address from, 38 | address to, 39 | uint256 tokenId, 40 | uint256 amount, 41 | bytes memory data 42 | ) public override onlyAllowedOperator(from) { 43 | super.safeTransferFrom(from, to, tokenId, amount, data); 44 | } 45 | 46 | function safeBatchTransferFrom( 47 | address from, 48 | address to, 49 | uint256[] memory ids, 50 | uint256[] memory amounts, 51 | bytes memory data 52 | ) public override onlyAllowedOperator(from) { 53 | super.safeBatchTransferFrom(from, to, ids, amounts, data); 54 | } 55 | 56 | function supportsInterface(bytes4 interfaceId) 57 | public 58 | view 59 | virtual 60 | override(ERC1155, ERC2981) 61 | returns (bool) 62 | { 63 | // Supports the following `interfaceId`s: 64 | // - IERC165: 0x01ffc9a7 65 | // - IERC1155: 0xd9b67a26 66 | // - IERC1155MetadataURI: 0x0e89341c 67 | // - IERC2981: 0x2a55205a 68 | return ERC1155.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 69 | } 70 | 71 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 72 | _setDefaultRoyalty(receiver, feeNumerator); 73 | } 74 | 75 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 76 | operatorFilteringEnabled = value; 77 | } 78 | 79 | function _operatorFilteringEnabled() internal view override returns (bool) { 80 | return operatorFilteringEnabled; 81 | } 82 | 83 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 84 | // OpenSea Seaport Conduit: 85 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 86 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 87 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/example/ExampleERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC721} from "openzeppelin-contracts/token/ERC721/ERC721.sol"; 5 | import {OperatorFilterer} from "../OperatorFilterer.sol"; 6 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 7 | import {IERC2981, ERC2981} from "openzeppelin-contracts/token/common/ERC2981.sol"; 8 | 9 | /** 10 | * @title ExampleERC721 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleERC721 is ERC721, OperatorFilterer, Ownable, ERC2981 { 17 | bool public operatorFilteringEnabled; 18 | 19 | constructor() ERC721("Example", "EXAMPLE") { 20 | _registerForOperatorFiltering(); 21 | operatorFilteringEnabled = true; 22 | 23 | // Set royalty receiver to the contract creator, 24 | // at 5% (default denominator is 10000). 25 | _setDefaultRoyalty(msg.sender, 500); 26 | } 27 | 28 | function setApprovalForAll(address operator, bool approved) 29 | public 30 | override 31 | onlyAllowedOperatorApproval(operator) 32 | { 33 | super.setApprovalForAll(operator, approved); 34 | } 35 | 36 | function approve(address operator, uint256 tokenId) 37 | public 38 | override 39 | onlyAllowedOperatorApproval(operator) 40 | { 41 | super.approve(operator, tokenId); 42 | } 43 | 44 | function transferFrom(address from, address to, uint256 tokenId) 45 | public 46 | override 47 | onlyAllowedOperator(from) 48 | { 49 | super.transferFrom(from, to, tokenId); 50 | } 51 | 52 | function safeTransferFrom(address from, address to, uint256 tokenId) 53 | public 54 | override 55 | onlyAllowedOperator(from) 56 | { 57 | super.safeTransferFrom(from, to, tokenId); 58 | } 59 | 60 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) 61 | public 62 | override 63 | onlyAllowedOperator(from) 64 | { 65 | super.safeTransferFrom(from, to, tokenId, data); 66 | } 67 | 68 | function tokenURI(uint256) public pure override returns (string memory) { 69 | return ""; 70 | } 71 | 72 | function supportsInterface(bytes4 interfaceId) 73 | public 74 | view 75 | virtual 76 | override(ERC721, ERC2981) 77 | returns (bool) 78 | { 79 | // Supports the following `interfaceId`s: 80 | // - IERC165: 0x01ffc9a7 81 | // - IERC721: 0x80ac58cd 82 | // - IERC721Metadata: 0x5b5e139f 83 | // - IERC2981: 0x2a55205a 84 | return ERC721.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 85 | } 86 | 87 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 88 | _setDefaultRoyalty(receiver, feeNumerator); 89 | } 90 | 91 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 92 | operatorFilteringEnabled = value; 93 | } 94 | 95 | function _operatorFilteringEnabled() internal view override returns (bool) { 96 | return operatorFilteringEnabled; 97 | } 98 | 99 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 100 | // OpenSea Seaport Conduit: 101 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 102 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 103 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/example/ExampleERC721A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {IERC721A, ERC721A} from "erc721a/ERC721A.sol"; 5 | import {ERC721AQueryable} from "erc721a/extensions/ERC721AQueryable.sol"; 6 | import {ERC721ABurnable} from "erc721a/extensions/ERC721ABurnable.sol"; 7 | import {OperatorFilterer} from "../OperatorFilterer.sol"; 8 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 9 | import {IERC2981, ERC2981} from "openzeppelin-contracts/token/common/ERC2981.sol"; 10 | 11 | /** 12 | * @title ExampleERC721A 13 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 14 | * token and subscribes it to OpenSea's curated filters. 15 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 16 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 17 | */ 18 | abstract contract ExampleERC721A is 19 | ERC721AQueryable, 20 | ERC721ABurnable, 21 | OperatorFilterer, 22 | Ownable, 23 | ERC2981 24 | { 25 | bool public operatorFilteringEnabled; 26 | 27 | constructor() ERC721A("Example", "EXAMPLE") { 28 | _registerForOperatorFiltering(); 29 | operatorFilteringEnabled = true; 30 | 31 | // Set royalty receiver to the contract creator, 32 | // at 5% (default denominator is 10000). 33 | _setDefaultRoyalty(msg.sender, 500); 34 | } 35 | 36 | function setApprovalForAll(address operator, bool approved) 37 | public 38 | override(IERC721A, ERC721A) 39 | onlyAllowedOperatorApproval(operator) 40 | { 41 | super.setApprovalForAll(operator, approved); 42 | } 43 | 44 | function approve(address operator, uint256 tokenId) 45 | public 46 | payable 47 | override(IERC721A, ERC721A) 48 | onlyAllowedOperatorApproval(operator) 49 | { 50 | super.approve(operator, tokenId); 51 | } 52 | 53 | /** 54 | * @dev Both safeTransferFrom functions in ERC721A call this function 55 | * so we don't need to override them. 56 | */ 57 | function transferFrom(address from, address to, uint256 tokenId) 58 | public 59 | payable 60 | override(IERC721A, ERC721A) 61 | onlyAllowedOperator(from) 62 | { 63 | super.transferFrom(from, to, tokenId); 64 | } 65 | 66 | function supportsInterface(bytes4 interfaceId) 67 | public 68 | view 69 | virtual 70 | override(IERC721A, ERC721A, ERC2981) 71 | returns (bool) 72 | { 73 | // Supports the following `interfaceId`s: 74 | // - IERC165: 0x01ffc9a7 75 | // - IERC721: 0x80ac58cd 76 | // - IERC721Metadata: 0x5b5e139f 77 | // - IERC2981: 0x2a55205a 78 | return ERC721A.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 79 | } 80 | 81 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 82 | _setDefaultRoyalty(receiver, feeNumerator); 83 | } 84 | 85 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 86 | operatorFilteringEnabled = value; 87 | } 88 | 89 | function _operatorFilteringEnabled() internal view override returns (bool) { 90 | return operatorFilteringEnabled; 91 | } 92 | 93 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 94 | // OpenSea Seaport Conduit: 95 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 96 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 97 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/example/ExampleSoladyERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC1155} from "solady/tokens/ERC1155.sol"; 5 | import {ERC2981} from "solady/tokens/ERC2981.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {OperatorFilterer} from "../OperatorFilterer.sol"; 8 | 9 | /** 10 | * @title ExampleERC1155 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleSoladyERC1155 is ERC1155, OperatorFilterer, Ownable, ERC2981 { 17 | bool public operatorFilteringEnabled; 18 | 19 | constructor() { 20 | // Solady's Ownable requires `_initializeOwner` to be called in 21 | // the constructor / initializer. 22 | _initializeOwner(msg.sender); 23 | _registerForOperatorFiltering(); 24 | operatorFilteringEnabled = true; 25 | 26 | // Set royalty receiver to the contract creator, 27 | // at 5% (default denominator is 10000). 28 | _setDefaultRoyalty(msg.sender, 500); 29 | } 30 | 31 | function uri(uint256) public view virtual override returns (string memory) { 32 | return ""; 33 | } 34 | 35 | function setApprovalForAll(address operator, bool approved) 36 | public 37 | override 38 | onlyAllowedOperatorApproval(operator) 39 | { 40 | super.setApprovalForAll(operator, approved); 41 | } 42 | 43 | function safeTransferFrom( 44 | address from, 45 | address to, 46 | uint256 tokenId, 47 | uint256 amount, 48 | bytes calldata data 49 | ) public override onlyAllowedOperator(from) { 50 | super.safeTransferFrom(from, to, tokenId, amount, data); 51 | } 52 | 53 | function safeBatchTransferFrom( 54 | address from, 55 | address to, 56 | uint256[] calldata ids, 57 | uint256[] calldata amounts, 58 | bytes calldata data 59 | ) public override onlyAllowedOperator(from) { 60 | super.safeBatchTransferFrom(from, to, ids, amounts, data); 61 | } 62 | 63 | function supportsInterface(bytes4 interfaceId) 64 | public 65 | view 66 | virtual 67 | override(ERC1155, ERC2981) 68 | returns (bool) 69 | { 70 | // Supports the following `interfaceId`s: 71 | // - IERC165: 0x01ffc9a7 72 | // - IERC1155: 0xd9b67a26 73 | // - IERC1155MetadataURI: 0x0e89341c 74 | // - IERC2981: 0x2a55205a 75 | return ERC1155.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 76 | } 77 | 78 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 79 | _setDefaultRoyalty(receiver, feeNumerator); 80 | } 81 | 82 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 83 | operatorFilteringEnabled = value; 84 | } 85 | 86 | function _operatorFilteringEnabled() internal view override returns (bool) { 87 | return operatorFilteringEnabled; 88 | } 89 | 90 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 91 | // OpenSea Seaport Conduit: 92 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 93 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 94 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/example/ExampleSoladyERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC721} from "solady/tokens/ERC721.sol"; 5 | import {ERC2981} from "solady/tokens/ERC2981.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {OperatorFilterer} from "../OperatorFilterer.sol"; 8 | 9 | /** 10 | * @title ExampleERC721 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleSoladyERC721 is ERC721, OperatorFilterer, Ownable, ERC2981 { 17 | bool public operatorFilteringEnabled; 18 | 19 | constructor() { 20 | // Solady's Ownable requires `_initializeOwner` to be called in 21 | // the constructor / initializer. 22 | _initializeOwner(msg.sender); 23 | _registerForOperatorFiltering(); 24 | operatorFilteringEnabled = true; 25 | 26 | // Set royalty receiver to the contract creator, 27 | // at 5% (default denominator is 10000). 28 | _setDefaultRoyalty(msg.sender, 500); 29 | } 30 | 31 | function name() public view virtual override returns (string memory) { 32 | return "Example"; 33 | } 34 | 35 | function symbol() public view virtual override returns (string memory) { 36 | return "EXAMPLE"; 37 | } 38 | 39 | function setApprovalForAll(address operator, bool approved) 40 | public 41 | override 42 | onlyAllowedOperatorApproval(operator) 43 | { 44 | super.setApprovalForAll(operator, approved); 45 | } 46 | 47 | function approve(address operator, uint256 tokenId) 48 | public 49 | payable 50 | override 51 | onlyAllowedOperatorApproval(operator) 52 | { 53 | super.approve(operator, tokenId); 54 | } 55 | 56 | /** 57 | * @dev Both safeTransferFrom functions in Solady's ERC721 call this function 58 | * so we don't need to override them. 59 | */ 60 | function transferFrom(address from, address to, uint256 tokenId) 61 | public 62 | payable 63 | override 64 | onlyAllowedOperator(from) 65 | { 66 | super.transferFrom(from, to, tokenId); 67 | } 68 | 69 | function tokenURI(uint256) public pure override returns (string memory) { 70 | return ""; 71 | } 72 | 73 | function supportsInterface(bytes4 interfaceId) 74 | public 75 | view 76 | virtual 77 | override(ERC721, ERC2981) 78 | returns (bool) 79 | { 80 | // Supports the following `interfaceId`s: 81 | // - IERC165: 0x01ffc9a7 82 | // - IERC721: 0x80ac58cd 83 | // - IERC721Metadata: 0x5b5e139f 84 | // - IERC2981: 0x2a55205a 85 | return ERC721.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 86 | } 87 | 88 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 89 | _setDefaultRoyalty(receiver, feeNumerator); 90 | } 91 | 92 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 93 | operatorFilteringEnabled = value; 94 | } 95 | 96 | function _operatorFilteringEnabled() internal view override returns (bool) { 97 | return operatorFilteringEnabled; 98 | } 99 | 100 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 101 | // OpenSea Seaport Conduit: 102 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 103 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 104 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/example/upgradeable/ExampleERC1155Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC1155Upgradeable} from 5 | "openzeppelin-contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; 6 | import {OperatorFilterer} from "../../OperatorFilterer.sol"; 7 | import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | import { 9 | IERC2981Upgradeable, 10 | ERC2981Upgradeable 11 | } from "openzeppelin-contracts-upgradeable/token/common/ERC2981Upgradeable.sol"; 12 | 13 | /** 14 | * @title ExampleERC1155Upgradeable 15 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 16 | * token and subscribes it to OpenSea's curated filters. 17 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 18 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 19 | */ 20 | abstract contract ExampleERC1155Upgradeable is 21 | ERC1155Upgradeable, 22 | OperatorFilterer, 23 | OwnableUpgradeable, 24 | ERC2981Upgradeable 25 | { 26 | bool public operatorFilteringEnabled; 27 | 28 | function initialize() public initializer { 29 | __ERC1155_init(""); 30 | __Ownable_init(); 31 | __ERC2981_init(); 32 | 33 | _registerForOperatorFiltering(); 34 | operatorFilteringEnabled = true; 35 | 36 | // Set royalty receiver to the contract creator, 37 | // at 5% (default denominator is 10000). 38 | _setDefaultRoyalty(msg.sender, 500); 39 | } 40 | 41 | function setApprovalForAll(address operator, bool approved) 42 | public 43 | override 44 | onlyAllowedOperatorApproval(operator) 45 | { 46 | super.setApprovalForAll(operator, approved); 47 | } 48 | 49 | function safeTransferFrom( 50 | address from, 51 | address to, 52 | uint256 tokenId, 53 | uint256 amount, 54 | bytes memory data 55 | ) public override onlyAllowedOperator(from) { 56 | super.safeTransferFrom(from, to, tokenId, amount, data); 57 | } 58 | 59 | function safeBatchTransferFrom( 60 | address from, 61 | address to, 62 | uint256[] memory ids, 63 | uint256[] memory amounts, 64 | bytes memory data 65 | ) public override onlyAllowedOperator(from) { 66 | super.safeBatchTransferFrom(from, to, ids, amounts, data); 67 | } 68 | 69 | function supportsInterface(bytes4 interfaceId) 70 | public 71 | view 72 | virtual 73 | override(ERC1155Upgradeable, ERC2981Upgradeable) 74 | returns (bool) 75 | { 76 | // Supports the following `interfaceId`s: 77 | // - IERC165: 0x01ffc9a7 78 | // - IERC1155: 0xd9b67a26 79 | // - IERC1155MetadataURI: 0x0e89341c 80 | // - IERC2981: 0x2a55205a 81 | return ERC1155Upgradeable.supportsInterface(interfaceId) 82 | || ERC2981Upgradeable.supportsInterface(interfaceId); 83 | } 84 | 85 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 86 | _setDefaultRoyalty(receiver, feeNumerator); 87 | } 88 | 89 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 90 | operatorFilteringEnabled = value; 91 | } 92 | 93 | function _operatorFilteringEnabled() internal view override returns (bool) { 94 | return operatorFilteringEnabled; 95 | } 96 | 97 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 98 | // OpenSea Seaport Conduit: 99 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 100 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 101 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/example/upgradeable/ExampleERC721AUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {IERC721AUpgradeable, ERC721AUpgradeable} from "erc721a-upgradeable/ERC721AUpgradeable.sol"; 5 | import {ERC721AQueryableUpgradeable} from 6 | "erc721a-upgradeable/extensions/ERC721AQueryableUpgradeable.sol"; 7 | import {ERC721ABurnableUpgradeable} from 8 | "erc721a-upgradeable/extensions/ERC721ABurnableUpgradeable.sol"; 9 | import {OperatorFilterer} from "../../OperatorFilterer.sol"; 10 | import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol"; 11 | import { 12 | IERC2981Upgradeable, 13 | ERC2981Upgradeable 14 | } from "openzeppelin-contracts-upgradeable/token/common/ERC2981Upgradeable.sol"; 15 | 16 | /** 17 | * @title ExampleERC721AUpgradeable 18 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 19 | * token and subscribes it to OpenSea's curated filters. 20 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 21 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 22 | */ 23 | abstract contract ExampleERC721AUpgradeable is 24 | ERC721AQueryableUpgradeable, 25 | ERC721ABurnableUpgradeable, 26 | OperatorFilterer, 27 | OwnableUpgradeable, 28 | ERC2981Upgradeable 29 | { 30 | bool public operatorFilteringEnabled; 31 | 32 | function initialize() public initializer initializerERC721A { 33 | __ERC721A_init("Example", "EXAMPLE"); 34 | __Ownable_init(); 35 | __ERC2981_init(); 36 | 37 | _registerForOperatorFiltering(); 38 | operatorFilteringEnabled = true; 39 | 40 | // Set royalty receiver to the contract creator, 41 | // at 5% (default denominator is 10000). 42 | _setDefaultRoyalty(msg.sender, 500); 43 | } 44 | 45 | function setApprovalForAll(address operator, bool approved) 46 | public 47 | override(IERC721AUpgradeable, ERC721AUpgradeable) 48 | onlyAllowedOperatorApproval(operator) 49 | { 50 | super.setApprovalForAll(operator, approved); 51 | } 52 | 53 | function approve(address operator, uint256 tokenId) 54 | public 55 | payable 56 | override(IERC721AUpgradeable, ERC721AUpgradeable) 57 | onlyAllowedOperatorApproval(operator) 58 | { 59 | super.approve(operator, tokenId); 60 | } 61 | 62 | function transferFrom(address from, address to, uint256 tokenId) 63 | public 64 | payable 65 | override(IERC721AUpgradeable, ERC721AUpgradeable) 66 | onlyAllowedOperator(from) 67 | { 68 | super.transferFrom(from, to, tokenId); 69 | } 70 | 71 | function safeTransferFrom(address from, address to, uint256 tokenId) 72 | public 73 | payable 74 | override(IERC721AUpgradeable, ERC721AUpgradeable) 75 | onlyAllowedOperator(from) 76 | { 77 | super.safeTransferFrom(from, to, tokenId); 78 | } 79 | 80 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) 81 | public 82 | payable 83 | override(IERC721AUpgradeable, ERC721AUpgradeable) 84 | onlyAllowedOperator(from) 85 | { 86 | super.safeTransferFrom(from, to, tokenId, data); 87 | } 88 | 89 | function supportsInterface(bytes4 interfaceId) 90 | public 91 | view 92 | virtual 93 | override(IERC721AUpgradeable, ERC721AUpgradeable, ERC2981Upgradeable) 94 | returns (bool) 95 | { 96 | // Supports the following `interfaceId`s: 97 | // - IERC165: 0x01ffc9a7 98 | // - IERC721: 0x80ac58cd 99 | // - IERC721Metadata: 0x5b5e139f 100 | // - IERC2981: 0x2a55205a 101 | return ERC721AUpgradeable.supportsInterface(interfaceId) 102 | || ERC2981Upgradeable.supportsInterface(interfaceId); 103 | } 104 | 105 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 106 | _setDefaultRoyalty(receiver, feeNumerator); 107 | } 108 | 109 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 110 | operatorFilteringEnabled = value; 111 | } 112 | 113 | function _operatorFilteringEnabled() internal view override returns (bool) { 114 | return operatorFilteringEnabled; 115 | } 116 | 117 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 118 | // OpenSea Seaport Conduit: 119 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 120 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 121 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/example/upgradeable/ExampleERC721Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC721Upgradeable} from 5 | "openzeppelin-contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; 6 | import {OperatorFilterer} from "../../OperatorFilterer.sol"; 7 | import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | import { 9 | IERC2981Upgradeable, 10 | ERC2981Upgradeable 11 | } from "openzeppelin-contracts-upgradeable/token/common/ERC2981Upgradeable.sol"; 12 | 13 | /** 14 | * @title ExampleERC721Upgradeable 15 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 16 | * token and subscribes it to OpenSea's curated filters. 17 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 18 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 19 | */ 20 | abstract contract ExampleERC721Upgradeable is 21 | ERC721Upgradeable, 22 | OperatorFilterer, 23 | OwnableUpgradeable, 24 | ERC2981Upgradeable 25 | { 26 | bool public operatorFilteringEnabled; 27 | 28 | function initialize() public initializer { 29 | __ERC721_init("Example", "EXAMPLE"); 30 | __Ownable_init(); 31 | __ERC2981_init(); 32 | 33 | _registerForOperatorFiltering(); 34 | operatorFilteringEnabled = true; 35 | 36 | // Set royalty receiver to the contract creator, 37 | // at 5% (default denominator is 10000). 38 | _setDefaultRoyalty(msg.sender, 500); 39 | } 40 | 41 | function setApprovalForAll(address operator, bool approved) 42 | public 43 | override 44 | onlyAllowedOperatorApproval(operator) 45 | { 46 | super.setApprovalForAll(operator, approved); 47 | } 48 | 49 | function approve(address operator, uint256 tokenId) 50 | public 51 | override 52 | onlyAllowedOperatorApproval(operator) 53 | { 54 | super.approve(operator, tokenId); 55 | } 56 | 57 | function transferFrom(address from, address to, uint256 tokenId) 58 | public 59 | override 60 | onlyAllowedOperator(from) 61 | { 62 | super.transferFrom(from, to, tokenId); 63 | } 64 | 65 | function safeTransferFrom(address from, address to, uint256 tokenId) 66 | public 67 | override 68 | onlyAllowedOperator(from) 69 | { 70 | super.safeTransferFrom(from, to, tokenId); 71 | } 72 | 73 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) 74 | public 75 | override 76 | onlyAllowedOperator(from) 77 | { 78 | super.safeTransferFrom(from, to, tokenId, data); 79 | } 80 | 81 | function tokenURI(uint256) public pure override returns (string memory) { 82 | return ""; 83 | } 84 | 85 | function supportsInterface(bytes4 interfaceId) 86 | public 87 | view 88 | virtual 89 | override(ERC721Upgradeable, ERC2981Upgradeable) 90 | returns (bool) 91 | { 92 | // Supports the following `interfaceId`s: 93 | // - IERC165: 0x01ffc9a7 94 | // - IERC721: 0x80ac58cd 95 | // - IERC721Metadata: 0x5b5e139f 96 | // - IERC2981: 0x2a55205a 97 | return ERC721Upgradeable.supportsInterface(interfaceId) 98 | || ERC2981Upgradeable.supportsInterface(interfaceId); 99 | } 100 | 101 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 102 | _setDefaultRoyalty(receiver, feeNumerator); 103 | } 104 | 105 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 106 | operatorFilteringEnabled = value; 107 | } 108 | 109 | function _operatorFilteringEnabled() internal view override returns (bool) { 110 | return operatorFilteringEnabled; 111 | } 112 | 113 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 114 | // OpenSea Seaport Conduit: 115 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 116 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 117 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/example/upgradeable/ExampleSoladyERC1155Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC1155} from "solady/tokens/ERC1155.sol"; 5 | import {ERC2981} from "solady/tokens/ERC2981.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {OperatorFilterer} from "../../OperatorFilterer.sol"; 8 | 9 | /** 10 | * @title ExampleERC1155Upgradeable 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleSoladyERC1155Upgradeable is ERC1155, OperatorFilterer, Ownable, ERC2981 { 17 | event Initialized(uint8 version); 18 | 19 | bool public operatorFilteringEnabled; 20 | bool public initialized; 21 | 22 | function initialize() public { 23 | require(!initialized, "Initializable: contract is already initialized"); 24 | initialized = true; 25 | emit Initialized(1); 26 | 27 | // Solady's Ownable requires `_initializeOwner` to be called in 28 | // the constructor / initializer. 29 | _initializeOwner(msg.sender); 30 | _registerForOperatorFiltering(); 31 | operatorFilteringEnabled = true; 32 | 33 | // Set royalty receiver to the contract creator, 34 | // at 5% (default denominator is 10000). 35 | _setDefaultRoyalty(msg.sender, 500); 36 | } 37 | 38 | function uri(uint256) public view virtual override returns (string memory) { 39 | return ""; 40 | } 41 | 42 | function setApprovalForAll(address operator, bool approved) 43 | public 44 | override 45 | onlyAllowedOperatorApproval(operator) 46 | { 47 | super.setApprovalForAll(operator, approved); 48 | } 49 | 50 | function safeTransferFrom( 51 | address from, 52 | address to, 53 | uint256 tokenId, 54 | uint256 amount, 55 | bytes calldata data 56 | ) public override onlyAllowedOperator(from) { 57 | super.safeTransferFrom(from, to, tokenId, amount, data); 58 | } 59 | 60 | function safeBatchTransferFrom( 61 | address from, 62 | address to, 63 | uint256[] calldata ids, 64 | uint256[] calldata amounts, 65 | bytes calldata data 66 | ) public override onlyAllowedOperator(from) { 67 | super.safeBatchTransferFrom(from, to, ids, amounts, data); 68 | } 69 | 70 | function supportsInterface(bytes4 interfaceId) 71 | public 72 | view 73 | virtual 74 | override(ERC1155, ERC2981) 75 | returns (bool) 76 | { 77 | // Supports the following `interfaceId`s: 78 | // - IERC165: 0x01ffc9a7 79 | // - IERC1155: 0xd9b67a26 80 | // - IERC1155MetadataURI: 0x0e89341c 81 | // - IERC2981: 0x2a55205a 82 | return ERC1155.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 83 | } 84 | 85 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 86 | _setDefaultRoyalty(receiver, feeNumerator); 87 | } 88 | 89 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 90 | operatorFilteringEnabled = value; 91 | } 92 | 93 | function _operatorFilteringEnabled() internal view override returns (bool) { 94 | return operatorFilteringEnabled; 95 | } 96 | 97 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 98 | // OpenSea Seaport Conduit: 99 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 100 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 101 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/example/upgradeable/ExampleSoladyERC721Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ERC721} from "solady/tokens/ERC721.sol"; 5 | import {ERC2981} from "solady/tokens/ERC2981.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {OperatorFilterer} from "../../OperatorFilterer.sol"; 8 | 9 | /** 10 | * @title ExampleERC721Upgradeable 11 | * @notice This example contract is configured to use the DefaultOperatorFilterer, which automatically registers the 12 | * token and subscribes it to OpenSea's curated filters. 13 | * Adding the onlyAllowedOperator modifier to the transferFrom and both safeTransferFrom methods ensures that 14 | * the msg.sender (operator) is allowed by the OperatorFilterRegistry. 15 | */ 16 | abstract contract ExampleSoladyERC721Upgradeable is ERC721, OperatorFilterer, Ownable, ERC2981 { 17 | event Initialized(uint8 version); 18 | 19 | bool public operatorFilteringEnabled; 20 | bool public initialized; 21 | 22 | function initialize() public { 23 | require(!initialized, "Initializable: contract is already initialized"); 24 | initialized = true; 25 | emit Initialized(1); 26 | 27 | // Solady's Ownable requires `_initializeOwner` to be called in 28 | // the constructor / initializer. 29 | _initializeOwner(msg.sender); 30 | _registerForOperatorFiltering(); 31 | operatorFilteringEnabled = true; 32 | 33 | // Set royalty receiver to the contract creator, 34 | // at 5% (default denominator is 10000). 35 | _setDefaultRoyalty(msg.sender, 500); 36 | } 37 | 38 | function name() public view virtual override returns (string memory) { 39 | return "Example"; 40 | } 41 | 42 | function symbol() public view virtual override returns (string memory) { 43 | return "EXAMPLE"; 44 | } 45 | 46 | function setApprovalForAll(address operator, bool approved) 47 | public 48 | override 49 | onlyAllowedOperatorApproval(operator) 50 | { 51 | super.setApprovalForAll(operator, approved); 52 | } 53 | 54 | function approve(address operator, uint256 tokenId) 55 | public 56 | payable 57 | override 58 | onlyAllowedOperatorApproval(operator) 59 | { 60 | super.approve(operator, tokenId); 61 | } 62 | 63 | /** 64 | * @dev Both safeTransferFrom functions in Solady's ERC721 call this function 65 | * so we don't need to override them. 66 | */ 67 | function transferFrom(address from, address to, uint256 tokenId) 68 | public 69 | payable 70 | override 71 | onlyAllowedOperator(from) 72 | { 73 | super.transferFrom(from, to, tokenId); 74 | } 75 | 76 | function tokenURI(uint256) public pure override returns (string memory) { 77 | return ""; 78 | } 79 | 80 | function supportsInterface(bytes4 interfaceId) 81 | public 82 | view 83 | virtual 84 | override(ERC721, ERC2981) 85 | returns (bool) 86 | { 87 | // Supports the following `interfaceId`s: 88 | // - IERC165: 0x01ffc9a7 89 | // - IERC721: 0x80ac58cd 90 | // - IERC721Metadata: 0x5b5e139f 91 | // - IERC2981: 0x2a55205a 92 | return ERC721.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); 93 | } 94 | 95 | function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { 96 | _setDefaultRoyalty(receiver, feeNumerator); 97 | } 98 | 99 | function setOperatorFilteringEnabled(bool value) public onlyOwner { 100 | operatorFilteringEnabled = value; 101 | } 102 | 103 | function _operatorFilteringEnabled() internal view override returns (bool) { 104 | return operatorFilteringEnabled; 105 | } 106 | 107 | function _isPriorityOperator(address operator) internal pure override returns (bool) { 108 | // OpenSea Seaport Conduit: 109 | // https://etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 110 | // https://goerli.etherscan.io/address/0x1E0049783F008A0085193E00003D00cd54003c71 111 | return operator == address(0x1E0049783F008A0085193E00003D00cd54003c71); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/BaseRegistryTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import { 6 | OperatorFilterRegistry, 7 | OperatorFilterRegistryErrorsAndEvents 8 | } from "operator-filter-registry/OperatorFilterRegistry.sol"; 9 | 10 | contract BaseRegistryTest is Test, OperatorFilterRegistryErrorsAndEvents { 11 | OperatorFilterRegistry constant registry = 12 | OperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E); 13 | 14 | address constant DEFAULT_SUBSCRIPTION = address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6); 15 | 16 | function setUp() public virtual { 17 | address deployedRegistry = address(new OperatorFilterRegistry()); 18 | vm.etch(address(registry), deployedRegistry.code); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/DefaultOperatorFilterer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../src/OperatorFilterer.sol"; 5 | import {BaseRegistryTest} from "./BaseRegistryTest.sol"; 6 | import {DefaultFilterer} from "./helpers/DefaultFilterer.sol"; 7 | 8 | contract DefaultOperatorFiltererTest is BaseRegistryTest { 9 | DefaultFilterer filterer; 10 | address filteredAddress; 11 | address filteredCodeHashAddress; 12 | bytes32 filteredCodeHash; 13 | address notFiltered; 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | notFiltered = makeAddr("not filtered"); 18 | vm.startPrank(DEFAULT_SUBSCRIPTION); 19 | registry.register(DEFAULT_SUBSCRIPTION); 20 | 21 | filteredAddress = makeAddr("filtered address"); 22 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 23 | filteredCodeHashAddress = makeAddr("filtered code hash"); 24 | bytes memory code = hex"deadbeef"; 25 | filteredCodeHash = keccak256(code); 26 | registry.updateCodeHash(address(DEFAULT_SUBSCRIPTION), filteredCodeHash, true); 27 | vm.etch(filteredCodeHashAddress, code); 28 | 29 | filterer = new DefaultFilterer(); 30 | vm.stopPrank(); 31 | } 32 | 33 | function testFilter() public { 34 | assertTrue(filterer.filter(notFiltered)); 35 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 36 | vm.prank(filteredAddress); 37 | filterer.filter(notFiltered); 38 | vm.expectRevert( 39 | abi.encodeWithSelector( 40 | CodeHashFiltered.selector, filteredCodeHashAddress, filteredCodeHash 41 | ) 42 | ); 43 | vm.prank(filteredCodeHashAddress); 44 | filterer.filter(notFiltered); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/OperatorFilterRegistry.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {BaseRegistryTest} from "./BaseRegistryTest.sol"; 5 | import {OperatorFilterer} from "../src/OperatorFilterer.sol"; 6 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 7 | 8 | contract Filterer is OperatorFilterer, Ownable { 9 | constructor(address) { 10 | _registerForOperatorFiltering(address(0), false); 11 | } 12 | 13 | function testFilter(address from) public view onlyAllowedOperator(from) returns (bool) { 14 | return true; 15 | } 16 | } 17 | 18 | contract Owned is Ownable {} 19 | 20 | contract OwnableReverter { 21 | error Bad(); 22 | 23 | function owner() public pure returns (address) { 24 | revert Bad(); 25 | } 26 | } 27 | 28 | contract OperatorFilterRegistryTest is BaseRegistryTest { 29 | Filterer filterer; 30 | Ownable owned; 31 | OwnableReverter reverter; 32 | 33 | function setUp() public override { 34 | super.setUp(); 35 | filterer = new Filterer(address(registry)); 36 | owned = new Owned(); 37 | reverter = new OwnableReverter(); 38 | } 39 | 40 | function testOnlyAddressOrOwner() public { 41 | vm.startPrank(makeAddr("not owner")); 42 | vm.expectRevert(abi.encodeWithSelector(NotOwnable.selector)); 43 | registry.register(address(this)); 44 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 45 | registry.register(address(owned)); 46 | vm.expectRevert(abi.encodeWithSelector(OwnableReverter.Bad.selector)); 47 | registry.register(address(reverter)); 48 | } 49 | 50 | function testRegister_constructor() public { 51 | vm.expectEmit(true, false, false, false, address(registry)); 52 | emit RegistrationUpdated(address(this), true); 53 | registry.register(address(this)); 54 | 55 | assertTrue(registry.isRegistered(address(filterer))); 56 | assertEq(registry.subscriptionOf(address(filterer)), address(0)); 57 | } 58 | 59 | function testRegister_onlyAddressOrOwner() public { 60 | vm.startPrank(makeAddr("not owner")); 61 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 62 | registry.register(address(filterer)); 63 | } 64 | 65 | function testRegister_alreadyRegistered() public { 66 | registry.register(address(this)); 67 | vm.expectRevert(abi.encodeWithSelector(AlreadyRegistered.selector)); 68 | registry.register(address(this)); 69 | } 70 | 71 | function testRegisterAndSubscribe() public { 72 | address subscription = makeAddr("subscription"); 73 | vm.prank(subscription); 74 | registry.register(subscription); 75 | vm.expectEmit(true, false, false, false, address(registry)); 76 | emit RegistrationUpdated(address(this), true); 77 | vm.expectEmit(true, true, true, false, address(registry)); 78 | emit SubscriptionUpdated(address(this), subscription, true); 79 | registry.registerAndSubscribe(address(this), subscription); 80 | assertEq(registry.subscribers(subscription).length, 1); 81 | assertEq(registry.subscribers(subscription)[0], address(this)); 82 | assertEq(registry.subscriberAt(subscription, 0), address(this)); 83 | } 84 | 85 | function testRegisterAndSubscribe_OnlyAddressOrOwner() public { 86 | address subscription = makeAddr("subscription"); 87 | vm.startPrank(makeAddr("not owner")); 88 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 89 | registry.registerAndSubscribe(address(owned), subscription); 90 | } 91 | 92 | function testRegisterAndSubscribe_AlreadyRegistered() public { 93 | registry.register(address(this)); 94 | vm.expectRevert(abi.encodeWithSelector(AlreadyRegistered.selector)); 95 | registry.registerAndSubscribe(address(this), makeAddr("subscription")); 96 | } 97 | 98 | function testRegisterAndSubscribe_CannotRegisterToSelf() public { 99 | vm.expectRevert(abi.encodeWithSelector(CannotSubscribeToSelf.selector)); 100 | registry.registerAndSubscribe(address(this), address(this)); 101 | } 102 | 103 | function testRegisterAndSubscribe_NotRegistered() public { 104 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, makeAddr("subscription"))); 105 | registry.registerAndSubscribe(address(this), makeAddr("subscription")); 106 | } 107 | 108 | function testRegisterAndSubscribe_CannotSubscribeToRegistrantWithSubscription() public { 109 | address subscription = makeAddr("subscription"); 110 | address superSubscription = makeAddr("superSubscription"); 111 | vm.prank(superSubscription); 112 | registry.register(superSubscription); 113 | vm.prank(subscription); 114 | registry.registerAndSubscribe(subscription, superSubscription); 115 | vm.expectRevert( 116 | abi.encodeWithSelector( 117 | CannotSubscribeToRegistrantWithSubscription.selector, subscription 118 | ) 119 | ); 120 | registry.registerAndSubscribe(address(this), subscription); 121 | } 122 | 123 | function testRegisterAndCopyEntries() public { 124 | registry.register(address(this)); 125 | registry.updateOperator(address(this), makeAddr("operator"), true); 126 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 127 | 128 | vm.expectEmit(true, true, true, false, address(registry)); 129 | emit OperatorUpdated(address(filterer), makeAddr("operator"), true); 130 | vm.expectEmit(true, true, true, false, address(registry)); 131 | emit CodeHashUpdated(address(filterer), bytes32(bytes4(0xdeadbeef)), true); 132 | 133 | registry.copyEntriesOf(address(filterer), address(this)); 134 | 135 | assertEq(registry.subscribers(address(this)).length, 0); 136 | assertTrue(registry.isRegistered(address(filterer))); 137 | assertEq(registry.filteredOperatorAt(address(filterer), 0), makeAddr("operator")); 138 | assertEq(registry.filteredCodeHashAt(address(filterer), 0), bytes32(bytes4(0xdeadbeef))); 139 | } 140 | 141 | function testRegisterAndCopyEntries_OnlyAddressOrOwner() public { 142 | vm.startPrank(makeAddr("not owner")); 143 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 144 | registry.registerAndCopyEntries(address(filterer), address(this)); 145 | } 146 | 147 | function testRegisterAndCopyEntries_CannotCopyFromSelf() public { 148 | registry.register(address(this)); 149 | vm.expectRevert(abi.encodeWithSelector(CannotCopyFromSelf.selector)); 150 | registry.registerAndCopyEntries(address(this), address(this)); 151 | } 152 | 153 | function testRegisterAndCopyEntries_AlreadyRegistered() public { 154 | registry.register(address(this)); 155 | vm.expectRevert(abi.encodeWithSelector(AlreadyRegistered.selector)); 156 | registry.registerAndCopyEntries(address(this), makeAddr("not registered but fail fast")); 157 | } 158 | 159 | function testRegisterAndCopyEntries_NotRegistered() public { 160 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, makeAddr("registrant"))); 161 | registry.registerAndCopyEntries(address(this), makeAddr("registrant")); 162 | } 163 | 164 | function testUpdateOperator() public { 165 | registry.register(address(this)); 166 | vm.expectEmit(true, true, true, false, address(registry)); 167 | emit OperatorUpdated(address(this), makeAddr("operator"), true); 168 | registry.updateOperator(address(this), makeAddr("operator"), true); 169 | assertTrue(registry.isOperatorFiltered(address(this), makeAddr("operator"))); 170 | assertEq(registry.filteredOperatorAt(address(this), 0), makeAddr("operator")); 171 | } 172 | 173 | function testUpdateOperator_OnlyAddressOrOwner() public { 174 | vm.startPrank(makeAddr("not owner")); 175 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 176 | registry.updateOperator(address(owned), makeAddr("operator"), true); 177 | } 178 | 179 | function testUpdateOperator_notRegistered() public { 180 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 181 | registry.updateOperator(address(this), makeAddr("operator"), true); 182 | } 183 | 184 | function testUpdateOperator_CannotUpdateWhileSubscribed() public { 185 | address subscription = makeAddr("subscription"); 186 | vm.prank(subscription); 187 | registry.register(subscription); 188 | registry.registerAndSubscribe(address(this), subscription); 189 | vm.expectRevert(abi.encodeWithSelector(CannotUpdateWhileSubscribed.selector, subscription)); 190 | registry.updateOperator(address(this), makeAddr("operator"), true); 191 | } 192 | 193 | function testUpdateOperator_unfilter() public { 194 | registry.register(address(this)); 195 | registry.updateOperator(address(this), makeAddr("operator"), true); 196 | vm.expectEmit(true, true, true, false, address(registry)); 197 | emit OperatorUpdated(address(this), makeAddr("operator"), false); 198 | registry.updateOperator(address(this), makeAddr("operator"), false); 199 | assertFalse(registry.isOperatorFiltered(address(this), makeAddr("operator"))); 200 | vm.expectRevert(); 201 | registry.filteredOperatorAt(address(this), 0); 202 | } 203 | 204 | function testUpdateOperator_AddressNotFiltered() public { 205 | registry.register(address(this)); 206 | vm.expectRevert(abi.encodeWithSelector(AddressNotFiltered.selector, makeAddr("operator"))); 207 | registry.updateOperator(address(this), makeAddr("operator"), false); 208 | } 209 | 210 | function testUpdateOperator_AddressAlreadyFiltered() public { 211 | registry.register(address(this)); 212 | registry.updateOperator(address(this), makeAddr("operator"), true); 213 | vm.expectRevert( 214 | abi.encodeWithSelector(AddressAlreadyFiltered.selector, makeAddr("operator")) 215 | ); 216 | registry.updateOperator(address(this), makeAddr("operator"), true); 217 | } 218 | 219 | function testUpdateCodeHash() public { 220 | registry.register(address(this)); 221 | vm.expectEmit(true, true, true, false, address(registry)); 222 | emit CodeHashUpdated(address(this), bytes32(bytes4(0xdeadbeef)), true); 223 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 224 | assertTrue(registry.isCodeHashFiltered(address(this), bytes32(bytes4(0xdeadbeef)))); 225 | assertEq(registry.filteredCodeHashAt(address(this), 0), bytes32(bytes4(0xdeadbeef))); 226 | } 227 | 228 | function testUpdateCodeHash_OnlyAddressOrOwner() public { 229 | vm.startPrank(makeAddr("not owner")); 230 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 231 | registry.updateCodeHash(address(owned), bytes32(bytes4(0xdeadbeef)), true); 232 | } 233 | 234 | function testUpdateCodeHash_CannotFilterEOAs() public { 235 | registry.register(address(this)); 236 | vm.expectRevert(abi.encodeWithSelector(CannotFilterEOAs.selector)); 237 | registry.updateCodeHash(address(this), keccak256(""), true); 238 | } 239 | 240 | function testUpdateCodeHash_NotRegistered() public { 241 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 242 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 243 | } 244 | 245 | function testUpdateCodeHash_CannotUpdateWhileSubscribed() public { 246 | address subscription = makeAddr("subscription"); 247 | vm.prank(subscription); 248 | registry.register(subscription); 249 | registry.registerAndSubscribe(address(this), subscription); 250 | vm.expectRevert(abi.encodeWithSelector(CannotUpdateWhileSubscribed.selector, subscription)); 251 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 252 | } 253 | 254 | function testUpdateCodeHash_unfilter() public { 255 | registry.register(address(this)); 256 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 257 | vm.expectEmit(true, true, true, false, address(registry)); 258 | emit CodeHashUpdated(address(this), bytes32(bytes4(0xdeadbeef)), false); 259 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), false); 260 | assertFalse(registry.isCodeHashFiltered(address(this), bytes32(bytes4(0xdeadbeef)))); 261 | vm.expectRevert(); 262 | registry.filteredCodeHashAt(address(this), 0); 263 | } 264 | 265 | function testUpdateCodeHash_CodeHashNotFiltered() public { 266 | registry.register(address(this)); 267 | vm.expectRevert( 268 | abi.encodeWithSelector(CodeHashNotFiltered.selector, bytes32(bytes4(0xdeadbeef))) 269 | ); 270 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), false); 271 | } 272 | 273 | function testUpdateCodeHash_CodeHashAlreadyFiltered() public { 274 | registry.register(address(this)); 275 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 276 | vm.expectRevert( 277 | abi.encodeWithSelector(CodeHashAlreadyFiltered.selector, bytes32(bytes4(0xdeadbeef))) 278 | ); 279 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 280 | } 281 | 282 | function testUpdateOperators() public { 283 | registry.register(address(this)); 284 | 285 | address[] memory operator = new address[](2); 286 | operator[0] = makeAddr("operator1"); 287 | operator[1] = makeAddr("operator2"); 288 | vm.expectEmit(true, true, false, false, address(registry)); 289 | emit OperatorsUpdated(address(this), operator, true); 290 | registry.updateOperators(address(this), operator, true); 291 | assertTrue(registry.isOperatorFiltered(address(this), operator[0])); 292 | assertTrue(registry.isOperatorFiltered(address(this), operator[1])); 293 | assertEq(registry.filteredOperatorAt(address(this), 0), operator[0]); 294 | assertEq(registry.filteredOperatorAt(address(this), 1), operator[1]); 295 | } 296 | 297 | function testUpdateOperators_OnlyAddressOrOwner() public { 298 | vm.startPrank(makeAddr("not owner")); 299 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 300 | address[] memory operator = new address[](1); 301 | operator[0] = makeAddr("operator1"); 302 | registry.updateOperators(address(owned), operator, true); 303 | } 304 | 305 | function testUpdateOperators_notRegistered() public { 306 | address[] memory operator = new address[](2); 307 | operator[0] = makeAddr("operator1"); 308 | operator[1] = makeAddr("operator2"); 309 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 310 | registry.updateOperators(address(this), operator, true); 311 | } 312 | 313 | function testUpdateOperators_CannotUpdateWhileSubscribed() public { 314 | address subscription = makeAddr("subscription"); 315 | vm.prank(subscription); 316 | registry.register(subscription); 317 | registry.registerAndSubscribe(address(this), subscription); 318 | address[] memory operator = new address[](2); 319 | operator[0] = makeAddr("operator1"); 320 | operator[1] = makeAddr("operator2"); 321 | vm.expectRevert(abi.encodeWithSelector(CannotUpdateWhileSubscribed.selector, subscription)); 322 | registry.updateOperators(address(this), operator, true); 323 | } 324 | 325 | function testUpdateOperators_unfilter() public { 326 | registry.register(address(this)); 327 | registry.updateOperator(address(this), makeAddr("operator1"), true); 328 | registry.updateOperator(address(this), makeAddr("operator2"), true); 329 | 330 | address[] memory operator = new address[](2); 331 | operator[0] = makeAddr("operator1"); 332 | operator[1] = makeAddr("operator2"); 333 | vm.expectEmit(true, true, true, false, address(registry)); 334 | emit OperatorsUpdated(address(this), operator, false); 335 | registry.updateOperators(address(this), operator, false); 336 | assertFalse(registry.isOperatorFiltered(address(this), operator[0])); 337 | assertFalse(registry.isOperatorFiltered(address(this), operator[1])); 338 | vm.expectRevert(); 339 | registry.filteredOperatorAt(address(this), 0); 340 | } 341 | 342 | function testUpdateOperators_AddressNotFiltered() public { 343 | registry.register(address(this)); 344 | address[] memory operator = new address[](2); 345 | operator[0] = makeAddr("operator1"); 346 | operator[1] = makeAddr("operator2"); 347 | vm.expectRevert(abi.encodeWithSelector(AddressNotFiltered.selector, makeAddr("operator1"))); 348 | registry.updateOperators(address(this), operator, false); 349 | } 350 | 351 | function testUpdateOperators_AddressAlreadyFiltered() public { 352 | registry.register(address(this)); 353 | registry.updateOperator(address(this), makeAddr("operator1"), true); 354 | registry.updateOperator(address(this), makeAddr("operator2"), true); 355 | address[] memory operator = new address[](2); 356 | operator[0] = makeAddr("operator1"); 357 | operator[1] = makeAddr("operator2"); 358 | vm.expectRevert( 359 | abi.encodeWithSelector(AddressAlreadyFiltered.selector, makeAddr("operator1")) 360 | ); 361 | registry.updateOperators(address(this), operator, true); 362 | } 363 | 364 | function testUpdateCodeHashes() public { 365 | registry.register(address(this)); 366 | bytes32[] memory codeHash = new bytes32[](2); 367 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 368 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 369 | vm.expectEmit(true, true, false, false, address(registry)); 370 | emit CodeHashesUpdated(address(this), codeHash, true); 371 | registry.updateCodeHashes(address(this), codeHash, true); 372 | } 373 | 374 | function testUpdateCodeHashes_OnlyAddressOrOwner() public { 375 | vm.startPrank(makeAddr("not owner")); 376 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 377 | bytes32[] memory codeHash = new bytes32[](1); 378 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 379 | registry.updateCodeHashes(address(owned), codeHash, true); 380 | } 381 | 382 | function testUpdateCodeHashes_notRegistered() public { 383 | bytes32[] memory codeHash = new bytes32[](2); 384 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 385 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 386 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 387 | registry.updateCodeHashes(address(this), codeHash, true); 388 | } 389 | 390 | function testUpdateCodeHashes_CannotUpdateWhileSubscribed() public { 391 | address subscription = makeAddr("subscription"); 392 | vm.prank(subscription); 393 | registry.register(subscription); 394 | registry.registerAndSubscribe(address(this), subscription); 395 | bytes32[] memory codeHash = new bytes32[](2); 396 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 397 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 398 | vm.expectRevert(abi.encodeWithSelector(CannotUpdateWhileSubscribed.selector, subscription)); 399 | registry.updateCodeHashes(address(this), codeHash, true); 400 | } 401 | 402 | function testUpdateCodeHashes_unfilter() public { 403 | registry.register(address(this)); 404 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 405 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeafbeef)), true); 406 | 407 | bytes32[] memory codeHash = new bytes32[](2); 408 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 409 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 410 | vm.expectEmit(true, true, false, false, address(registry)); 411 | emit CodeHashesUpdated(address(this), codeHash, false); 412 | registry.updateCodeHashes(address(this), codeHash, false); 413 | assertFalse(registry.isCodeHashFiltered(address(this), codeHash[0])); 414 | assertFalse(registry.isCodeHashFiltered(address(this), codeHash[1])); 415 | vm.expectRevert(); 416 | registry.filteredCodeHashAt(address(this), 0); 417 | } 418 | 419 | function testUpdateCodeHashes_CodeHashNotFiltered() public { 420 | registry.register(address(this)); 421 | bytes32[] memory codeHash = new bytes32[](2); 422 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 423 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 424 | vm.expectRevert( 425 | abi.encodeWithSelector(CodeHashNotFiltered.selector, bytes32(bytes4(0xdeadbeef))) 426 | ); 427 | registry.updateCodeHashes(address(this), codeHash, false); 428 | } 429 | 430 | function testUpdateCodeHashes_CannotFilterEOAs() public { 431 | registry.register(address(this)); 432 | bytes32[] memory codeHash = new bytes32[](2); 433 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 434 | codeHash[1] = keccak256(""); 435 | vm.expectRevert(CannotFilterEOAs.selector); 436 | registry.updateCodeHashes(address(this), codeHash, true); 437 | } 438 | 439 | function testUpdateCodeHashes_CodeHashAlreadyFiltered() public { 440 | registry.register(address(this)); 441 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeadbeef)), true); 442 | registry.updateCodeHash(address(this), bytes32(bytes4(0xdeafbeef)), true); 443 | bytes32[] memory codeHash = new bytes32[](2); 444 | codeHash[0] = bytes32(bytes4(0xdeadbeef)); 445 | codeHash[1] = bytes32(bytes4(0xdeafbeef)); 446 | vm.expectRevert( 447 | abi.encodeWithSelector(CodeHashAlreadyFiltered.selector, bytes32(bytes4(0xdeadbeef))) 448 | ); 449 | registry.updateCodeHashes(address(this), codeHash, true); 450 | } 451 | 452 | function testSubscribe() public { 453 | address subscription = makeAddr("subscription"); 454 | vm.prank(subscription); 455 | registry.register(subscription); 456 | registry.register(address(this)); 457 | vm.expectEmit(true, true, true, false, address(registry)); 458 | emit SubscriptionUpdated(address(this), subscription, true); 459 | registry.subscribe(address(this), subscription); 460 | 461 | assertEq(registry.subscriptionOf(address(this)), subscription); 462 | assertEq(registry.subscribers(subscription).length, 1); 463 | assertEq(registry.subscribers(subscription)[0], address(this)); 464 | assertEq(registry.subscriberAt(subscription, 0), address(this)); 465 | } 466 | 467 | function testSubscribe_OnlyAddressOrOwner() public { 468 | address subscription = makeAddr("subscription"); 469 | vm.prank(subscription); 470 | registry.register(subscription); 471 | vm.startPrank(makeAddr("not owner")); 472 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 473 | registry.subscribe(address(owned), subscription); 474 | } 475 | 476 | function testSubscribe_CannotSubscribeToSelf() public { 477 | registry.register(address(this)); 478 | vm.expectRevert(abi.encodeWithSelector(CannotSubscribeToSelf.selector)); 479 | registry.subscribe(address(this), address(this)); 480 | } 481 | 482 | function testSubscribe_CannotSubscribeToZeroAddress() public { 483 | registry.register(address(this)); 484 | vm.expectRevert(abi.encodeWithSelector(CannotSubscribeToZeroAddress.selector)); 485 | registry.subscribe(address(this), address(0)); 486 | } 487 | 488 | function testSubscribe_notRegistered() public { 489 | address subscription = makeAddr("subscription"); 490 | vm.prank(subscription); 491 | registry.register(subscription); 492 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 493 | registry.subscribe(address(this), subscription); 494 | } 495 | 496 | function testSubscribe_AlreadySubscribed() public { 497 | address subscription = makeAddr("subscription"); 498 | vm.prank(subscription); 499 | registry.register(subscription); 500 | registry.register(address(this)); 501 | registry.subscribe(address(this), subscription); 502 | vm.expectRevert(abi.encodeWithSelector(AlreadySubscribed.selector, subscription)); 503 | registry.subscribe(address(this), subscription); 504 | } 505 | 506 | function testSubscribe_SubscriptionNotRegistered() public { 507 | registry.register(address(this)); 508 | address subscription = makeAddr("subscription"); 509 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, subscription)); 510 | registry.subscribe(address(this), subscription); 511 | } 512 | 513 | function testSubscribe_removeOldSubscription() public { 514 | address oldSubscription = makeAddr("oldSubscription"); 515 | vm.prank(oldSubscription); 516 | registry.register(oldSubscription); 517 | registry.register(address(this)); 518 | registry.subscribe(address(this), oldSubscription); 519 | 520 | address newSubscription = makeAddr("newSubscription"); 521 | vm.prank(newSubscription); 522 | registry.register(newSubscription); 523 | vm.expectEmit(true, true, true, false, address(registry)); 524 | emit SubscriptionUpdated(address(this), oldSubscription, false); 525 | vm.expectEmit(true, true, true, false, address(registry)); 526 | emit SubscriptionUpdated(address(this), newSubscription, true); 527 | registry.subscribe(address(this), newSubscription); 528 | 529 | assertEq(registry.subscriptionOf(address(this)), newSubscription); 530 | assertEq(registry.subscribers(oldSubscription).length, 0); 531 | assertEq(registry.subscribers(newSubscription).length, 1); 532 | assertEq(registry.subscribers(newSubscription)[0], address(this)); 533 | assertEq(registry.subscriberAt(newSubscription, 0), address(this)); 534 | } 535 | 536 | function testSubscribe_CannotSubscribeToRegistrantWithSubscription() public { 537 | address subscription = makeAddr("subscription"); 538 | address superSubscription = makeAddr("superSubscription"); 539 | vm.prank(superSubscription); 540 | registry.register(superSubscription); 541 | vm.prank(subscription); 542 | registry.registerAndSubscribe(subscription, superSubscription); 543 | registry.register(address(this)); 544 | vm.expectRevert( 545 | abi.encodeWithSelector( 546 | CannotSubscribeToRegistrantWithSubscription.selector, subscription 547 | ) 548 | ); 549 | registry.subscribe(address(this), subscription); 550 | } 551 | 552 | function testUnsubscribe() public { 553 | address subscription = makeAddr("subscription"); 554 | vm.prank(subscription); 555 | registry.register(subscription); 556 | registry.register(address(this)); 557 | registry.subscribe(address(this), subscription); 558 | 559 | vm.expectEmit(true, true, true, false, address(registry)); 560 | emit SubscriptionUpdated(address(this), subscription, false); 561 | registry.unsubscribe(address(this), false); 562 | 563 | assertEq(registry.subscriptionOf(address(this)), address(0)); 564 | assertEq(registry.subscribers(subscription).length, 0); 565 | assertEq(registry.subscriptionOf(address(this)), address(0)); 566 | } 567 | 568 | function testUnsubscribe_notRegistered() public { 569 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 570 | registry.unregister(address(this)); 571 | } 572 | 573 | function testUnsubscribe_onlyAddressOrOwner() public { 574 | address subscription = makeAddr("subscription"); 575 | vm.prank(subscription); 576 | registry.register(subscription); 577 | registry.register(address(this)); 578 | registry.subscribe(address(this), subscription); 579 | 580 | vm.startPrank(makeAddr("not owner")); 581 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 582 | registry.unsubscribe(address(owned), false); 583 | } 584 | 585 | function testUnsubscribe_copyExistingEntries() public { 586 | address subscription = makeAddr("subscription"); 587 | address operator = makeAddr("operator"); 588 | bytes32 codeHash = bytes32(bytes4(0xdeadbeef)); 589 | vm.startPrank(subscription); 590 | registry.register(subscription); 591 | registry.updateOperator(subscription, operator, true); 592 | registry.updateCodeHash(subscription, codeHash, true); 593 | vm.stopPrank(); 594 | registry.register(address(this)); 595 | registry.subscribe(address(this), subscription); 596 | 597 | vm.expectEmit(true, true, true, false, address(registry)); 598 | emit SubscriptionUpdated(address(this), subscription, false); 599 | vm.expectEmit(true, true, true, false, address(registry)); 600 | emit OperatorUpdated(address(this), operator, true); 601 | vm.expectEmit(true, true, true, false, address(registry)); 602 | emit CodeHashUpdated(address(this), codeHash, true); 603 | registry.unsubscribe(address(this), true); 604 | 605 | assertEq(registry.subscriptionOf(address(this)), address(0)); 606 | } 607 | 608 | function testUnsubscribe_NotRegistered() public { 609 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 610 | registry.unsubscribe(address(this), false); 611 | } 612 | 613 | function testUnsubscribe_NotSubscribed() public { 614 | registry.register(address(this)); 615 | vm.expectRevert(abi.encodeWithSelector(NotSubscribed.selector)); 616 | registry.unsubscribe(address(this), false); 617 | } 618 | 619 | function testCopyEntriesOf() public { 620 | address subscription = makeAddr("subscription"); 621 | address operator = makeAddr("operator"); 622 | address duplicateOperator = makeAddr("duplicateOperator"); 623 | bytes32 codeHash = bytes32(bytes4(0xdeadbeef)); 624 | bytes32 duplicateCodeHash = bytes32(bytes5(0xdeadbeef22)); 625 | vm.startPrank(subscription); 626 | registry.register(subscription); 627 | registry.updateOperator(subscription, operator, true); 628 | registry.updateOperator(subscription, duplicateOperator, true); 629 | registry.updateCodeHash(subscription, codeHash, true); 630 | registry.updateCodeHash(subscription, duplicateCodeHash, true); 631 | vm.stopPrank(); 632 | // test that it does not throw errors for duplicate entries 633 | // and that events are not emitted for them 634 | registry.register(address(this)); 635 | registry.updateOperator(address(this), duplicateOperator, true); 636 | registry.updateCodeHash(address(this), duplicateCodeHash, true); 637 | 638 | vm.expectEmit(true, true, true, false, address(registry)); 639 | emit OperatorUpdated(address(this), operator, true); 640 | vm.expectEmit(true, true, true, false, address(registry)); 641 | emit CodeHashUpdated(address(this), codeHash, true); 642 | registry.copyEntriesOf(address(this), subscription); 643 | 644 | assertEq(registry.filteredOperators(address(this)).length, 2); 645 | assertEq(registry.filteredOperators(address(this))[0], duplicateOperator); 646 | assertEq(registry.filteredOperatorAt(address(this), 0), duplicateOperator); 647 | assertEq(registry.filteredOperators(address(this))[1], operator); 648 | assertEq(registry.filteredOperatorAt(address(this), 1), operator); 649 | 650 | assertEq(registry.filteredCodeHashes(address(this)).length, 2); 651 | assertEq(registry.filteredCodeHashes(address(this))[0], duplicateCodeHash); 652 | assertEq(registry.filteredCodeHashAt(address(this), 0), duplicateCodeHash); 653 | assertEq(registry.filteredCodeHashes(address(this))[1], codeHash); 654 | assertEq(registry.filteredCodeHashAt(address(this), 1), codeHash); 655 | } 656 | 657 | function testCopyEntriesOf_cannotCopySelf() public { 658 | registry.register(address(this)); 659 | vm.expectRevert(CannotCopyFromSelf.selector); 660 | registry.copyEntriesOf(address(this), address(this)); 661 | } 662 | 663 | function testCopyEntriesOf_OnlyAddressOrOwner() public { 664 | address subscription = makeAddr("subscription"); 665 | vm.startPrank(subscription); 666 | registry.register(subscription); 667 | vm.stopPrank(); 668 | registry.register(address(this)); 669 | 670 | vm.startPrank(makeAddr("not owner")); 671 | vm.expectRevert(abi.encodeWithSelector(OnlyAddressOrOwner.selector)); 672 | registry.copyEntriesOf(address(owned), subscription); 673 | } 674 | 675 | function testCopyEntriesOf_NotRegistered() public { 676 | address subscription = makeAddr("subscription"); 677 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 678 | registry.copyEntriesOf(address(this), subscription); 679 | } 680 | 681 | function testCopyEntriesOf_CannotUpdateWhileSubscribed() public { 682 | address subscription = makeAddr("subscription"); 683 | address operator = makeAddr("operator"); 684 | bytes32 codeHash = bytes32(bytes4(0xdeadbeef)); 685 | vm.startPrank(subscription); 686 | registry.register(subscription); 687 | registry.updateOperator(subscription, operator, true); 688 | registry.updateCodeHash(subscription, codeHash, true); 689 | vm.stopPrank(); 690 | registry.register(address(this)); 691 | registry.subscribe(address(this), subscription); 692 | 693 | vm.expectRevert(abi.encodeWithSelector(CannotUpdateWhileSubscribed.selector, subscription)); 694 | registry.copyEntriesOf(address(this), subscription); 695 | } 696 | 697 | function testCopyEntriesOf_NotRegistered_registrant() public { 698 | registry.register(address(this)); 699 | address subscription = makeAddr("subscription"); 700 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, subscription)); 701 | registry.copyEntriesOf(address(this), subscription); 702 | } 703 | 704 | function testCodeHashOf() public { 705 | address toCheck = makeAddr("toCheck"); 706 | bytes memory code = hex"deadbeef"; 707 | bytes32 codeHash = keccak256(code); 708 | vm.etch(toCheck, code); 709 | assertEq(registry.codeHashOf(toCheck), codeHash); 710 | } 711 | 712 | function testIsCodeHashOfFiltered() public { 713 | address toCheck = makeAddr("toCheck"); 714 | bytes memory code = hex"deadbeef"; 715 | bytes32 codeHash = keccak256(code); 716 | vm.etch(toCheck, code); 717 | registry.register(address(this)); 718 | registry.updateCodeHash(address(this), codeHash, true); 719 | assertTrue(registry.isCodeHashOfFiltered(address(this), toCheck)); 720 | assertFalse(registry.isCodeHashOfFiltered(address(this), makeAddr("not filtered"))); 721 | } 722 | 723 | function testIsCodeHashOfFiltered_subscription() public { 724 | address toCheck = makeAddr("toCheck"); 725 | bytes memory code = hex"deadbeef"; 726 | bytes32 codeHash = keccak256(code); 727 | vm.etch(toCheck, code); 728 | address subscription = makeAddr("subscription"); 729 | vm.startPrank(subscription); 730 | registry.register(subscription); 731 | registry.updateCodeHash(subscription, codeHash, true); 732 | vm.stopPrank(); 733 | registry.registerAndSubscribe(address(this), subscription); 734 | assertTrue(registry.isCodeHashOfFiltered(address(this), toCheck)); 735 | assertFalse(registry.isCodeHashOfFiltered(address(this), makeAddr("not filtered"))); 736 | } 737 | 738 | function testIsCodeHashFiltered_subscription() public { 739 | address toCheck = makeAddr("toCheck"); 740 | bytes memory code = hex"deadbeef"; 741 | bytes32 codeHash = keccak256(code); 742 | vm.etch(toCheck, code); 743 | address subscription = makeAddr("subscription"); 744 | vm.startPrank(subscription); 745 | registry.register(subscription); 746 | registry.updateCodeHash(subscription, codeHash, true); 747 | vm.stopPrank(); 748 | registry.registerAndSubscribe(address(this), subscription); 749 | assertTrue(registry.isCodeHashFiltered(address(this), codeHash)); 750 | assertFalse(registry.isCodeHashFiltered(address(this), bytes32(bytes4(0xdeadbeef)))); 751 | } 752 | 753 | function testIsOperatorFiltered_subscription() public { 754 | address operator = makeAddr("operator"); 755 | address subscription = makeAddr("subscription"); 756 | vm.startPrank(subscription); 757 | registry.register(subscription); 758 | registry.updateOperator(subscription, operator, true); 759 | vm.stopPrank(); 760 | registry.registerAndSubscribe(address(this), subscription); 761 | assertTrue(registry.isOperatorFiltered(address(this), operator)); 762 | assertFalse(registry.isOperatorFiltered(address(this), makeAddr("not filtered"))); 763 | } 764 | 765 | function testFilteredOperators_subscription() public { 766 | address operator = makeAddr("operator"); 767 | address subscription = makeAddr("subscription"); 768 | vm.startPrank(subscription); 769 | registry.register(subscription); 770 | registry.updateOperator(subscription, operator, true); 771 | vm.stopPrank(); 772 | registry.registerAndSubscribe(address(this), subscription); 773 | assertEq(registry.filteredOperators(address(this)).length, 1); 774 | assertEq(registry.filteredOperators(address(this))[0], operator); 775 | assertEq(registry.filteredOperatorAt(address(this), 0), operator); 776 | } 777 | 778 | function testFilteredCodeHashes_subscription() public { 779 | address toCheck = makeAddr("toCheck"); 780 | bytes memory code = hex"deadbeef"; 781 | bytes32 codeHash = keccak256(code); 782 | vm.etch(toCheck, code); 783 | address subscription = makeAddr("subscription"); 784 | vm.startPrank(subscription); 785 | registry.register(subscription); 786 | registry.updateCodeHash(subscription, codeHash, true); 787 | vm.stopPrank(); 788 | registry.registerAndSubscribe(address(this), subscription); 789 | assertEq(registry.filteredCodeHashes(address(this)).length, 1); 790 | assertEq(registry.filteredCodeHashes(address(this))[0], codeHash); 791 | assertEq(registry.filteredCodeHashAt(address(this), 0), codeHash); 792 | } 793 | 794 | function testFilteredOperatorAt_subscription() public { 795 | address operator = makeAddr("operator"); 796 | address subscription = makeAddr("subscription"); 797 | vm.startPrank(subscription); 798 | registry.register(subscription); 799 | registry.updateOperator(subscription, operator, true); 800 | vm.stopPrank(); 801 | registry.registerAndSubscribe(address(this), subscription); 802 | assertEq(registry.filteredOperatorAt(address(this), 0), operator); 803 | } 804 | 805 | function testFilteredCodeHashAt_subscription() public { 806 | address toCheck = makeAddr("toCheck"); 807 | bytes memory code = hex"deadbeef"; 808 | bytes32 codeHash = keccak256(code); 809 | vm.etch(toCheck, code); 810 | address subscription = makeAddr("subscription"); 811 | vm.startPrank(subscription); 812 | registry.register(subscription); 813 | registry.updateCodeHash(subscription, codeHash, true); 814 | vm.stopPrank(); 815 | registry.registerAndSubscribe(address(this), subscription); 816 | assertEq(registry.filteredCodeHashAt(address(this), 0), codeHash); 817 | } 818 | 819 | function testIsRegistered() public { 820 | registry.register(address(this)); 821 | assertTrue(registry.isRegistered(address(this))); 822 | assertFalse(registry.isRegistered(makeAddr("not registered"))); 823 | } 824 | 825 | function testIsOperatorAllowed_NotRegistered() public { 826 | assertTrue(registry.isOperatorAllowed(address(this), makeAddr("allowed"))); 827 | } 828 | 829 | function testIsOperatorAllowed() public { 830 | address operator = makeAddr("operator"); 831 | address toCheck = makeAddr("toCheck"); 832 | bytes memory code = hex"deadbeef"; 833 | bytes32 codeHash = keccak256(code); 834 | vm.etch(toCheck, code); 835 | registry.register(address(this)); 836 | registry.updateOperator(address(this), operator, true); 837 | registry.updateCodeHash(address(this), codeHash, true); 838 | 839 | assertTrue(registry.isOperatorAllowed(address(this), makeAddr("allowed"))); 840 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, address(operator))); 841 | registry.isOperatorAllowed(address(this), operator); 842 | vm.expectRevert( 843 | abi.encodeWithSelector(CodeHashFiltered.selector, address(toCheck), codeHash) 844 | ); 845 | registry.isOperatorAllowed(address(this), toCheck); 846 | } 847 | 848 | function testIsOperatorAllowed_subscription() public { 849 | address operator = makeAddr("operator"); 850 | address toCheck = makeAddr("toCheck"); 851 | bytes memory code = hex"deadbeef"; 852 | bytes32 codeHash = keccak256(code); 853 | vm.etch(toCheck, code); 854 | address subscription = makeAddr("subscription"); 855 | vm.startPrank(subscription); 856 | registry.register(subscription); 857 | registry.updateOperator(subscription, operator, true); 858 | registry.updateCodeHash(subscription, codeHash, true); 859 | vm.stopPrank(); 860 | registry.registerAndSubscribe(address(this), subscription); 861 | 862 | assertTrue(registry.isOperatorAllowed(address(this), makeAddr("allowed"))); 863 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, address(operator))); 864 | registry.isOperatorAllowed(address(this), operator); 865 | vm.expectRevert( 866 | abi.encodeWithSelector(CodeHashFiltered.selector, address(toCheck), codeHash) 867 | ); 868 | registry.isOperatorAllowed(address(this), toCheck); 869 | } 870 | 871 | function testUnregister() public { 872 | address subscription = makeAddr("subscription"); 873 | vm.prank(subscription); 874 | registry.register(subscription); 875 | registry.registerAndSubscribe(address(this), subscription); 876 | assertTrue(registry.isRegistered(address(this))); 877 | vm.expectEmit(true, true, true, false, address(registry)); 878 | emit SubscriptionUpdated(address(this), subscription, false); 879 | vm.expectEmit(true, true, true, false, address(registry)); 880 | emit RegistrationUpdated(address(this), false); 881 | registry.unregister(address(this)); 882 | assertFalse(registry.isRegistered(address(this))); 883 | assertEq(registry.subscribers(subscription).length, 0); 884 | } 885 | 886 | function testSubscriptionOf_notRegistered() public { 887 | vm.expectRevert(abi.encodeWithSelector(NotRegistered.selector, address(this))); 888 | registry.subscriptionOf(address(this)); 889 | } 890 | } 891 | -------------------------------------------------------------------------------- /test/OperatorFilterer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../src/OperatorFilterer.sol"; 5 | import {BaseRegistryTest} from "./BaseRegistryTest.sol"; 6 | import {Vm} from "forge-std/Vm.sol"; 7 | import {Filterer} from "./helpers/Filterer.sol"; 8 | 9 | contract ConcreteOperatorFilterer is OperatorFilterer { 10 | constructor(address registrant, bool sub) { 11 | _registerForOperatorFiltering(registrant, sub); 12 | } 13 | } 14 | 15 | contract OperatorFiltererTest is BaseRegistryTest { 16 | Filterer filterer; 17 | address filteredAddress; 18 | address filteredCodeHashAddress; 19 | bytes32 filteredCodeHash; 20 | address notFiltered; 21 | 22 | function setUp() public override { 23 | super.setUp(); 24 | notFiltered = makeAddr("not filtered"); 25 | filterer = new Filterer(); 26 | filteredAddress = makeAddr("filtered address"); 27 | registry.updateOperator(address(filterer), filteredAddress, true); 28 | filteredCodeHashAddress = makeAddr("filtered code hash"); 29 | bytes memory code = hex"deadbeef"; 30 | filteredCodeHash = keccak256(code); 31 | registry.updateCodeHash(address(filterer), filteredCodeHash, true); 32 | vm.etch(filteredCodeHashAddress, code); 33 | } 34 | 35 | function testFilterWithMsgSenderGas() public view { 36 | filterer.filter(address(this)); 37 | } 38 | 39 | function testFilterWithMsgSenderOriginalGas() public view { 40 | filterer.filterOriginal(address(this)); 41 | } 42 | 43 | function testFilterWithOperatorGas() public view { 44 | filterer.filter(notFiltered); 45 | } 46 | 47 | function testFilterWithOperatorOriginalGas() public view { 48 | filterer.filterOriginal(notFiltered); 49 | } 50 | 51 | function testFilter() public { 52 | assertTrue(filterer.filter(notFiltered)); 53 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 54 | vm.prank(filteredAddress); 55 | filterer.filter(notFiltered); 56 | vm.expectRevert( 57 | abi.encodeWithSelector( 58 | CodeHashFiltered.selector, filteredCodeHashAddress, filteredCodeHash 59 | ) 60 | ); 61 | vm.prank(filteredCodeHashAddress); 62 | filterer.filter(notFiltered); 63 | } 64 | 65 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 66 | 67 | function testConstructory_noSubscribeOrCopy() public { 68 | vm.recordLogs(); 69 | Filterer filterer2 = new Filterer(); 70 | Vm.Log[] memory logs = vm.getRecordedLogs(); 71 | 72 | assertEq(logs.length, 2); 73 | uint256 i; 74 | if (logs[0].topics[0] == keccak256("OwnershipTransferred(address,address)")) { 75 | i = 1; 76 | } 77 | assertEq(logs[i].topics[0], keccak256("RegistrationUpdated(address,bool)")); 78 | assertEq(address(uint160(uint256(logs[i].topics[1]))), address(filterer2)); 79 | assertEq(logs[i ^ 1].topics[0], keccak256("OwnershipTransferred(address,address)")); 80 | } 81 | 82 | function testConstructor_copy() public { 83 | address deployed = computeCreateAddress(address(this), vm.getNonce(address(this))); 84 | vm.expectEmit(true, false, false, false, address(registry)); 85 | emit RegistrationUpdated(deployed, true); 86 | vm.expectEmit(true, true, true, false, address(registry)); 87 | emit OperatorUpdated(deployed, filteredAddress, true); 88 | vm.expectEmit(true, true, true, false, address(registry)); 89 | emit CodeHashUpdated(deployed, filteredCodeHash, true); 90 | new ConcreteOperatorFilterer(address(filterer), false); 91 | } 92 | 93 | function testConstructor_subscribe() public { 94 | address deployed = computeCreateAddress(address(this), vm.getNonce(address(this))); 95 | vm.expectEmit(true, false, false, false, address(registry)); 96 | emit RegistrationUpdated(deployed, true); 97 | vm.expectEmit(true, true, true, false, address(registry)); 98 | emit SubscriptionUpdated(deployed, address(filterer), true); 99 | vm.recordLogs(); 100 | new ConcreteOperatorFilterer(address(filterer), true); 101 | assertEq(vm.getRecordedLogs().length, 2); 102 | } 103 | 104 | function testRegistryNotDeployedDoesNotRevert() public { 105 | vm.etch(address(registry), ""); 106 | Filterer filterer2 = new Filterer(); 107 | assertTrue(filterer2.filter(address(this))); 108 | } 109 | 110 | function testRegisterNonExistentRegistryDoesNotRevert() public { 111 | new ConcreteOperatorFilterer(address(0x12345678791234567879), true); 112 | new ConcreteOperatorFilterer(address(0x12345678791234567879), false); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/OwnedRegistrant.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OwnedRegistrant} from "operator-filter-registry/OwnedRegistrant.sol"; 5 | import {BaseRegistryTest} from "./BaseRegistryTest.sol"; 6 | 7 | contract OperatorFiltererTest is BaseRegistryTest { 8 | OwnedRegistrant registrant; 9 | address filteredAddress; 10 | address filteredCodeHashAddress; 11 | bytes32 filteredCodeHash; 12 | 13 | function setUp() public override { 14 | super.setUp(); 15 | 16 | registrant = new OwnedRegistrant(address(this)); 17 | filteredAddress = makeAddr("filtered address"); 18 | filteredCodeHashAddress = makeAddr("filtered code hash"); 19 | bytes memory code = hex"deadbeef"; 20 | filteredCodeHash = keccak256(code); 21 | } 22 | 23 | function testConstructor() public { 24 | assertTrue(registry.isRegistered(address(registrant))); 25 | registry.updateOperator(address(registrant), filteredAddress, true); 26 | assertTrue(registry.isOperatorFiltered(address(registrant), filteredAddress)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/PriorityOperatorFilterer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../src/OperatorFilterer.sol"; 5 | import {BaseRegistryTest} from "./BaseRegistryTest.sol"; 6 | import {Vm} from "forge-std/Vm.sol"; 7 | import {PriorityFilterer} from "./helpers/PriorityFilterer.sol"; 8 | 9 | contract PriorityOperatorFiltererTest is BaseRegistryTest { 10 | PriorityFilterer filterer; 11 | address filteredAddress; 12 | address filteredCodeHashAddress; 13 | address priorityOperator; 14 | bytes32 filteredCodeHash; 15 | address notFiltered; 16 | 17 | function setUp() public override { 18 | super.setUp(); 19 | priorityOperator = makeAddr("priority operator"); 20 | notFiltered = makeAddr("not filtered"); 21 | filterer = new PriorityFilterer(priorityOperator); 22 | filteredAddress = makeAddr("filtered address"); 23 | registry.updateOperator(address(filterer), filteredAddress, true); 24 | filteredCodeHashAddress = makeAddr("filtered code hash"); 25 | bytes memory code = hex"deadbeef"; 26 | filteredCodeHash = keccak256(code); 27 | registry.updateCodeHash(address(filterer), filteredCodeHash, true); 28 | vm.etch(filteredCodeHashAddress, code); 29 | } 30 | 31 | function testPriorityFilterWithMsgSenderGas() public view { 32 | filterer.filter(address(this)); 33 | } 34 | 35 | function testPriorityFilterWithOperatorGas() public view { 36 | filterer.filter(notFiltered); 37 | } 38 | 39 | function testPriorityFilterWithPriorityOperatorGas() public { 40 | vm.prank(priorityOperator); 41 | filterer.filter(notFiltered); 42 | } 43 | 44 | function testPriorityFilter() public { 45 | assertTrue(filterer.filter(notFiltered)); 46 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 47 | vm.prank(filteredAddress); 48 | filterer.filter(notFiltered); 49 | vm.expectRevert( 50 | abi.encodeWithSelector( 51 | CodeHashFiltered.selector, filteredCodeHashAddress, filteredCodeHash 52 | ) 53 | ); 54 | vm.prank(filteredCodeHashAddress); 55 | filterer.filter(notFiltered); 56 | 57 | registry.updateOperator(address(filterer), priorityOperator, true); 58 | vm.prank(priorityOperator); 59 | filterer.filter(notFiltered); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/example/ExampleERC1155.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC1155} from "../../src/example/ExampleERC1155.sol"; 5 | import {BaseRegistryTest} from "../BaseRegistryTest.sol"; 6 | 7 | contract TestableExampleERC1155 is ExampleERC1155 { 8 | function mint(address to, uint256 tokenId) external { 9 | _mint(to, tokenId, 1, ""); 10 | } 11 | 12 | function repeatRegistration() public { 13 | _registerForOperatorFiltering(); 14 | } 15 | } 16 | 17 | contract ExampleERC1155Test is BaseRegistryTest { 18 | TestableExampleERC1155 example; 19 | address filteredAddress; 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | vm.startPrank(DEFAULT_SUBSCRIPTION); 25 | registry.register(DEFAULT_SUBSCRIPTION); 26 | 27 | filteredAddress = makeAddr("filtered address"); 28 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 29 | vm.stopPrank(); 30 | 31 | example = new TestableExampleERC1155(); 32 | } 33 | 34 | function testFilter() public { 35 | vm.startPrank(address(filteredAddress)); 36 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 37 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, 1, ""); 38 | uint256[] memory ids = new uint256[](1); 39 | ids[0] = 1; 40 | uint256[] memory amounts = new uint256[](1); 41 | amounts[0] = 1; 42 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 43 | example.safeBatchTransferFrom(makeAddr("from"), makeAddr("to"), ids, amounts, ""); 44 | } 45 | 46 | function testOwnersNotExcluded() public { 47 | address alice = address(0xA11CE); 48 | example.mint(alice, 1); 49 | 50 | vm.prank(DEFAULT_SUBSCRIPTION); 51 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 52 | 53 | vm.prank(alice); 54 | example.safeTransferFrom(alice, makeAddr("to"), 1, 1, ""); 55 | } 56 | 57 | function testOwnersNotExcludedBatch() public { 58 | address alice = address(0xA11CE); 59 | example.mint(alice, 1); 60 | uint256[] memory ids = new uint256[](1); 61 | ids[0] = 1; 62 | uint256[] memory amounts = new uint256[](1); 63 | amounts[0] = 1; 64 | 65 | vm.prank(DEFAULT_SUBSCRIPTION); 66 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 67 | 68 | vm.prank(alice); 69 | example.safeBatchTransferFrom(alice, makeAddr("to"), ids, amounts, ""); 70 | } 71 | 72 | function testExclusionExceptionDoesNotApplyToOperators() public { 73 | address alice = address(0xA11CE); 74 | address bob = address(0xB0B); 75 | example.mint(bob, 1); 76 | vm.prank(bob); 77 | example.setApprovalForAll(alice, true); 78 | 79 | vm.prank(DEFAULT_SUBSCRIPTION); 80 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 81 | 82 | vm.startPrank(alice); 83 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 84 | example.safeTransferFrom(bob, makeAddr("to"), 1, 1, ""); 85 | } 86 | 87 | function testExcludeApprovals() public { 88 | address alice = address(0xA11CE); 89 | address bob = address(0xB0B); 90 | example.mint(bob, 1); 91 | 92 | vm.prank(DEFAULT_SUBSCRIPTION); 93 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 94 | 95 | vm.startPrank(bob); 96 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 97 | example.setApprovalForAll(alice, true); 98 | } 99 | 100 | function testSetOperatorFilteringEnabled() public { 101 | uint256 randomness = uint256(keccak256(abi.encode(123))); 102 | address alice = address(0xA11CE); 103 | address to = makeAddr("to"); 104 | example.setOperatorFilteringEnabled(false); 105 | vm.prank(alice); 106 | example.setApprovalForAll(address(filteredAddress), true); 107 | 108 | for (uint256 i = 0; i < 128; ++i) { 109 | example.mint(alice, i); 110 | bool enabled = randomness & 1 == 0; 111 | vm.prank(example.owner()); 112 | example.setOperatorFilteringEnabled(enabled); 113 | 114 | vm.prank(address(filteredAddress)); 115 | if (enabled) { 116 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 117 | } 118 | example.safeTransferFrom(alice, to, i, 1, ""); 119 | 120 | randomness = randomness >> 1; 121 | } 122 | 123 | for (uint256 i = 0; i < 128; ++i) { 124 | bool enabled = randomness & 1 == 0; 125 | vm.prank(example.owner()); 126 | example.setOperatorFilteringEnabled(enabled); 127 | 128 | vm.prank(alice); 129 | if (enabled) { 130 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 131 | } 132 | example.setApprovalForAll(address(filteredAddress), true); 133 | randomness = randomness >> 1; 134 | } 135 | } 136 | 137 | function testRepeatRegistrationOk() public { 138 | vm.prank(example.owner()); 139 | example.repeatRegistration(); 140 | testSetOperatorFilteringEnabled(); 141 | } 142 | 143 | function testSupportsInterface() public { 144 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 145 | assertTrue(example.supportsInterface(0xd9b67a26)); // IERC1155 146 | assertTrue(example.supportsInterface(0x0e89341c)); // IERC1155MetadataURI 147 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 148 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/example/ExampleERC721.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC721} from "../../src/example/ExampleERC721.sol"; 5 | import {BaseRegistryTest} from "../BaseRegistryTest.sol"; 6 | 7 | contract TestableExampleERC721 is ExampleERC721 { 8 | function mint(address to, uint256 tokenId) external { 9 | _mint(to, tokenId); 10 | } 11 | 12 | function repeatRegistration() public { 13 | _registerForOperatorFiltering(); 14 | } 15 | } 16 | 17 | contract ExampleERC721Test is BaseRegistryTest { 18 | TestableExampleERC721 example; 19 | address filteredAddress; 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | vm.startPrank(DEFAULT_SUBSCRIPTION); 25 | registry.register(DEFAULT_SUBSCRIPTION); 26 | 27 | filteredAddress = makeAddr("filtered address"); 28 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 29 | vm.stopPrank(); 30 | 31 | example = new TestableExampleERC721(); 32 | } 33 | 34 | function testFilter() public { 35 | vm.startPrank(address(filteredAddress)); 36 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 37 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 38 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 39 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 40 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 41 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 42 | } 43 | 44 | function testOwnersNotExcluded() public { 45 | address alice = address(0xA11CE); 46 | example.mint(alice, 1); 47 | 48 | vm.prank(DEFAULT_SUBSCRIPTION); 49 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 50 | 51 | vm.prank(alice); 52 | example.transferFrom(alice, makeAddr("to"), 1); 53 | } 54 | 55 | function testOwnersNotExcludedSafeTransfer() public { 56 | address alice = address(0xA11CE); 57 | example.mint(alice, 1); 58 | example.mint(alice, 2); 59 | 60 | vm.prank(DEFAULT_SUBSCRIPTION); 61 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 62 | 63 | vm.startPrank(alice); 64 | example.safeTransferFrom(alice, makeAddr("to"), 1); 65 | example.safeTransferFrom(alice, makeAddr("to"), 2, ""); 66 | } 67 | 68 | function testExclusionExceptionDoesNotApplyToOperators() public { 69 | address alice = address(0xA11CE); 70 | address bob = address(0xB0B); 71 | example.mint(bob, 1); 72 | vm.prank(bob); 73 | example.setApprovalForAll(alice, true); 74 | 75 | vm.prank(DEFAULT_SUBSCRIPTION); 76 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 77 | 78 | vm.startPrank(alice); 79 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 80 | example.transferFrom(bob, makeAddr("to"), 1); 81 | } 82 | 83 | function testExcludeApprovals() public { 84 | address alice = address(0xA11CE); 85 | address bob = address(0xB0B); 86 | example.mint(bob, 1); 87 | 88 | vm.prank(DEFAULT_SUBSCRIPTION); 89 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 90 | 91 | vm.startPrank(bob); 92 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 93 | example.setApprovalForAll(alice, true); 94 | 95 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 96 | example.approve(alice, 1); 97 | } 98 | 99 | function testSetOperatorFilteringEnabled() public { 100 | uint256 randomness = uint256(keccak256(abi.encode(123))); 101 | address alice = address(0xA11CE); 102 | address to = makeAddr("to"); 103 | example.setOperatorFilteringEnabled(false); 104 | vm.prank(alice); 105 | example.setApprovalForAll(address(filteredAddress), true); 106 | 107 | for (uint256 i = 0; i < 128; ++i) { 108 | example.mint(alice, i); 109 | bool enabled = randomness & 1 == 0; 110 | vm.prank(example.owner()); 111 | example.setOperatorFilteringEnabled(enabled); 112 | 113 | vm.prank(address(filteredAddress)); 114 | if (enabled) { 115 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 116 | } 117 | example.transferFrom(alice, to, i); 118 | 119 | randomness = randomness >> 1; 120 | } 121 | 122 | for (uint256 i = 0; i < 128; ++i) { 123 | bool enabled = randomness & 1 == 0; 124 | vm.prank(example.owner()); 125 | example.setOperatorFilteringEnabled(enabled); 126 | 127 | vm.prank(alice); 128 | if (enabled) { 129 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 130 | } 131 | example.setApprovalForAll(address(filteredAddress), true); 132 | randomness = randomness >> 1; 133 | } 134 | } 135 | 136 | function testRepeatRegistrationOk() public { 137 | vm.prank(example.owner()); 138 | example.repeatRegistration(); 139 | testSetOperatorFilteringEnabled(); 140 | } 141 | 142 | function testSupportsInterface() public { 143 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 144 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 145 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 146 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 147 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/example/ExampleERC721A.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC721A} from "../../src/example/ExampleERC721A.sol"; 5 | import {BaseRegistryTest} from "../BaseRegistryTest.sol"; 6 | 7 | contract TestableExampleERC721A is ExampleERC721A { 8 | function mint(address to, uint256 quantity) external { 9 | _mint(to, quantity); 10 | } 11 | 12 | function repeatRegistration() public { 13 | _registerForOperatorFiltering(); 14 | } 15 | } 16 | 17 | contract ExampleERC721ATest is BaseRegistryTest { 18 | TestableExampleERC721A example; 19 | address filteredAddress; 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | vm.startPrank(DEFAULT_SUBSCRIPTION); 25 | registry.register(DEFAULT_SUBSCRIPTION); 26 | 27 | filteredAddress = makeAddr("filtered address"); 28 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 29 | vm.stopPrank(); 30 | 31 | example = new TestableExampleERC721A(); 32 | } 33 | 34 | function testFilter() public { 35 | vm.startPrank(address(filteredAddress)); 36 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 37 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 38 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 39 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 40 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 41 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 42 | } 43 | 44 | function testOwnersNotExcluded() public { 45 | address alice = address(0xA11CE); 46 | example.mint(alice, 1); 47 | 48 | vm.prank(DEFAULT_SUBSCRIPTION); 49 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 50 | 51 | vm.prank(alice); 52 | example.transferFrom(alice, makeAddr("to"), 0); 53 | } 54 | 55 | function testOwnersNotExcludedSafeTransfer() public { 56 | address alice = address(0xA11CE); 57 | example.mint(alice, 2); 58 | 59 | vm.prank(DEFAULT_SUBSCRIPTION); 60 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 61 | 62 | vm.startPrank(alice); 63 | example.safeTransferFrom(alice, makeAddr("to"), 0); 64 | example.safeTransferFrom(alice, makeAddr("to"), 1, ""); 65 | } 66 | 67 | function testExclusionExceptionDoesNotApplyToOperators() public { 68 | address alice = address(0xA11CE); 69 | address bob = address(0xB0B); 70 | example.mint(bob, 1); 71 | vm.prank(bob); 72 | example.setApprovalForAll(alice, true); 73 | 74 | vm.prank(DEFAULT_SUBSCRIPTION); 75 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 76 | 77 | vm.startPrank(alice); 78 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 79 | example.transferFrom(bob, makeAddr("to"), 0); 80 | } 81 | 82 | function testExcludeApprovals() public { 83 | address alice = address(0xA11CE); 84 | address bob = address(0xB0B); 85 | example.mint(bob, 1); 86 | 87 | vm.prank(DEFAULT_SUBSCRIPTION); 88 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 89 | 90 | vm.startPrank(bob); 91 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 92 | example.setApprovalForAll(alice, true); 93 | 94 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 95 | example.approve(alice, 0); 96 | } 97 | 98 | function testSetOperatorFilteringEnabled() public { 99 | uint256 randomness = uint256(keccak256(abi.encode(123))); 100 | address alice = address(0xA11CE); 101 | address to = makeAddr("to"); 102 | example.setOperatorFilteringEnabled(false); 103 | vm.prank(alice); 104 | example.setApprovalForAll(address(filteredAddress), true); 105 | 106 | example.mint(alice, 128); 107 | for (uint256 i = 0; i < 128; ++i) { 108 | bool enabled = randomness & 1 == 0; 109 | vm.prank(example.owner()); 110 | example.setOperatorFilteringEnabled(enabled); 111 | 112 | vm.prank(address(filteredAddress)); 113 | if (enabled) { 114 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 115 | } 116 | example.transferFrom(alice, to, i); 117 | 118 | randomness = randomness >> 1; 119 | } 120 | 121 | for (uint256 i = 0; i < 128; ++i) { 122 | bool enabled = randomness & 1 == 0; 123 | vm.prank(example.owner()); 124 | example.setOperatorFilteringEnabled(enabled); 125 | 126 | vm.prank(alice); 127 | if (enabled) { 128 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 129 | } 130 | example.setApprovalForAll(address(filteredAddress), true); 131 | randomness = randomness >> 1; 132 | } 133 | } 134 | 135 | function testRepeatRegistrationOk() public { 136 | vm.prank(example.owner()); 137 | example.repeatRegistration(); 138 | testSetOperatorFilteringEnabled(); 139 | } 140 | 141 | function testSupportsInterface() public { 142 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 143 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 144 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 145 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 146 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /test/example/ExampleSoladyERC1155.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleSoladyERC1155} from "../../src/example/ExampleSoladyERC1155.sol"; 5 | import {BaseRegistryTest} from "../BaseRegistryTest.sol"; 6 | 7 | contract TestableExampleSoladyERC1155 is ExampleSoladyERC1155 { 8 | function mint(address to, uint256 tokenId) external { 9 | _mint(to, tokenId, 1, ""); 10 | } 11 | 12 | function repeatRegistration() public { 13 | _registerForOperatorFiltering(); 14 | } 15 | } 16 | 17 | contract ExampleSoladyERC1155Test is BaseRegistryTest { 18 | TestableExampleSoladyERC1155 example; 19 | address filteredAddress; 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | vm.startPrank(DEFAULT_SUBSCRIPTION); 25 | registry.register(DEFAULT_SUBSCRIPTION); 26 | 27 | filteredAddress = makeAddr("filtered address"); 28 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 29 | vm.stopPrank(); 30 | 31 | example = new TestableExampleSoladyERC1155(); 32 | } 33 | 34 | function testFilter() public { 35 | vm.startPrank(address(filteredAddress)); 36 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 37 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, 1, ""); 38 | uint256[] memory ids = new uint256[](1); 39 | ids[0] = 1; 40 | uint256[] memory amounts = new uint256[](1); 41 | amounts[0] = 1; 42 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 43 | example.safeBatchTransferFrom(makeAddr("from"), makeAddr("to"), ids, amounts, ""); 44 | } 45 | 46 | function testOwnersNotExcluded() public { 47 | address alice = address(0xA11CE); 48 | example.mint(alice, 1); 49 | 50 | vm.prank(DEFAULT_SUBSCRIPTION); 51 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 52 | 53 | vm.prank(alice); 54 | example.safeTransferFrom(alice, makeAddr("to"), 1, 1, ""); 55 | } 56 | 57 | function testOwnersNotExcludedBatch() public { 58 | address alice = address(0xA11CE); 59 | example.mint(alice, 1); 60 | uint256[] memory ids = new uint256[](1); 61 | ids[0] = 1; 62 | uint256[] memory amounts = new uint256[](1); 63 | amounts[0] = 1; 64 | 65 | vm.prank(DEFAULT_SUBSCRIPTION); 66 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 67 | 68 | vm.prank(alice); 69 | example.safeBatchTransferFrom(alice, makeAddr("to"), ids, amounts, ""); 70 | } 71 | 72 | function testExclusionExceptionDoesNotApplyToOperators() public { 73 | address alice = address(0xA11CE); 74 | address bob = address(0xB0B); 75 | example.mint(bob, 1); 76 | vm.prank(bob); 77 | example.setApprovalForAll(alice, true); 78 | 79 | vm.prank(DEFAULT_SUBSCRIPTION); 80 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 81 | 82 | vm.startPrank(alice); 83 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 84 | example.safeTransferFrom(bob, makeAddr("to"), 1, 1, ""); 85 | } 86 | 87 | function testExcludeApprovals() public { 88 | address alice = address(0xA11CE); 89 | address bob = address(0xB0B); 90 | example.mint(bob, 1); 91 | 92 | vm.prank(DEFAULT_SUBSCRIPTION); 93 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 94 | 95 | vm.startPrank(bob); 96 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 97 | example.setApprovalForAll(alice, true); 98 | } 99 | 100 | function testSetOperatorFilteringEnabled() public { 101 | uint256 randomness = uint256(keccak256(abi.encode(123))); 102 | address alice = address(0xA11CE); 103 | address to = makeAddr("to"); 104 | example.setOperatorFilteringEnabled(false); 105 | vm.prank(alice); 106 | example.setApprovalForAll(address(filteredAddress), true); 107 | 108 | for (uint256 i = 0; i < 128; ++i) { 109 | example.mint(alice, i); 110 | bool enabled = randomness & 1 == 0; 111 | vm.prank(example.owner()); 112 | example.setOperatorFilteringEnabled(enabled); 113 | 114 | vm.prank(address(filteredAddress)); 115 | if (enabled) { 116 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 117 | } 118 | example.safeTransferFrom(alice, to, i, 1, ""); 119 | 120 | randomness = randomness >> 1; 121 | } 122 | 123 | for (uint256 i = 0; i < 128; ++i) { 124 | bool enabled = randomness & 1 == 0; 125 | vm.prank(example.owner()); 126 | example.setOperatorFilteringEnabled(enabled); 127 | 128 | vm.prank(alice); 129 | if (enabled) { 130 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 131 | } 132 | example.setApprovalForAll(address(filteredAddress), true); 133 | randomness = randomness >> 1; 134 | } 135 | } 136 | 137 | function testRepeatRegistrationOk() public { 138 | vm.prank(example.owner()); 139 | example.repeatRegistration(); 140 | testSetOperatorFilteringEnabled(); 141 | } 142 | 143 | function testSupportsInterface() public { 144 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 145 | assertTrue(example.supportsInterface(0xd9b67a26)); // IERC1155 146 | assertTrue(example.supportsInterface(0x0e89341c)); // IERC1155MetadataURI 147 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 148 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/example/ExampleSoladyERC721.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleSoladyERC721} from "../../src/example/ExampleSoladyERC721.sol"; 5 | import {BaseRegistryTest} from "../BaseRegistryTest.sol"; 6 | 7 | contract TestableExampleSoladyERC721 is ExampleSoladyERC721 { 8 | function mint(address to, uint256 tokenId) external { 9 | _mint(to, tokenId); 10 | } 11 | 12 | function repeatRegistration() public { 13 | _registerForOperatorFiltering(); 14 | } 15 | } 16 | 17 | contract ExampleSoladyERC721Test is BaseRegistryTest { 18 | TestableExampleSoladyERC721 example; 19 | address filteredAddress; 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | vm.startPrank(DEFAULT_SUBSCRIPTION); 25 | registry.register(DEFAULT_SUBSCRIPTION); 26 | 27 | filteredAddress = makeAddr("filtered address"); 28 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 29 | vm.stopPrank(); 30 | 31 | example = new TestableExampleSoladyERC721(); 32 | } 33 | 34 | function testFilter() public { 35 | vm.startPrank(address(filteredAddress)); 36 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 37 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 38 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 39 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 40 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 41 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 42 | } 43 | 44 | function testOwnersNotExcluded() public { 45 | address alice = address(0xA11CE); 46 | example.mint(alice, 1); 47 | 48 | vm.prank(DEFAULT_SUBSCRIPTION); 49 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 50 | 51 | vm.prank(alice); 52 | example.transferFrom(alice, makeAddr("to"), 1); 53 | } 54 | 55 | function testOwnersNotExcludedSafeTransfer() public { 56 | address alice = address(0xA11CE); 57 | example.mint(alice, 1); 58 | example.mint(alice, 2); 59 | 60 | vm.prank(DEFAULT_SUBSCRIPTION); 61 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 62 | 63 | vm.startPrank(alice); 64 | example.safeTransferFrom(alice, makeAddr("to"), 1); 65 | example.safeTransferFrom(alice, makeAddr("to"), 2, ""); 66 | } 67 | 68 | function testExclusionExceptionDoesNotApplyToOperators() public { 69 | address alice = address(0xA11CE); 70 | address bob = address(0xB0B); 71 | example.mint(bob, 1); 72 | vm.prank(bob); 73 | example.setApprovalForAll(alice, true); 74 | 75 | vm.prank(DEFAULT_SUBSCRIPTION); 76 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 77 | 78 | vm.startPrank(alice); 79 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 80 | example.transferFrom(bob, makeAddr("to"), 1); 81 | } 82 | 83 | function testExcludeApprovals() public { 84 | address alice = address(0xA11CE); 85 | address bob = address(0xB0B); 86 | example.mint(bob, 1); 87 | 88 | vm.prank(DEFAULT_SUBSCRIPTION); 89 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 90 | 91 | vm.startPrank(bob); 92 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 93 | example.setApprovalForAll(alice, true); 94 | 95 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 96 | example.approve(alice, 1); 97 | } 98 | 99 | function testSetOperatorFilteringEnabled() public { 100 | uint256 randomness = uint256(keccak256(abi.encode(123))); 101 | address alice = address(0xA11CE); 102 | address to = makeAddr("to"); 103 | example.setOperatorFilteringEnabled(false); 104 | vm.prank(alice); 105 | example.setApprovalForAll(address(filteredAddress), true); 106 | 107 | for (uint256 i = 0; i < 128; ++i) { 108 | example.mint(alice, i); 109 | bool enabled = randomness & 1 == 0; 110 | vm.prank(example.owner()); 111 | example.setOperatorFilteringEnabled(enabled); 112 | 113 | vm.prank(address(filteredAddress)); 114 | if (enabled) { 115 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 116 | } 117 | example.transferFrom(alice, to, i); 118 | 119 | randomness = randomness >> 1; 120 | } 121 | 122 | for (uint256 i = 0; i < 128; ++i) { 123 | bool enabled = randomness & 1 == 0; 124 | vm.prank(example.owner()); 125 | example.setOperatorFilteringEnabled(enabled); 126 | 127 | vm.prank(alice); 128 | if (enabled) { 129 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 130 | } 131 | example.setApprovalForAll(address(filteredAddress), true); 132 | randomness = randomness >> 1; 133 | } 134 | } 135 | 136 | function testRepeatRegistrationOk() public { 137 | vm.prank(example.owner()); 138 | example.repeatRegistration(); 139 | testSetOperatorFilteringEnabled(); 140 | } 141 | 142 | function testSupportsInterface() public { 143 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 144 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 145 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 146 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 147 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/example/upgradeable/ExampleERC1155Upgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC1155Upgradeable} from 5 | "../../../src/example/upgradeable/ExampleERC1155Upgradeable.sol"; 6 | import {BaseRegistryTest} from "../../BaseRegistryTest.sol"; 7 | import {Initializable} from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | 9 | contract TestableExampleERC1155 is ExampleERC1155Upgradeable { 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId, 1, ""); 12 | } 13 | 14 | function repeatRegistration() public { 15 | _registerForOperatorFiltering(); 16 | } 17 | } 18 | 19 | contract ExampleERC1155UpgradeableTest is BaseRegistryTest, Initializable { 20 | TestableExampleERC1155 example; 21 | address filteredAddress; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | 26 | vm.startPrank(DEFAULT_SUBSCRIPTION); 27 | registry.register(DEFAULT_SUBSCRIPTION); 28 | 29 | filteredAddress = makeAddr("filtered address"); 30 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 31 | vm.stopPrank(); 32 | 33 | example = new TestableExampleERC1155(); 34 | example.initialize(); 35 | } 36 | 37 | function testUpgradeable() public { 38 | TestableExampleERC1155 example2 = new TestableExampleERC1155(); 39 | vm.expectEmit(true, true, false, true, address(example2)); 40 | emit Initialized(1); 41 | example2.initialize(); 42 | vm.expectRevert(bytes("Initializable: contract is already initialized")); 43 | example2.initialize(); 44 | } 45 | 46 | function testFilter() public { 47 | vm.startPrank(address(filteredAddress)); 48 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 49 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, 1, ""); 50 | uint256[] memory ids = new uint256[](1); 51 | ids[0] = 1; 52 | uint256[] memory amounts = new uint256[](1); 53 | amounts[0] = 1; 54 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 55 | example.safeBatchTransferFrom(makeAddr("from"), makeAddr("to"), ids, amounts, ""); 56 | } 57 | 58 | function testOwnersNotExcluded() public { 59 | address alice = address(0xA11CE); 60 | example.mint(alice, 1); 61 | 62 | vm.prank(DEFAULT_SUBSCRIPTION); 63 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 64 | 65 | vm.prank(alice); 66 | example.safeTransferFrom(alice, makeAddr("to"), 1, 1, ""); 67 | } 68 | 69 | function testOwnersNotExcludedBatch() public { 70 | address alice = address(0xA11CE); 71 | example.mint(alice, 1); 72 | uint256[] memory ids = new uint256[](1); 73 | ids[0] = 1; 74 | uint256[] memory amounts = new uint256[](1); 75 | amounts[0] = 1; 76 | 77 | vm.prank(DEFAULT_SUBSCRIPTION); 78 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 79 | 80 | vm.prank(alice); 81 | example.safeBatchTransferFrom(alice, makeAddr("to"), ids, amounts, ""); 82 | } 83 | 84 | function testExclusionExceptionDoesNotApplyToOperators() public { 85 | address alice = address(0xA11CE); 86 | address bob = address(0xB0B); 87 | example.mint(bob, 1); 88 | vm.prank(bob); 89 | example.setApprovalForAll(alice, true); 90 | 91 | vm.prank(DEFAULT_SUBSCRIPTION); 92 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 93 | 94 | vm.startPrank(alice); 95 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 96 | example.safeTransferFrom(bob, makeAddr("to"), 1, 1, ""); 97 | } 98 | 99 | function testExcludeApprovals() public { 100 | address alice = address(0xA11CE); 101 | address bob = address(0xB0B); 102 | example.mint(bob, 1); 103 | 104 | vm.prank(DEFAULT_SUBSCRIPTION); 105 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 106 | 107 | vm.startPrank(bob); 108 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 109 | example.setApprovalForAll(alice, true); 110 | } 111 | 112 | function testSetOperatorFilteringEnabled() public { 113 | uint256 randomness = uint256(keccak256(abi.encode(123))); 114 | address alice = address(0xA11CE); 115 | address to = makeAddr("to"); 116 | example.setOperatorFilteringEnabled(false); 117 | vm.prank(alice); 118 | example.setApprovalForAll(address(filteredAddress), true); 119 | 120 | for (uint256 i = 0; i < 128; ++i) { 121 | example.mint(alice, i); 122 | bool enabled = randomness & 1 == 0; 123 | vm.prank(example.owner()); 124 | example.setOperatorFilteringEnabled(enabled); 125 | 126 | vm.prank(address(filteredAddress)); 127 | if (enabled) { 128 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 129 | } 130 | example.safeTransferFrom(alice, to, i, 1, ""); 131 | 132 | randomness = randomness >> 1; 133 | } 134 | 135 | for (uint256 i = 0; i < 128; ++i) { 136 | bool enabled = randomness & 1 == 0; 137 | vm.prank(example.owner()); 138 | example.setOperatorFilteringEnabled(enabled); 139 | 140 | vm.prank(alice); 141 | if (enabled) { 142 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 143 | } 144 | example.setApprovalForAll(address(filteredAddress), true); 145 | randomness = randomness >> 1; 146 | } 147 | } 148 | 149 | function testRepeatRegistrationOk() public { 150 | vm.prank(example.owner()); 151 | example.repeatRegistration(); 152 | testSetOperatorFilteringEnabled(); 153 | } 154 | 155 | function testSupportsInterface() public { 156 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 157 | assertTrue(example.supportsInterface(0xd9b67a26)); // IERC1155 158 | assertTrue(example.supportsInterface(0x0e89341c)); // IERC1155MetadataURI 159 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 160 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/example/upgradeable/ExampleERC721AUpgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC721AUpgradeable} from 5 | "../../../src/example/upgradeable/ExampleERC721AUpgradeable.sol"; 6 | import {BaseRegistryTest} from "../../BaseRegistryTest.sol"; 7 | import {Initializable} from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | 9 | contract TestableExampleERC721A is ExampleERC721AUpgradeable { 10 | function mint(address to, uint256 quantity) external { 11 | _mint(to, quantity); 12 | } 13 | 14 | function repeatRegistration() public { 15 | _registerForOperatorFiltering(); 16 | } 17 | } 18 | 19 | contract ExampleERC721AUpgradeableTest is BaseRegistryTest, Initializable { 20 | TestableExampleERC721A example; 21 | address filteredAddress; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | 26 | vm.startPrank(DEFAULT_SUBSCRIPTION); 27 | registry.register(DEFAULT_SUBSCRIPTION); 28 | 29 | filteredAddress = makeAddr("filtered address"); 30 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 31 | vm.stopPrank(); 32 | 33 | example = new TestableExampleERC721A(); 34 | example.initialize(); 35 | } 36 | 37 | function testUpgradeable() public { 38 | TestableExampleERC721A example2 = new TestableExampleERC721A(); 39 | vm.expectEmit(true, true, false, true, address(example2)); 40 | emit Initialized(1); 41 | example2.initialize(); 42 | vm.expectRevert(bytes("Initializable: contract is already initialized")); 43 | example2.initialize(); 44 | } 45 | 46 | function testFilter() public { 47 | vm.startPrank(address(filteredAddress)); 48 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 49 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 50 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 51 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 52 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 53 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 54 | } 55 | 56 | function testOwnersNotExcluded() public { 57 | address alice = address(0xA11CE); 58 | example.mint(alice, 1); 59 | 60 | vm.prank(DEFAULT_SUBSCRIPTION); 61 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 62 | 63 | vm.prank(alice); 64 | example.transferFrom(alice, makeAddr("to"), 0); 65 | } 66 | 67 | function testOwnersNotExcludedSafeTransfer() public { 68 | address alice = address(0xA11CE); 69 | example.mint(alice, 2); 70 | 71 | vm.prank(DEFAULT_SUBSCRIPTION); 72 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 73 | 74 | vm.startPrank(alice); 75 | example.safeTransferFrom(alice, makeAddr("to"), 0); 76 | example.safeTransferFrom(alice, makeAddr("to"), 1, ""); 77 | } 78 | 79 | function testExclusionExceptionDoesNotApplyToOperators() public { 80 | address alice = address(0xA11CE); 81 | address bob = address(0xB0B); 82 | example.mint(bob, 1); 83 | vm.prank(bob); 84 | example.setApprovalForAll(alice, true); 85 | 86 | vm.prank(DEFAULT_SUBSCRIPTION); 87 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 88 | 89 | vm.startPrank(alice); 90 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 91 | example.transferFrom(bob, makeAddr("to"), 0); 92 | } 93 | 94 | function testExcludeApprovals() public { 95 | address alice = address(0xA11CE); 96 | address bob = address(0xB0B); 97 | example.mint(bob, 1); 98 | 99 | vm.prank(DEFAULT_SUBSCRIPTION); 100 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 101 | 102 | vm.startPrank(bob); 103 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 104 | example.setApprovalForAll(alice, true); 105 | 106 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 107 | example.approve(alice, 0); 108 | } 109 | 110 | function testSetOperatorFilteringEnabled() public { 111 | uint256 randomness = uint256(keccak256(abi.encode(123))); 112 | address alice = address(0xA11CE); 113 | address to = makeAddr("to"); 114 | example.setOperatorFilteringEnabled(false); 115 | vm.prank(alice); 116 | example.setApprovalForAll(address(filteredAddress), true); 117 | 118 | example.mint(alice, 128); 119 | for (uint256 i = 0; i < 128; ++i) { 120 | bool enabled = randomness & 1 == 0; 121 | vm.prank(example.owner()); 122 | example.setOperatorFilteringEnabled(enabled); 123 | 124 | vm.prank(address(filteredAddress)); 125 | if (enabled) { 126 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 127 | } 128 | example.transferFrom(alice, to, i); 129 | 130 | randomness = randomness >> 1; 131 | } 132 | 133 | for (uint256 i = 0; i < 128; ++i) { 134 | bool enabled = randomness & 1 == 0; 135 | vm.prank(example.owner()); 136 | example.setOperatorFilteringEnabled(enabled); 137 | 138 | vm.prank(alice); 139 | if (enabled) { 140 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 141 | } 142 | example.setApprovalForAll(address(filteredAddress), true); 143 | randomness = randomness >> 1; 144 | } 145 | } 146 | 147 | function testRepeatRegistrationOk() public { 148 | vm.prank(example.owner()); 149 | example.repeatRegistration(); 150 | testSetOperatorFilteringEnabled(); 151 | } 152 | 153 | function testSupportsInterface() public { 154 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 155 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 156 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 157 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 158 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/example/upgradeable/ExampleERC721Upgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleERC721Upgradeable} from 5 | "../../../src/example/upgradeable/ExampleERC721Upgradeable.sol"; 6 | import {BaseRegistryTest} from "../../BaseRegistryTest.sol"; 7 | import {Initializable} from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | 9 | contract TestableExampleERC721 is ExampleERC721Upgradeable { 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId); 12 | } 13 | 14 | function repeatRegistration() public { 15 | _registerForOperatorFiltering(); 16 | } 17 | } 18 | 19 | contract ExampleERC721UpgradeableTest is BaseRegistryTest, Initializable { 20 | TestableExampleERC721 example; 21 | address filteredAddress; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | 26 | vm.startPrank(DEFAULT_SUBSCRIPTION); 27 | registry.register(DEFAULT_SUBSCRIPTION); 28 | 29 | filteredAddress = makeAddr("filtered address"); 30 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 31 | vm.stopPrank(); 32 | 33 | example = new TestableExampleERC721(); 34 | example.initialize(); 35 | } 36 | 37 | function testUpgradeable() public { 38 | TestableExampleERC721 example2 = new TestableExampleERC721(); 39 | vm.expectEmit(true, true, false, true, address(example2)); 40 | emit Initialized(1); 41 | example2.initialize(); 42 | vm.expectRevert(bytes("Initializable: contract is already initialized")); 43 | example2.initialize(); 44 | } 45 | 46 | function testFilter() public { 47 | vm.startPrank(address(filteredAddress)); 48 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 49 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 50 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 51 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 52 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 53 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 54 | } 55 | 56 | function testOwnersNotExcluded() public { 57 | address alice = address(0xA11CE); 58 | example.mint(alice, 1); 59 | 60 | vm.prank(DEFAULT_SUBSCRIPTION); 61 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 62 | 63 | vm.prank(alice); 64 | example.transferFrom(alice, makeAddr("to"), 1); 65 | } 66 | 67 | function testOwnersNotExcludedSafeTransfer() public { 68 | address alice = address(0xA11CE); 69 | example.mint(alice, 1); 70 | example.mint(alice, 2); 71 | 72 | vm.prank(DEFAULT_SUBSCRIPTION); 73 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 74 | 75 | vm.startPrank(alice); 76 | example.safeTransferFrom(alice, makeAddr("to"), 1); 77 | example.safeTransferFrom(alice, makeAddr("to"), 2, ""); 78 | } 79 | 80 | function testExclusionExceptionDoesNotApplyToOperators() public { 81 | address alice = address(0xA11CE); 82 | address bob = address(0xB0B); 83 | example.mint(bob, 1); 84 | vm.prank(bob); 85 | example.setApprovalForAll(alice, true); 86 | 87 | vm.prank(DEFAULT_SUBSCRIPTION); 88 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 89 | 90 | vm.startPrank(alice); 91 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 92 | example.transferFrom(bob, makeAddr("to"), 1); 93 | } 94 | 95 | function testExcludeApprovals() public { 96 | address alice = address(0xA11CE); 97 | address bob = address(0xB0B); 98 | example.mint(bob, 1); 99 | 100 | vm.prank(DEFAULT_SUBSCRIPTION); 101 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 102 | 103 | vm.startPrank(bob); 104 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 105 | example.setApprovalForAll(alice, true); 106 | 107 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 108 | example.approve(alice, 1); 109 | } 110 | 111 | function testSetOperatorFilteringEnabled() public { 112 | uint256 randomness = uint256(keccak256(abi.encode(123))); 113 | address alice = address(0xA11CE); 114 | address to = makeAddr("to"); 115 | example.setOperatorFilteringEnabled(false); 116 | vm.prank(alice); 117 | example.setApprovalForAll(address(filteredAddress), true); 118 | 119 | for (uint256 i = 0; i < 128; ++i) { 120 | example.mint(alice, i); 121 | bool enabled = randomness & 1 == 0; 122 | vm.prank(example.owner()); 123 | example.setOperatorFilteringEnabled(enabled); 124 | 125 | vm.prank(address(filteredAddress)); 126 | if (enabled) { 127 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 128 | } 129 | example.transferFrom(alice, to, i); 130 | 131 | randomness = randomness >> 1; 132 | } 133 | 134 | for (uint256 i = 0; i < 128; ++i) { 135 | bool enabled = randomness & 1 == 0; 136 | vm.prank(example.owner()); 137 | example.setOperatorFilteringEnabled(enabled); 138 | 139 | vm.prank(alice); 140 | if (enabled) { 141 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 142 | } 143 | example.setApprovalForAll(address(filteredAddress), true); 144 | randomness = randomness >> 1; 145 | } 146 | } 147 | 148 | function testRepeatRegistrationOk() public { 149 | vm.prank(example.owner()); 150 | example.repeatRegistration(); 151 | testSetOperatorFilteringEnabled(); 152 | } 153 | 154 | function testSupportsInterface() public { 155 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 156 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 157 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 158 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 159 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/example/upgradeable/ExampleSoladyERC1155Upgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleSoladyERC1155Upgradeable} from 5 | "../../../src/example/upgradeable/ExampleSoladyERC1155Upgradeable.sol"; 6 | import {BaseRegistryTest} from "../../BaseRegistryTest.sol"; 7 | import {Initializable} from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | 9 | contract TestableExampleSoladyERC1155 is ExampleSoladyERC1155Upgradeable { 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId, 1, ""); 12 | } 13 | 14 | function repeatRegistration() public { 15 | _registerForOperatorFiltering(); 16 | } 17 | } 18 | 19 | contract ExampleSoladyERC1155UpgradeableTest is BaseRegistryTest, Initializable { 20 | TestableExampleSoladyERC1155 example; 21 | address filteredAddress; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | 26 | vm.startPrank(DEFAULT_SUBSCRIPTION); 27 | registry.register(DEFAULT_SUBSCRIPTION); 28 | 29 | filteredAddress = makeAddr("filtered address"); 30 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 31 | vm.stopPrank(); 32 | 33 | example = new TestableExampleSoladyERC1155(); 34 | example.initialize(); 35 | } 36 | 37 | function testUpgradeable() public { 38 | TestableExampleSoladyERC1155 example2 = new TestableExampleSoladyERC1155(); 39 | vm.expectEmit(true, true, false, true, address(example2)); 40 | emit Initialized(1); 41 | example2.initialize(); 42 | vm.expectRevert(bytes("Initializable: contract is already initialized")); 43 | example2.initialize(); 44 | } 45 | 46 | function testFilter() public { 47 | vm.startPrank(address(filteredAddress)); 48 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 49 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, 1, ""); 50 | uint256[] memory ids = new uint256[](1); 51 | ids[0] = 1; 52 | uint256[] memory amounts = new uint256[](1); 53 | amounts[0] = 1; 54 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 55 | example.safeBatchTransferFrom(makeAddr("from"), makeAddr("to"), ids, amounts, ""); 56 | } 57 | 58 | function testOwnersNotExcluded() public { 59 | address alice = address(0xA11CE); 60 | example.mint(alice, 1); 61 | 62 | vm.prank(DEFAULT_SUBSCRIPTION); 63 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 64 | 65 | vm.prank(alice); 66 | example.safeTransferFrom(alice, makeAddr("to"), 1, 1, ""); 67 | } 68 | 69 | function testOwnersNotExcludedBatch() public { 70 | address alice = address(0xA11CE); 71 | example.mint(alice, 1); 72 | uint256[] memory ids = new uint256[](1); 73 | ids[0] = 1; 74 | uint256[] memory amounts = new uint256[](1); 75 | amounts[0] = 1; 76 | 77 | vm.prank(DEFAULT_SUBSCRIPTION); 78 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 79 | 80 | vm.prank(alice); 81 | example.safeBatchTransferFrom(alice, makeAddr("to"), ids, amounts, ""); 82 | } 83 | 84 | function testExclusionExceptionDoesNotApplyToOperators() public { 85 | address alice = address(0xA11CE); 86 | address bob = address(0xB0B); 87 | example.mint(bob, 1); 88 | vm.prank(bob); 89 | example.setApprovalForAll(alice, true); 90 | 91 | vm.prank(DEFAULT_SUBSCRIPTION); 92 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 93 | 94 | vm.startPrank(alice); 95 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 96 | example.safeTransferFrom(bob, makeAddr("to"), 1, 1, ""); 97 | } 98 | 99 | function testExcludeApprovals() public { 100 | address alice = address(0xA11CE); 101 | address bob = address(0xB0B); 102 | example.mint(bob, 1); 103 | 104 | vm.prank(DEFAULT_SUBSCRIPTION); 105 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 106 | 107 | vm.startPrank(bob); 108 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 109 | example.setApprovalForAll(alice, true); 110 | } 111 | 112 | function testSetOperatorFilteringEnabled() public { 113 | uint256 randomness = uint256(keccak256(abi.encode(123))); 114 | address alice = address(0xA11CE); 115 | address to = makeAddr("to"); 116 | example.setOperatorFilteringEnabled(false); 117 | vm.prank(alice); 118 | example.setApprovalForAll(address(filteredAddress), true); 119 | 120 | for (uint256 i = 0; i < 128; ++i) { 121 | example.mint(alice, i); 122 | bool enabled = randomness & 1 == 0; 123 | vm.prank(example.owner()); 124 | example.setOperatorFilteringEnabled(enabled); 125 | 126 | vm.prank(address(filteredAddress)); 127 | if (enabled) { 128 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 129 | } 130 | example.safeTransferFrom(alice, to, i, 1, ""); 131 | 132 | randomness = randomness >> 1; 133 | } 134 | 135 | for (uint256 i = 0; i < 128; ++i) { 136 | bool enabled = randomness & 1 == 0; 137 | vm.prank(example.owner()); 138 | example.setOperatorFilteringEnabled(enabled); 139 | 140 | vm.prank(alice); 141 | if (enabled) { 142 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 143 | } 144 | example.setApprovalForAll(address(filteredAddress), true); 145 | randomness = randomness >> 1; 146 | } 147 | } 148 | 149 | function testRepeatRegistrationOk() public { 150 | vm.prank(example.owner()); 151 | example.repeatRegistration(); 152 | testSetOperatorFilteringEnabled(); 153 | } 154 | 155 | function testSupportsInterface() public { 156 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 157 | assertTrue(example.supportsInterface(0xd9b67a26)); // IERC1155 158 | assertTrue(example.supportsInterface(0x0e89341c)); // IERC1155MetadataURI 159 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 160 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/example/upgradeable/ExampleSoladyERC721Upgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {ExampleSoladyERC721Upgradeable} from 5 | "../../../src/example/upgradeable/ExampleSoladyERC721Upgradeable.sol"; 6 | import {BaseRegistryTest} from "../../BaseRegistryTest.sol"; 7 | import {Initializable} from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | 9 | contract TestableExampleSoladyERC721 is ExampleSoladyERC721Upgradeable { 10 | function mint(address to, uint256 tokenId) external { 11 | _mint(to, tokenId); 12 | } 13 | 14 | function repeatRegistration() public { 15 | _registerForOperatorFiltering(); 16 | } 17 | } 18 | 19 | contract ExampleSoladyERC721UpgradeableTest is BaseRegistryTest, Initializable { 20 | TestableExampleSoladyERC721 example; 21 | address filteredAddress; 22 | 23 | function setUp() public override { 24 | super.setUp(); 25 | 26 | vm.startPrank(DEFAULT_SUBSCRIPTION); 27 | registry.register(DEFAULT_SUBSCRIPTION); 28 | 29 | filteredAddress = makeAddr("filtered address"); 30 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), filteredAddress, true); 31 | vm.stopPrank(); 32 | 33 | example = new TestableExampleSoladyERC721(); 34 | example.initialize(); 35 | } 36 | 37 | function testUpgradeable() public { 38 | TestableExampleSoladyERC721 example2 = new TestableExampleSoladyERC721(); 39 | vm.expectEmit(true, true, false, true, address(example2)); 40 | emit Initialized(1); 41 | example2.initialize(); 42 | vm.expectRevert(bytes("Initializable: contract is already initialized")); 43 | example2.initialize(); 44 | } 45 | 46 | function testFilter() public { 47 | vm.startPrank(address(filteredAddress)); 48 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 49 | example.transferFrom(makeAddr("from"), makeAddr("to"), 1); 50 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 51 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1); 52 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 53 | example.safeTransferFrom(makeAddr("from"), makeAddr("to"), 1, ""); 54 | } 55 | 56 | function testOwnersNotExcluded() public { 57 | address alice = address(0xA11CE); 58 | example.mint(alice, 1); 59 | 60 | vm.prank(DEFAULT_SUBSCRIPTION); 61 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 62 | 63 | vm.prank(alice); 64 | example.transferFrom(alice, makeAddr("to"), 1); 65 | } 66 | 67 | function testOwnersNotExcludedSafeTransfer() public { 68 | address alice = address(0xA11CE); 69 | example.mint(alice, 1); 70 | example.mint(alice, 2); 71 | 72 | vm.prank(DEFAULT_SUBSCRIPTION); 73 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 74 | 75 | vm.startPrank(alice); 76 | example.safeTransferFrom(alice, makeAddr("to"), 1); 77 | example.safeTransferFrom(alice, makeAddr("to"), 2, ""); 78 | } 79 | 80 | function testExclusionExceptionDoesNotApplyToOperators() public { 81 | address alice = address(0xA11CE); 82 | address bob = address(0xB0B); 83 | example.mint(bob, 1); 84 | vm.prank(bob); 85 | example.setApprovalForAll(alice, true); 86 | 87 | vm.prank(DEFAULT_SUBSCRIPTION); 88 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 89 | 90 | vm.startPrank(alice); 91 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 92 | example.transferFrom(bob, makeAddr("to"), 1); 93 | } 94 | 95 | function testExcludeApprovals() public { 96 | address alice = address(0xA11CE); 97 | address bob = address(0xB0B); 98 | example.mint(bob, 1); 99 | 100 | vm.prank(DEFAULT_SUBSCRIPTION); 101 | registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true); 102 | 103 | vm.startPrank(bob); 104 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 105 | example.setApprovalForAll(alice, true); 106 | 107 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, alice)); 108 | example.approve(alice, 1); 109 | } 110 | 111 | function testSetOperatorFilteringEnabled() public { 112 | uint256 randomness = uint256(keccak256(abi.encode(123))); 113 | address alice = address(0xA11CE); 114 | address to = makeAddr("to"); 115 | example.setOperatorFilteringEnabled(false); 116 | vm.prank(alice); 117 | example.setApprovalForAll(address(filteredAddress), true); 118 | 119 | for (uint256 i = 0; i < 128; ++i) { 120 | example.mint(alice, i); 121 | bool enabled = randomness & 1 == 0; 122 | vm.prank(example.owner()); 123 | example.setOperatorFilteringEnabled(enabled); 124 | 125 | vm.prank(address(filteredAddress)); 126 | if (enabled) { 127 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 128 | } 129 | example.transferFrom(alice, to, i); 130 | 131 | randomness = randomness >> 1; 132 | } 133 | 134 | for (uint256 i = 0; i < 128; ++i) { 135 | bool enabled = randomness & 1 == 0; 136 | vm.prank(example.owner()); 137 | example.setOperatorFilteringEnabled(enabled); 138 | 139 | vm.prank(alice); 140 | if (enabled) { 141 | vm.expectRevert(abi.encodeWithSelector(AddressFiltered.selector, filteredAddress)); 142 | } 143 | example.setApprovalForAll(address(filteredAddress), true); 144 | randomness = randomness >> 1; 145 | } 146 | } 147 | 148 | function testRepeatRegistrationOk() public { 149 | vm.prank(example.owner()); 150 | example.repeatRegistration(); 151 | testSetOperatorFilteringEnabled(); 152 | } 153 | 154 | function testSupportsInterface() public { 155 | assertTrue(example.supportsInterface(0x01ffc9a7)); // IERC165 156 | assertTrue(example.supportsInterface(0x80ac58cd)); // IERC721 157 | assertTrue(example.supportsInterface(0x5b5e139f)); // IERC721Metadata 158 | assertTrue(example.supportsInterface(0x2a55205a)); // IERC2981 159 | assertFalse(example.supportsInterface(0x10101010)); // Some unsupported interface. 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/helpers/DefaultFilterer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../../src/OperatorFilterer.sol"; 5 | 6 | contract DefaultFilterer is OperatorFilterer { 7 | constructor() { 8 | _registerForOperatorFiltering(); 9 | } 10 | 11 | function filter(address from) public view onlyAllowedOperator(from) returns (bool) { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/helpers/Filterer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../../src/OperatorFilterer.sol"; 5 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 6 | import {IOperatorFilterRegistry} from "operator-filter-registry/IOperatorFilterRegistry.sol"; 7 | 8 | contract Filterer is OperatorFilterer, Ownable { 9 | error OperatorNotAllowed(address operator); 10 | 11 | constructor() { 12 | _registerForOperatorFiltering(address(0), false); 13 | /// @solidity memory-safe-assembly 14 | assembly { 15 | if iszero(eq(mload(0x40), 0x80)) { revert(0, 0) } 16 | } 17 | } 18 | 19 | function filter(address from) public view onlyAllowedOperator(from) returns (bool) { 20 | /// @solidity memory-safe-assembly 21 | assembly { 22 | if iszero(eq(mload(0x40), 0x80)) { revert(0, 0) } 23 | } 24 | return true; 25 | } 26 | 27 | function filterOriginal(address from) 28 | public 29 | view 30 | onlyAllowedOperatorOriginal(from) 31 | returns (bool) 32 | { 33 | return true; 34 | } 35 | 36 | modifier onlyAllowedOperatorOriginal(address from) virtual { 37 | if (address(_OPERATOR_FILTER_REGISTRY).code.length > 0) { 38 | if (from == msg.sender) { 39 | _; 40 | return; 41 | } 42 | if ( 43 | !IOperatorFilterRegistry(_OPERATOR_FILTER_REGISTRY).isOperatorAllowed( 44 | address(this), msg.sender 45 | ) 46 | ) { 47 | revert OperatorNotAllowed(msg.sender); 48 | } 49 | } 50 | _; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/helpers/PriorityFilterer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {OperatorFilterer} from "../../src/OperatorFilterer.sol"; 5 | import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; 6 | import {IOperatorFilterRegistry} from "operator-filter-registry/IOperatorFilterRegistry.sol"; 7 | 8 | contract PriorityFilterer is OperatorFilterer, Ownable { 9 | address private immutable _priorityOperator; 10 | 11 | constructor(address priorityOperator) { 12 | _priorityOperator = priorityOperator; 13 | _registerForOperatorFiltering(address(0), false); 14 | } 15 | 16 | function filter(address from) public view onlyAllowedOperator(from) returns (bool) { 17 | return true; 18 | } 19 | 20 | function _isPriorityOperator(address operator) internal view override returns (bool) { 21 | return operator == _priorityOperator; 22 | } 23 | } 24 | --------------------------------------------------------------------------------