Skip to content
This repository was archived by the owner on Sep 1, 2023. It is now read-only.

Commit cd02dc7

Browse files
start updatable
1 parent 356f210 commit cd02dc7

9 files changed

+134
-36
lines changed

src/OperatorFilterer.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ abstract contract OperatorFilterer {
4848
_checkFilterOperator(operator);
4949
_;
5050
}
51-
51+
5252
function _checkFilterOperator(address operator) internal view virtual {
5353
// Check registry code length to facilitate testing in environments without a deployed registry.
5454
if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {

src/RevokableDefaultOperatorFilterer.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
pragma solidity ^0.8.13;
33

44
import {RevokableOperatorFilterer} from "./RevokableOperatorFilterer.sol";
5-
import {OperatorFilterer} from "./OperatorFilterer.sol";
65

76
/**
87
* @title RevokableDefaultOperatorFilterer
@@ -11,5 +10,5 @@ import {OperatorFilterer} from "./OperatorFilterer.sol";
1110
abstract contract RevokableDefaultOperatorFilterer is RevokableOperatorFilterer {
1211
address constant DEFAULT_SUBSCRIPTION = address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6);
1312

14-
constructor() OperatorFilterer(DEFAULT_SUBSCRIPTION, true) {}
13+
constructor() RevokableOperatorFilterer(0x000000000000AAeB6D7670E522A718067333cd4E, DEFAULT_SUBSCRIPTION, true) {}
1514
}

src/RevokableOperatorFilterer.sol

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,51 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.13;
33

4-
import {OperatorFilterer} from "./OperatorFilterer.sol";
4+
import {UpdateableOperatorFilterer} from "./UpdateableOperatorFilterer.sol";
55

66
/**
77
* @title RevokableOperatorFilterer
8-
* @notice This contract is meant to allow contracts to permanently opt out of the OperatorFilterRegistry. The Registry
9-
* itself has an "unregister" function, but if the contract is ownable, the owner can re-register at any point.
10-
* As implemented, this abstract contract allows the contract owner to toggle the
11-
* isOperatorFilterRegistryRevoked flag in order to permanently bypass the OperatorFilterRegistry checks.
8+
* @notice This contract is meant to allow contracts to permanently skip OperatorFilterRegistry checks if desired. The
9+
* Registry itself has an "unregister" function, but if the contract is ownable, the owner can re-register at
10+
* any point. As implemented, this abstract contract allows the contract owner to permanently skip the
11+
* OperatorFilterRegistry checks by passing the zero address to updateRegistryAddress. Once done, the registry
12+
* address cannot be further updated.
1213
*/
13-
abstract contract RevokableOperatorFilterer is OperatorFilterer {
14-
error OnlyOwner();
15-
error AlreadyRevoked();
14+
abstract contract RevokableOperatorFilterer is UpdateableOperatorFilterer {
15+
error RegistryHasBeenRevoked();
16+
error RegistryAddressCannotBeZeroAddress();
1617

17-
bool private _isOperatorFilterRegistryRevoked;
18+
constructor(address _registry, address subscriptionOrRegistrantToCopy, bool subscribe)
19+
UpdateableOperatorFilterer(_registry, subscriptionOrRegistrantToCopy, subscribe)
20+
{
21+
// don't allow creating a contract with a permanently revoked registry
22+
if (_registry == address(0)) {
23+
revert RegistryAddressCannotBeZeroAddress();
24+
}
25+
}
1826

1927
function _checkFilterOperator(address operator) internal view virtual override {
20-
if (!_isOperatorFilterRegistryRevoked) {
28+
if (address(operatorFilterRegistry) != address(0)) {
2129
super._checkFilterOperator(operator);
2230
}
2331
}
2432

2533
/**
26-
* @notice Disable the isOperatorFilterRegistryRevoked flag. OnlyOwner.
34+
* @notice Update the address that the contract will make OperatorFilter checks against. When set to the zero
35+
* address, checks will be permanently bypassed, and the address cannot be updated again. OnlyOwner.
2736
*/
28-
function revokeOperatorFilterRegistry() external {
37+
function updateOperatorFilterRegistryAddress(address newRegistry) public override {
2938
if (msg.sender != owner()) {
3039
revert OnlyOwner();
3140
}
32-
if (_isOperatorFilterRegistryRevoked) {
33-
revert AlreadyRevoked();
41+
// if registry address has been set to 0 (revoked), do not allow further updates
42+
if (address(operatorFilterRegistry) == address(0)) {
43+
revert RegistryHasBeenRevoked();
3444
}
35-
_isOperatorFilterRegistryRevoked = true;
45+
super.updateOperatorFilterRegistryAddress(newRegistry);
3646
}
3747

3848
function isOperatorFilterRegistryRevoked() public view returns (bool) {
39-
return _isOperatorFilterRegistryRevoked;
49+
return address(operatorFilterRegistry) == address(0);
4050
}
41-
42-
/**
43-
* @dev assume the contract has an owner, but leave specific Ownable implementation up to inheriting contract
44-
*/
45-
function owner() public view virtual returns (address);
4651
}

src/UpdateableOperatorFilterer.sol

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.13;
3+
4+
import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
5+
6+
/**
7+
* @title UpdateableOperatorFilterer
8+
* @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
9+
* registrant's entries in the OperatorFilterRegistry. This contract allows the Owner to update the
10+
* OperatorFilterRegistry address via updateOperatorFilterRegistryAddress, including to the zero address,
11+
* which will bypass registry checks.
12+
* @dev This smart contract is meant to be inherited by token contracts so they can use the following:
13+
* - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
14+
* - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
15+
*/
16+
abstract contract UpdateableOperatorFilterer {
17+
error OperatorNotAllowed(address operator);
18+
error OnlyOwner();
19+
20+
event OperatorFilterRegistryUpdated(address previousRegistryAddress, address newRegistryAddress);
21+
22+
IOperatorFilterRegistry public operatorFilterRegistry;
23+
24+
constructor(address _registry, address subscriptionOrRegistrantToCopy, bool subscribe) {
25+
IOperatorFilterRegistry registry = IOperatorFilterRegistry(_registry);
26+
operatorFilterRegistry = registry;
27+
// If an inheriting token contract is deployed to a network without the registry deployed, the modifier
28+
// will not revert, but the contract will need to be registered with the registry once it is deployed in
29+
// order for the modifier to filter addresses.
30+
if (address(registry).code.length > 0) {
31+
if (subscribe) {
32+
registry.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
33+
} else {
34+
if (subscriptionOrRegistrantToCopy != address(0)) {
35+
registry.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
36+
} else {
37+
registry.register(address(this));
38+
}
39+
}
40+
}
41+
}
42+
43+
modifier onlyAllowedOperator(address from) virtual {
44+
// Allow spending tokens from addresses with balance
45+
// Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
46+
// from an EOA.
47+
if (from != msg.sender) {
48+
_checkFilterOperator(msg.sender);
49+
}
50+
_;
51+
}
52+
53+
modifier onlyAllowedOperatorApproval(address operator) virtual {
54+
_checkFilterOperator(operator);
55+
_;
56+
}
57+
58+
/**
59+
* @notice Update the address that the contract will make OperatorFilter checks against. When set to the zero
60+
* address, checks will be bypassed. OnlyOwner.
61+
*/
62+
function updateOperatorFilterRegistryAddress(address newRegistry) public virtual {
63+
if (msg.sender != owner()) {
64+
revert OnlyOwner();
65+
}
66+
_updateRegistryAddress(newRegistry);
67+
}
68+
69+
function _updateRegistryAddress(address newRegistry) internal {
70+
address oldRegistry = address(operatorFilterRegistry);
71+
operatorFilterRegistry = IOperatorFilterRegistry(newRegistry);
72+
emit OperatorFilterRegistryUpdated(oldRegistry, newRegistry);
73+
}
74+
75+
/**
76+
* @dev assume the contract has an owner, but leave specific Ownable implementation up to inheriting contract
77+
*/
78+
function owner() public view virtual returns (address);
79+
80+
function _checkFilterOperator(address operator) internal view virtual {
81+
IOperatorFilterRegistry registry = operatorFilterRegistry;
82+
// Check registry code length to facilitate testing in environments without a deployed registry.
83+
if (address(registry) != address(0) && address(registry).code.length > 0) {
84+
if (!registry.isOperatorAllowed(address(this), operator)) {
85+
revert OperatorNotAllowed(operator);
86+
}
87+
}
88+
}
89+
}

src/example/RevokableExampleERC1155.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.13;
33

44
import {ERC1155} from "openzeppelin-contracts/token/ERC1155/ERC1155.sol";
5-
import {RevokableOperatorFilterer} from "../RevokableOperatorFilterer.sol";
5+
import {UpdateableOperatorFilterer} from "../UpdateableOperatorFilterer.sol";
66
import {RevokableDefaultOperatorFilterer} from "../RevokableDefaultOperatorFilterer.sol";
77
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
88

@@ -38,7 +38,7 @@ abstract contract RevokableExampleERC1155 is ERC1155(""), RevokableDefaultOperat
3838
super.safeBatchTransferFrom(from, to, ids, amounts, data);
3939
}
4040

41-
function owner() public view virtual override (Ownable, RevokableOperatorFilterer) returns (address) {
41+
function owner() public view virtual override (Ownable, UpdateableOperatorFilterer) returns (address) {
4242
return Ownable.owner();
4343
}
4444
}

src/example/RevokableExampleERC721.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.13;
33

44
import {ERC721} from "openzeppelin-contracts/token/ERC721/ERC721.sol";
5-
import {RevokableOperatorFilterer} from "../RevokableOperatorFilterer.sol";
5+
import {UpdateableOperatorFilterer} from "../UpdateableOperatorFilterer.sol";
66
import {RevokableDefaultOperatorFilterer} from "../RevokableDefaultOperatorFilterer.sol";
77
import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
88

@@ -40,7 +40,7 @@ abstract contract RevokableExampleERC721 is ERC721("Example", "EXAMPLE"), Revoka
4040
super.safeTransferFrom(from, to, tokenId, data);
4141
}
4242

43-
function owner() public view virtual override (Ownable, RevokableOperatorFilterer) returns (address) {
43+
function owner() public view virtual override (Ownable, UpdateableOperatorFilterer) returns (address) {
4444
return Ownable.owner();
4545
}
4646
}

test/RevokableDefaultOperatorFilterer.t.sol

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.13;
33

4-
import {RevokableDefaultOperatorFilterer} from "../src/RevokableDefaultOperatorFilterer.sol";
4+
import {RevokableDefaultOperatorFilterer} from "src/RevokableDefaultOperatorFilterer.sol";
5+
import {RevokableOperatorFilterer} from "src/RevokableOperatorFilterer.sol";
56
import {BaseRegistryTest} from "./BaseRegistryTest.sol";
67
import {RevokableDefaultFilterer} from "./helpers/RevokableDefaultFilterer.sol";
78

@@ -49,18 +50,22 @@ contract RevokableDefaultOperatorFiltererTest is BaseRegistryTest {
4950
vm.stopPrank();
5051

5152
vm.startPrank(DEFAULT_SUBSCRIPTION);
52-
filterer.revokeOperatorFilterRegistry();
53+
filterer.updateOperatorFilterRegistryAddress(address(0));
5354
assertTrue(filterer.isOperatorFilterRegistryRevoked());
5455
vm.stopPrank();
5556
vm.expectRevert(abi.encodeWithSignature("OnlyOwner()"));
56-
filterer.revokeOperatorFilterRegistry();
57+
filterer.updateOperatorFilterRegistryAddress(address(0));
5758
vm.startPrank(DEFAULT_SUBSCRIPTION);
58-
vm.expectRevert(abi.encodeWithSignature("AlreadyRevoked()"));
59-
filterer.revokeOperatorFilterRegistry();
59+
vm.expectRevert(abi.encodeWithSignature("RegistryHasBeenRevoked()"));
60+
filterer.updateOperatorFilterRegistryAddress(address(0));
6061
vm.stopPrank();
6162

6263
vm.startPrank(filteredAddress);
6364
assertTrue(filterer.filterTest(address(0)));
6465
vm.stopPrank();
6566
}
67+
68+
function testConstructor_zeroAddress() public {
69+
70+
}
6671
}

test/example/RevokableERC1155.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ contract RevokeExampleERC1155Test is BaseRegistryTest {
104104
vm.prank(DEFAULT_SUBSCRIPTION);
105105
registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true);
106106

107-
example.revokeOperatorFilterRegistry();
107+
example.updateOperatorFilterRegistryAddress(address(0));
108108

109109
vm.prank(bob);
110110
example.setApprovalForAll(alice, true);

test/example/RevokableERC721.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ contract TestableExampleERC721 is RevokableExampleERC721 {
1010
}
1111
}
1212

13-
contract ExampleERC721Test is BaseRegistryTest {
13+
contract RevokableExampleERC721Test is BaseRegistryTest {
1414
TestableExampleERC721 example;
1515
address filteredAddress;
1616

@@ -102,7 +102,7 @@ contract ExampleERC721Test is BaseRegistryTest {
102102
vm.prank(DEFAULT_SUBSCRIPTION);
103103
registry.updateOperator(address(DEFAULT_SUBSCRIPTION), alice, true);
104104

105-
example.revokeOperatorFilterRegistry();
105+
example.updateOperatorFilterRegistryAddress(address(0));
106106

107107
vm.prank(bob);
108108
example.setApprovalForAll(alice, true);

0 commit comments

Comments
 (0)