Skip to content

Commit f6dffe0

Browse files
authored
Add zone manager role to ImmutableSignedZoneV2 (#210)
1 parent bcd3af9 commit f6dffe0

File tree

8 files changed

+342
-117
lines changed

8 files changed

+342
-117
lines changed

contracts/trading/seaport/zones/immutable-signed-zone/v2/ImmutableSignedZoneV2.sol

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
// solhint-disable-next-line compiler-version
55
pragma solidity ^0.8.20;
66

7-
import {ZoneInterface} from "seaport/contracts/interfaces/ZoneInterface.sol";
8-
import {ZoneParameters, Schema, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol";
97
import {AccessControlEnumerable} from "openzeppelin-contracts-5.0.2/access/extensions/AccessControlEnumerable.sol";
108
import {ECDSA} from "openzeppelin-contracts-5.0.2/utils/cryptography/ECDSA.sol";
119
import {MessageHashUtils} from "openzeppelin-contracts-5.0.2/utils/cryptography/MessageHashUtils.sol";
1210
import {ERC165} from "openzeppelin-contracts-5.0.2/utils/introspection/ERC165.sol";
1311
import {Math} from "openzeppelin-contracts-5.0.2/utils/math/Math.sol";
12+
import {ZoneInterface} from "seaport/contracts/interfaces/ZoneInterface.sol";
13+
import {ZoneParameters, Schema, ReceivedItem} from "seaport-types/src/lib/ConsiderationStructs.sol";
14+
import {ZoneAccessControl} from "./ZoneAccessControl.sol";
1415
import {SIP5Interface} from "./interfaces/SIP5Interface.sol";
1516
import {SIP6Interface} from "./interfaces/SIP6Interface.sol";
1617
import {SIP7Interface} from "./interfaces/SIP7Interface.sol";
@@ -24,11 +25,11 @@ import {SIP7Interface} from "./interfaces/SIP7Interface.sol";
2425
*/
2526
contract ImmutableSignedZoneV2 is
2627
ERC165,
28+
ZoneAccessControl,
2729
ZoneInterface,
2830
SIP5Interface,
2931
SIP6Interface,
30-
SIP7Interface,
31-
AccessControlEnumerable
32+
SIP7Interface
3233
{
3334
/// @dev The EIP-712 domain type hash.
3435
bytes32 private constant _EIP_712_DOMAIN_TYPEHASH = keccak256(
@@ -82,7 +83,9 @@ contract ImmutableSignedZoneV2 is
8283
* @param owner The address of the owner of this contract. Specified in the
8384
* constructor to be CREATE2 / CREATE3 compatible.
8485
*/
85-
constructor(string memory zoneName, string memory apiEndpoint, string memory documentationURI, address owner) {
86+
constructor(string memory zoneName, string memory apiEndpoint, string memory documentationURI, address owner)
87+
ZoneAccessControl(owner)
88+
{
8689
// Set the zone name.
8790
_ZONE_NAME = zoneName;
8891

@@ -100,17 +103,14 @@ contract ImmutableSignedZoneV2 is
100103

101104
// Emit an event to signal a SIP-5 contract has been deployed.
102105
emit SeaportCompatibleContractDeployed();
103-
104-
// Grant admin role to the specified owner.
105-
_grantRole(DEFAULT_ADMIN_ROLE, owner);
106106
}
107107

108108
/**
109109
* @notice Add a new signer to the zone.
110110
*
111111
* @param signer The new signer address to add.
112112
*/
113-
function addSigner(address signer) external override onlyRole(DEFAULT_ADMIN_ROLE) {
113+
function addSigner(address signer) external override onlyRole(ZONE_MANAGER_ROLE) {
114114
// Do not allow the zero address to be added as a signer.
115115
if (signer == address(0)) {
116116
revert SignerCannotBeZeroAddress();
@@ -140,7 +140,7 @@ contract ImmutableSignedZoneV2 is
140140
*
141141
* @param signer The signer address to remove.
142142
*/
143-
function removeSigner(address signer) external override onlyRole(DEFAULT_ADMIN_ROLE) {
143+
function removeSigner(address signer) external override onlyRole(ZONE_MANAGER_ROLE) {
144144
// Revert if the signer is not active.
145145
if (!_signers[signer].active) {
146146
revert SignerNotActive(signer);
@@ -158,7 +158,7 @@ contract ImmutableSignedZoneV2 is
158158
*
159159
* @param newApiEndpoint The new API endpoint.
160160
*/
161-
function updateAPIEndpoint(string calldata newApiEndpoint) external override onlyRole(DEFAULT_ADMIN_ROLE) {
161+
function updateAPIEndpoint(string calldata newApiEndpoint) external override onlyRole(ZONE_MANAGER_ROLE) {
162162
_apiEndpoint = newApiEndpoint;
163163
}
164164

@@ -170,7 +170,7 @@ contract ImmutableSignedZoneV2 is
170170
function updateDocumentationURI(string calldata newDocumentationURI)
171171
external
172172
override
173-
onlyRole(DEFAULT_ADMIN_ROLE)
173+
onlyRole(ZONE_MANAGER_ROLE)
174174
{
175175
_documentationURI = newDocumentationURI;
176176
}

contracts/trading/seaport/zones/immutable-signed-zone/v2/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ flowchart LR
3333
seaport -- 3a. transferFrom --> erc20[IERC20.sol]
3434
seaport -- 3b. transferFrom --> erc721[IERC721.sol]
3535
seaport -- 3c. safeTransferFrom --> erc1155[IERC1155.sol]
36-
seaport -- 4. validateOrder --> zone[ImmutableSignedZoneV2.sol]
36+
seaport -- 4. validateOrder --> Zone
37+
subgraph Zone
38+
direction TB
39+
zone[ImmutableSignedZoneV2.sol] --> AccessControlEnumerable.sol
40+
end
3741
```
3842

3943
The sequence of events is as follows:
4044

41-
1. The client makes a HTTP `POST .../fulfillment-data` request to the Immutable Orderbook, which will construct signs and sign an `extraData` payload to return to the client
45+
1. The client makes a HTTP `POST .../fulfillment-data` request to the Immutable Orderbook, which will construct and sign an `extraData` payload to return to the client
4246
2. The client calls `fulfillAdvancedOrder` or `fulfillAvailableAdavancedOrders` on `ImmutableSeaport.sol` to fulfill an order
4347
3. `ImmutableSeaport.sol` executes the fufilment by transferring items between parties
4448
4. `ImmutableSeaport.sol` calls `validateOrder` on `ImmutableSignedZoneV2.sol`, passing it the fulfilment execution details as well as the `extraData` parameter
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: Apache-2
3+
4+
// solhint-disable-next-line compiler-version
5+
pragma solidity ^0.8.20;
6+
7+
import {AccessControl} from "openzeppelin-contracts-5.0.2/access/AccessControl.sol";
8+
import {IAccessControl} from "openzeppelin-contracts-5.0.2/access/IAccessControl.sol";
9+
import {AccessControlEnumerable} from "openzeppelin-contracts-5.0.2/access/extensions/AccessControlEnumerable.sol";
10+
import {ZoneAccessControlEventsAndErrors} from
11+
"../../../../../../contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/ZoneAccessControlEventsAndErrors.sol";
12+
13+
/**
14+
* @notice ZoneAccessControl encapsulates access control functionality for the zone.
15+
*/
16+
abstract contract ZoneAccessControl is AccessControlEnumerable, ZoneAccessControlEventsAndErrors {
17+
/// @dev Zone manager manages the zone.
18+
bytes32 public constant ZONE_MANAGER_ROLE = bytes32("ZONE_MANAGER");
19+
20+
/**
21+
* @notice Constructor to setup initial default admin.
22+
*
23+
* @param owner The address to assign the DEFAULT_ADMIN_ROLE.
24+
*/
25+
constructor(address owner) {
26+
// Grant admin role to the specified owner.
27+
_grantRole(DEFAULT_ADMIN_ROLE, owner);
28+
}
29+
30+
/**
31+
* @inheritdoc AccessControl
32+
*/
33+
function revokeRole(bytes32 role, address account) public override(AccessControl, IAccessControl) onlyRole(getRoleAdmin(role)) {
34+
super.revokeRole(role, account);
35+
36+
if (role == DEFAULT_ADMIN_ROLE && super.getRoleMemberCount(DEFAULT_ADMIN_ROLE) == 0) {
37+
revert LastDefaultAdminRole(account);
38+
}
39+
}
40+
41+
/**
42+
* @inheritdoc AccessControl
43+
*/
44+
function renounceRole(bytes32 role, address callerConfirmation) public override(AccessControl, IAccessControl) {
45+
super.renounceRole(role, callerConfirmation);
46+
47+
if (role == DEFAULT_ADMIN_ROLE && super.getRoleMemberCount(DEFAULT_ADMIN_ROLE) == 0) {
48+
revert LastDefaultAdminRole(callerConfirmation);
49+
}
50+
}
51+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: Apache-2
3+
4+
// solhint-disable compiler-version
5+
pragma solidity ^0.8.17;
6+
7+
/**
8+
* @notice ZoneAccessControlEventsAndErrors contains errors and events
9+
* related to zone access control.
10+
*/
11+
interface ZoneAccessControlEventsAndErrors {
12+
/**
13+
* @dev Revert with an error if revoking last DEFAULT_ADMIN_ROLE.
14+
*/
15+
error LastDefaultAdminRole(address account);
16+
}

test/trading/seaport/ImmutableSeaportSignedZoneV2Integration.t.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ contract ImmutableSeaportSignedZoneV2IntegrationTest is Test, SigningTestHelper
4141
"./foundry-out/ImmutableSignedZoneV2Harness.t.sol/ImmutableSignedZoneV2Harness.json";
4242

4343
address private immutable OWNER = makeAddr("owner");
44+
address private immutable ZONE_MANAGER = makeAddr("zone_manager");
4445
address private immutable SIGNER;
4546
uint256 private immutable SIGNER_PRIVATE_KEY;
4647
address private immutable FULFILLER = makeAddr("fulfiller");
@@ -98,6 +99,10 @@ contract ImmutableSeaportSignedZoneV2IntegrationTest is Test, SigningTestHelper
9899
)
99100
);
100101
vm.prank(OWNER);
102+
bytes32 managerRole = zone.ZONE_MANAGER_ROLE();
103+
vm.prank(OWNER);
104+
zone.grantRole(managerRole, ZONE_MANAGER);
105+
vm.prank(ZONE_MANAGER);
101106
zone.addSigner(SIGNER);
102107

103108
// seaport

test/trading/seaport/zones/immutable-signed-zone/v2/IImmutableSignedZoneV2Harness.t.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import {SIP7Interface} from
1212
// solhint-disable func-name-mixedcase
1313

1414
interface IImmutableSignedZoneV2Harness is ZoneInterface, SIP7Interface {
15+
function grantRole(bytes32 role, address account) external;
16+
17+
function DEFAULT_ADMIN_ROLE() external view returns (bytes32);
18+
19+
function ZONE_MANAGER_ROLE() external view returns (bytes32);
20+
1521
function exposed_domainSeparator() external view returns (bytes32);
1622

1723
function exposed_deriveDomainSeparator() external view returns (bytes32 domainSeparator);

0 commit comments

Comments
 (0)