@cofhe/mock-contracts 0.7.1 → 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @cofhe/mock-contracts Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6f7a4c5: Resolve the Task Manager through the `CoFHEAddressBook` (`@fhenixprotocol/cofhe-contracts@1.0.0`). **Breaking:** `TASK_MANAGER_ADDRESS` is removed from `@cofhe/sdk`. Use `getTaskManagerAddress(publicClient)`, which reads `getTm(TASK_MANAGER_ID)` from the book at `COFHE_ADDRESS_BOOK_ADDRESS` (both exported) and caches the result per chain. If the chain has no book, or the id is unset, it throws `TASK_MANAGER_UNRESOLVED`. `verifyDecryptResult`, the ACL/ACP lookups and `useCofheEnabled` / `useCofheReadDecryptionResults` now resolve the Task Manager this way. `@cofhe/react` adds `useCofheTaskManagerAddress()`.
8
+
9
+ Mocks: a new `MockCoFHEAddressBook` is deployed at FHE.sol's `COFHE_ADDRESS_BOOK`, and `MockTaskManager` moves to `MOCKS_TASK_MANAGER_ADDRESS` (`0x…5000`) and is registered in the book. `MockACL` now stores its Task Manager (`setTaskManager` / `getTaskManagerAddress`) instead of reading a constant, so its `TASK_MANAGER_ADDRESS_()` getter is gone. All three plugins deploy and wire the book. Hardhat 2 exposes `hre.cofhe.mocks.getMockCoFHEAddressBook()`, Hardhat 3 exposes `conn.cofhe.mocks.MockCoFHEAddressBook`, and Foundry exposes `CofheTest.mockAddressBook`. Contracts must be built against `@fhenixprotocol/cofhe-contracts@1.0.0` or later (the hardhat plugin's peer range is now `>=1.0.0`). Each FHE op now makes one extra `getTm` call on the book, as it does on real networks, so expect slightly higher gas numbers.
10
+
11
+ ### Patch Changes
12
+
13
+ - ccb7b68: Realistic gas reporting for mocks. Under Foundry, mock-only work is now excluded from gas metering by default (opt out with `mockTaskManager.setMockGasExcluded(false)`), so expect `forge snapshot` numbers to drop. Under Hardhat, mock transactions emit `MockGasConsumed` events (extra receipt logs), and the plugins add `getAdjustedGasUsed(receipt)` / `getAdjustedGasBreakdown(receipt)` plus an opt-in `cofhe.gasSummary` report.
14
+
3
15
  ## 0.7.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -10,6 +10,7 @@ A mock smart contract library for testing CoFHE (Confidential Computing Framewor
10
10
  ## Features
11
11
 
12
12
  - Mock implementations of core CoFHE contracts:
13
+ - MockCoFHEAddressBook (deployed at FHE.sol's `COFHE_ADDRESS_BOOK`, resolves the TaskManager)
13
14
  - MockTaskManager
14
15
  - MockThresholdNetwork
15
16
  - MockZkVerifier
@@ -137,6 +138,15 @@ When interacting with CoFHE this request is routed to the Threshold Network, whi
137
138
 
138
139
  When working with the mocks, the `cofheClient` instead queries the `MockThresholdNetwork` contract, which verifies the request `acp` and returns the decrypted result.
139
140
 
141
+ ### Gas
142
+
143
+ Because the mocks replicate the off-chain FHE computation on-chain (plaintext math, mock storage writes, logging), transactions consume more gas than they would on a real CoFHE network. The mocks account for this in two ways:
144
+
145
+ - **Foundry**: when gas exclusion is enabled (`setMockGasExcluded(true)` — the foundry plugin's `deployMocks()` does this automatically), the mock-only work runs between `pauseGasMetering` / `resumeGasMetering` cheatcodes, so forge's reported gas approximates real-network cost. Requires cheatcode access (`vm.allowCheatcodes(taskManagerAddress)`) and forge ≥ 1.0; without either, execution silently stays metered.
146
+ - **Everywhere else** (Hardhat, anvil): `MockTaskManager` measures each block of mock-only work with `gasleft()` and emits a `MockGasConsumed(uint256)` event. Summing these events from a receipt and subtracting from `gasUsed` yields the adjusted cost — the hardhat plugins expose this as `getAdjustedGasUsed(receipt)` / `getAdjustedGasBreakdown(receipt)` and an opt-in `gasSummary` table.
147
+
148
+ `eth_estimateGas` is never adjusted: the mock work really executes, so transactions still need the raw gas limit.
149
+
140
150
  ### Using Foundry
141
151
 
142
152
  Use [`@cofhe/foundry-plugin`](../foundry-plugin/README.md), which builds on these mocks. Inherit its
@@ -1,8 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity >=0.8.19 <0.9.0;
2
+ pragma solidity >=0.8.25 <0.9.0;
3
3
 
4
- import { EnumerableSet } from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
5
- import { ACP, IPermissionCustomIdValidator } from './Permissioned.sol';
4
+ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
5
+ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
6
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
7
+ import {ACP, IPermissionCustomIdValidator} from "./Permissioned.sol";
6
8
 
7
9
  /**
8
10
  * @notice On-chain hand-off for sharing ACPs — replaces the JSON copy-paste flow.
@@ -12,7 +14,15 @@ import { ACP, IPermissionCustomIdValidator } from './Permissioned.sol';
12
14
  * are indexed globally per recipient — a share is addressed to a person, and any
13
15
  * cofhesdk-enabled app may surface it.
14
16
  *
15
- * The registry stores the payload verbatim and stays deliberately dumb:
17
+ * Pointer-based: the full ACP and an optional metadata blob travel in the `Shared`
18
+ * event; storage keeps only the share header — the fields this contract checks
19
+ * (issuer, recipient, expiration, revoker) and the block of that event — plus the
20
+ * recipient's set of share ids. A reader takes the header from `sharesFor` / `getShare`
21
+ * and fetches the event with a `getLogs` over that one block, filtered by the
22
+ * share id topic. If that block holds several `Shared` events for the share id (it
23
+ * was removed and posted again within the block), the last one is the share.
24
+ *
25
+ * The registry stays deliberately dumb:
16
26
  *
17
27
  * - The posted ACP carries `sealingKey = 0` and `recipientSignature = ""` — the
18
28
  * recipient supplies both at import, exactly as in the off-chain flow.
@@ -20,126 +30,229 @@ import { ACP, IPermissionCustomIdValidator } from './Permissioned.sol';
20
30
  * before the recipient signs, and the SDK validates everything at import. The
21
31
  * trust the registry adds is `msg.sender == acp.issuer` — a share listed under
22
32
  * a recipient was genuinely posted by its claimed issuer.
33
+ * - The metadata is opaque bytes, never interpreted here (the SDK defines the
34
+ * format: labels saying where each ctHash of a SNAPSHOT share came from). It is
35
+ * written once, with the share, and cannot be replaced.
23
36
  * - `isShareValid` is the verification hook for other contracts: share exists,
24
37
  * is unexpired, and is not revoked (per the share's own revoker contract).
25
38
  *
26
- * Nothing stored here is sensitive: every field is part of the cleartext share
39
+ * Nothing posted here is sensitive: every field is part of the cleartext share
27
40
  * payload by design. Posting on-chain does make the issuer→recipient sharing
28
41
  * relationship public. (A future variant may accept an encrypted payload as a
29
42
  * parallel entry type; this registry's cleartext entries would be unaffected.)
30
- *
31
- * The production counterpart is upgradeable (OZ AccessControl for the upgrader
32
- * role) and deployed to a fixed address as core infrastructure.
33
43
  */
34
- contract ACPShareRegistry {
35
- using EnumerableSet for EnumerableSet.Bytes32Set;
36
-
37
- /// @notice recipient => ids of shares addressed to them
38
- mapping(address => EnumerableSet.Bytes32Set) private _shareIdsFor;
39
-
40
- /// @notice share id => stored payload
41
- mapping(bytes32 => ACP) private _shares;
42
-
43
- event Shared(address indexed recipient, address indexed issuer, bytes32 shareId);
44
- event ShareRemoved(address indexed recipient, address indexed issuer, bytes32 shareId);
45
-
46
- error NotIssuer();
47
- error NotIssuerOrRecipient();
48
- error RecipientMissing();
49
- error SealingKeyMustBeEmpty();
50
- error IssuerSignatureMissing();
51
- error ShareExpired();
52
- error AlreadyShared();
53
- error UnknownShare();
54
-
55
- /// @notice Post a sharing ACP for its recipient to pick up.
56
- /// @dev The share id is the hash of the payload — reposting an identical share reverts.
57
- function share(ACP calldata acp) external returns (bytes32 shareId) {
58
- if (msg.sender != acp.issuer) revert NotIssuer();
59
- if (acp.recipient == address(0)) revert RecipientMissing();
60
- if (acp.sealingKey != bytes32(0)) revert SealingKeyMustBeEmpty();
61
- if (acp.issuerSignature.length == 0) revert IssuerSignatureMissing();
62
- if (acp.expiration < block.timestamp) revert ShareExpired();
63
-
64
- shareId = keccak256(abi.encode(acp));
65
- // the id commits to the recipient, so a duplicate can only be in this set
66
- if (!_shareIdsFor[acp.recipient].add(shareId)) revert AlreadyShared();
67
- _shares[shareId] = acp;
68
-
69
- emit Shared(acp.recipient, acp.issuer, shareId);
70
- }
71
-
72
- /// @notice Remove a share. The issuer may retract it; the recipient may dismiss it
73
- /// (e.g. after importing, or to decline).
74
- function removeShare(bytes32 shareId) external {
75
- ACP storage acp = _shares[shareId];
76
- if (acp.issuer == address(0)) revert UnknownShare();
77
- if (msg.sender != acp.issuer && msg.sender != acp.recipient) revert NotIssuerOrRecipient();
78
-
79
- address recipient = acp.recipient;
80
- address issuer = acp.issuer;
81
-
82
- // the id set and the payload map stay in sync — mirror share()'s add() handling
83
- if (!_shareIdsFor[recipient].remove(shareId)) revert UnknownShare();
84
- delete _shares[shareId];
85
-
86
- emit ShareRemoved(recipient, issuer, shareId);
87
- }
88
-
89
- /// @notice All importable shares addressed to `recipient`: unexpired and not revoked.
90
- /// Dead entries stay in storage until removed but are filtered here.
91
- function sharesFor(address recipient) external view returns (ACP[] memory acps) {
92
- EnumerableSet.Bytes32Set storage ids = _shareIdsFor[recipient];
93
- uint256 len = ids.length();
94
- if (len == 0) return acps;
95
-
96
- // single pass: allocate for the maximum, fill with valid shares only
97
- acps = new ACP[](len);
98
- uint256 live = 0;
99
- for (uint256 i = 0; i < len; i++) {
100
- ACP storage acp = _shares[ids.at(i)];
101
- if (_isValid(acp)) {
102
- acps[live] = acp;
103
- live++;
104
- }
44
+ contract ACPShareRegistry is UUPSUpgradeable, AccessControlUpgradeable {
45
+ using EnumerableSet for EnumerableSet.Bytes32Set;
46
+
47
+ /// @notice Role allowed to upgrade the implementation.
48
+ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
49
+
50
+ /// @notice What storage keeps of a share: the fields `removeShare` and the validity
51
+ /// check read, and where to find the rest.
52
+ /// @dev Packed into four slots; the two revoker slots stay zero for a share without
53
+ /// a revoker.
54
+ struct ShareHeader {
55
+ address issuer;
56
+ uint64 expiration;
57
+ address recipient;
58
+ /// @dev Block of the `Shared` event that carries the full ACP and the metadata
59
+ /// (the L2 block on Arbitrum). The last `Shared` event for the share id in that
60
+ /// block, should it hold more than one.
61
+ uint64 blockNumber;
62
+ address revokerContract;
63
+ uint256 revokerData;
64
+ }
65
+
66
+ /// @custom:storage-location erc7201:cofhe.storage.ACPShareRegistry.v2
67
+ struct ACPShareRegistryStorage {
68
+ /// @dev recipient => ids of shares addressed to them
69
+ mapping(address => EnumerableSet.Bytes32Set) shareIdsFor;
70
+ /// @dev share id => stored header
71
+ mapping(bytes32 => ShareHeader) headers;
72
+ }
73
+
74
+ /// @dev keccak256(abi.encode(uint256(keccak256("cofhe.storage.ACPShareRegistry.v2")) - 1)) & ~bytes32(uint256(0xff))
75
+ bytes32 private constant ACP_SHARE_REGISTRY_SLOT =
76
+ keccak256(abi.encode(uint256(keccak256("cofhe.storage.ACPShareRegistry.v2")) - 1)) & ~bytes32(uint256(0xff));
77
+
78
+ /// @notice The first version's storage, which kept every share whole. Abandoned by the
79
+ /// in-place upgrade: nothing reads it, and its shares are not carried over. Declared
80
+ /// only so the namespace stays reserved (the OpenZeppelin layout check requires it).
81
+ /// @custom:storage-location erc7201:cofhe.storage.ACPShareRegistry
82
+ struct ACPShareRegistryStorageV1 {
83
+ /// @dev recipient => ids of shares addressed to them
84
+ mapping(address => EnumerableSet.Bytes32Set) shareIdsFor;
85
+ /// @dev share id => stored payload
86
+ mapping(bytes32 => ACP) shares;
87
+ }
88
+
89
+ /// @notice A share was posted. `acp` is the payload as posted; `metadata` is the opaque
90
+ /// blob that came with it (empty when none). The share id is
91
+ /// `keccak256(abi.encode(acp))`. A share removed and posted again in the same block
92
+ /// emits this twice there with the same id; the later one (higher log index) is
93
+ /// the share, and only its `metadata` can differ.
94
+ event Shared(address indexed recipient, address indexed issuer, bytes32 indexed shareId, ACP acp, bytes metadata);
95
+ event ShareRemoved(address indexed recipient, address indexed issuer, bytes32 indexed shareId);
96
+
97
+ error NotIssuer();
98
+ error NotIssuerOrRecipient();
99
+ error RecipientMissing();
100
+ error SealingKeyMustBeEmpty();
101
+ error IssuerSignatureMissing();
102
+ error ShareExpired();
103
+ error AlreadyShared();
104
+ error UnknownShare();
105
+
106
+ /// @custom:oz-upgrades-unsafe-allow constructor
107
+ constructor() {
108
+ _disableInitializers();
105
109
  }
106
110
 
107
- // truncate the memory array's length to the live count (shrink-only)
108
- if (live < len) {
109
- assembly {
110
- mstore(acps, live)
111
- }
111
+ /**
112
+ * @notice Initializes the contract.
113
+ * @param initialAdmin Receives DEFAULT_ADMIN_ROLE and UPGRADER_ROLE.
114
+ */
115
+ function initialize(address initialAdmin) public initializer {
116
+ __AccessControl_init();
117
+ _grantRole(DEFAULT_ADMIN_ROLE, initialAdmin);
118
+ _grantRole(UPGRADER_ROLE, initialAdmin);
112
119
  }
113
- }
114
-
115
- /// @notice A single share by id (zeroed struct if unknown/removed).
116
- function getShare(bytes32 shareId) external view returns (ACP memory) {
117
- return _shares[shareId];
118
- }
119
-
120
- /// @notice Verification hook for contracts: the share exists, was posted by its
121
- /// claimed issuer (guaranteed at posting), is unexpired, and is not
122
- /// revoked per its own revoker contract.
123
- function isShareValid(bytes32 shareId) external view returns (bool) {
124
- if (_shares[shareId].issuer == address(0)) return false;
125
- return _isValid(_shares[shareId]);
126
- }
127
-
128
- /// @dev Unexpired and not revoked. The revoker call mirrors `withPermission`'s
129
- /// revocation clause; a reverting revoker fails closed (share invalid).
130
- function _isValid(ACP storage acp) private view returns (bool) {
131
- if (acp.expiration < block.timestamp) return false;
132
-
133
- if (acp.revokerData != 0 && acp.revokerContract != address(0)) {
134
- try IPermissionCustomIdValidator(acp.revokerContract).disabled(acp.issuer, acp.revokerData) returns (
135
- bool disabled
136
- ) {
137
- if (disabled) return false;
138
- } catch {
139
- return false;
140
- }
120
+
121
+ /// @notice Post a sharing ACP for its recipient to pick up, with an optional metadata
122
+ /// blob (`""` for none).
123
+ /// @dev The share id is the hash of the payload — reposting an identical share reverts,
124
+ /// whatever its metadata.
125
+ function share(ACP calldata acp, bytes calldata metadata) external returns (bytes32 shareId) {
126
+ if (msg.sender != acp.issuer) revert NotIssuer();
127
+ if (acp.recipient == address(0)) revert RecipientMissing();
128
+ if (acp.sealingKey != bytes32(0)) revert SealingKeyMustBeEmpty();
129
+ if (acp.issuerSignature.length == 0) revert IssuerSignatureMissing();
130
+ if (acp.expiration < block.timestamp) revert ShareExpired();
131
+
132
+ ACPShareRegistryStorage storage $ = _getStorage();
133
+
134
+ shareId = keccak256(abi.encode(acp));
135
+ // the id commits to the recipient, so a duplicate can only be in this set
136
+ if (!$.shareIdsFor[acp.recipient].add(shareId)) revert AlreadyShared();
137
+ $.headers[shareId] = ShareHeader({
138
+ issuer: acp.issuer,
139
+ expiration: acp.expiration,
140
+ recipient: acp.recipient,
141
+ blockNumber: uint64(_blockNumber()),
142
+ revokerContract: acp.revokerContract,
143
+ revokerData: acp.revokerData
144
+ });
145
+
146
+ emit Shared(acp.recipient, acp.issuer, shareId, acp, metadata);
141
147
  }
142
148
 
143
- return true;
144
- }
149
+ /// @notice Remove a share. The issuer may retract it; the recipient may dismiss it
150
+ /// (e.g. after importing, or to decline). The `Shared` event stays in its block;
151
+ /// readers go by the header, which is gone.
152
+ function removeShare(bytes32 shareId) external {
153
+ ACPShareRegistryStorage storage $ = _getStorage();
154
+
155
+ ShareHeader storage header = $.headers[shareId];
156
+ if (header.issuer == address(0)) revert UnknownShare();
157
+ if (msg.sender != header.issuer && msg.sender != header.recipient) revert NotIssuerOrRecipient();
158
+
159
+ address recipient = header.recipient;
160
+ address issuer = header.issuer;
161
+
162
+ // the id set and the header map stay in sync — mirror share()'s add() handling
163
+ if (!$.shareIdsFor[recipient].remove(shareId)) revert UnknownShare();
164
+ delete $.headers[shareId];
165
+
166
+ emit ShareRemoved(recipient, issuer, shareId);
167
+ }
168
+
169
+ /// @notice The importable shares addressed to `recipient` — unexpired and not revoked —
170
+ /// as their ids and headers, index for index. Dead entries stay in storage until
171
+ /// removed but are filtered here.
172
+ function sharesFor(
173
+ address recipient
174
+ ) external view returns (bytes32[] memory shareIds, ShareHeader[] memory headers) {
175
+ ACPShareRegistryStorage storage $ = _getStorage();
176
+ EnumerableSet.Bytes32Set storage ids = $.shareIdsFor[recipient];
177
+ uint256 len = ids.length();
178
+
179
+ // single pass: allocate for the maximum, fill with valid shares only
180
+ shareIds = new bytes32[](len);
181
+ headers = new ShareHeader[](len);
182
+ uint256 live = 0;
183
+ for (uint256 i = 0; i < len; i++) {
184
+ bytes32 shareId = ids.at(i);
185
+ ShareHeader storage header = $.headers[shareId];
186
+ if (_isValid(header)) {
187
+ shareIds[live] = shareId;
188
+ headers[live] = header;
189
+ live++;
190
+ }
191
+ }
192
+
193
+ // truncate the memory arrays' length to the live count (shrink-only)
194
+ if (live < len) {
195
+ assembly {
196
+ mstore(shareIds, live)
197
+ mstore(headers, live)
198
+ }
199
+ }
200
+ }
201
+
202
+ /// @notice The header of a single share (zeroed struct if unknown/removed).
203
+ function getShare(bytes32 shareId) external view returns (ShareHeader memory) {
204
+ return _getStorage().headers[shareId];
205
+ }
206
+
207
+ /// @notice Verification hook for contracts: the share exists, was posted by its
208
+ /// claimed issuer (guaranteed at posting), is unexpired, and is not
209
+ /// revoked per its own revoker contract.
210
+ function isShareValid(bytes32 shareId) external view returns (bool) {
211
+ ShareHeader storage header = _getStorage().headers[shareId];
212
+ if (header.issuer == address(0)) return false;
213
+ return _isValid(header);
214
+ }
215
+
216
+ /// @dev Unexpired and not revoked. The revoker call mirrors `withPermission`'s
217
+ /// revocation clause; a reverting revoker fails closed (share invalid).
218
+ function _isValid(ShareHeader storage header) private view returns (bool) {
219
+ if (header.expiration < block.timestamp) return false;
220
+
221
+ if (header.revokerData != 0 && header.revokerContract != address(0)) {
222
+ try IPermissionCustomIdValidator(header.revokerContract).disabled(header.issuer, header.revokerData) returns (
223
+ bool disabled
224
+ ) {
225
+ if (disabled) return false;
226
+ } catch {
227
+ return false;
228
+ }
229
+ }
230
+
231
+ return true;
232
+ }
233
+
234
+ /// @dev The number a `getLogs` query takes for the current block. On Arbitrum `block.number`
235
+ /// is an L1 block estimate and the L2 block comes from the ArbSys precompile; elsewhere
236
+ /// nothing answers at that address and `block.number` is the block.
237
+ function _blockNumber() private view returns (uint256) {
238
+ (bool ok, bytes memory result) = address(100).staticcall(abi.encodeWithSignature("arbBlockNumber()"));
239
+ return ok && result.length == 32 ? abi.decode(result, (uint256)) : block.number;
240
+ }
241
+
242
+ /**
243
+ * @dev Should revert when `msg.sender` is not authorized to upgrade the contract.
244
+ * Empty implementation since authorization is handled by the role check.
245
+ */
246
+ /* solhint-disable-next-line no-empty-blocks */
247
+ function _authorizeUpgrade(address _newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) {}
248
+
249
+ /**
250
+ * @dev Returns the registry storage location.
251
+ */
252
+ function _getStorage() internal pure returns (ACPShareRegistryStorage storage $) {
253
+ bytes32 slot = ACP_SHARE_REGISTRY_SLOT;
254
+ assembly {
255
+ $.slot := slot
256
+ }
257
+ }
145
258
  }
@@ -1,62 +1,64 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity >=0.8.19 <0.9.0;
3
3
 
4
- import { IPermissionCustomIdValidator } from './Permissioned.sol';
4
+ import {IPermissionCustomIdValidator} from "./Permissioned.sol";
5
5
 
6
6
  /**
7
7
  * @notice Default ACP revoker — timestamp-based revocation.
8
8
  *
9
9
  * Implements the unchanged revoker interface. Interprets a permission's
10
- * `revokerData` as its creation timestamp (populated by the SDK at acp
11
- * creation). Every SDK-created acp points here by default: no upfront
12
- * contract call is needed — an ACP is revocable from birth.
10
+ * `revokerData` as its creation timestamp (populated by the SDK at permission
11
+ * creation, backdated 60s for clock skew). Every SDK-created permission points
12
+ * here by default: no upfront contract call is needed — a permission is
13
+ * revocable from birth.
13
14
  *
14
15
  * `disabled(issuer, id)` truth table:
15
16
  *
16
17
  * | condition | result | meaning |
17
18
  * |-----------------------------|--------|----------------------------------|
18
- * | id > block.timestamp | true | future-dated acps never |
19
+ * | id > block.timestamp | true | future-dated permissions never |
19
20
  * | | | validate (closes the revoke-all |
20
- * | | | dodge: an ACP dated ahead of a|
21
- * | | | mass revocation would survive it)|
21
+ * | | | dodge: a permission dated ahead |
22
+ * | | | of a mass revocation would |
23
+ * | | | otherwise survive it) |
22
24
  * | id <= revokeAllAt[issuer] | true | mass revocation (inclusive) |
23
25
  * | revokedSingle[issuer][id] | true | targeted revocation |
24
- * | otherwise | false | acp valid |
26
+ * | otherwise | false | permission valid |
25
27
  *
26
- * Accepted trade-offs (by design, see ACPV3 spec discussion):
27
- * - Two acps minted by the same issuer in the same second share an id;
28
+ * Accepted trade-offs (by design, see ACP spec discussion):
29
+ * - Two permissions minted by the same issuer in the same second share an id;
28
30
  * revoking one revokes both. Over-revocation is the fail-safe direction.
29
31
  * - Revocation is permanent — there is no un-revoke.
30
32
  *
31
- * The production counterpart is upgradeable (OZ AccessControl for the
32
- * upgrader role) and deployed to a fixed address as core infrastructure.
33
+ * @dev TODO(review): production wants this upgradeable (OZ AccessControl for the
34
+ * upgrader role) and deployed to a fixed address as core infrastructure.
33
35
  */
34
36
  contract ACPTimestampRevoker is IPermissionCustomIdValidator {
35
- /// @notice issuer => threshold; acps with id (creation ts) at or before this are revoked
36
- mapping(address => uint256) public revokeAllAt;
37
+ /// @notice issuer => threshold; permissions with id (creation ts) at or before this are revoked
38
+ mapping(address => uint256) public revokeAllAt;
37
39
 
38
- /// @notice issuer => id => revoked
39
- mapping(address => mapping(uint256 => bool)) public revokedSingle;
40
+ /// @notice issuer => id => revoked
41
+ mapping(address => mapping(uint256 => bool)) public revokedSingle;
40
42
 
41
- event RevokedSingle(address indexed issuer, uint256 indexed id);
42
- event RevokedAll(address indexed issuer, uint256 at);
43
+ event RevokedSingle(address indexed issuer, uint256 indexed id);
44
+ event RevokedAll(address indexed issuer, uint256 at);
43
45
 
44
- /// @notice Revoke a single acp by its id (creation timestamp).
45
- /// Only affects acps issued by the caller.
46
- function revokeSingle(uint256 id) external {
47
- revokedSingle[msg.sender][id] = true;
48
- emit RevokedSingle(msg.sender, id);
49
- }
46
+ /// @notice Revoke a single permission by its id (creation timestamp).
47
+ /// Only affects permissions issued by the caller.
48
+ function revokeSingle(uint256 id) external {
49
+ revokedSingle[msg.sender][id] = true;
50
+ emit RevokedSingle(msg.sender, id);
51
+ }
50
52
 
51
- /// @notice Revoke every acp the caller created up to now.
52
- /// O(1): a single threshold write, regardless of acp count.
53
- function revokeAllExisting() external {
54
- revokeAllAt[msg.sender] = block.timestamp;
55
- emit RevokedAll(msg.sender, block.timestamp);
56
- }
53
+ /// @notice Revoke every permission the caller created up to now.
54
+ /// O(1): a single threshold write, regardless of permission count.
55
+ function revokeAllExisting() external {
56
+ revokeAllAt[msg.sender] = block.timestamp;
57
+ emit RevokedAll(msg.sender, block.timestamp);
58
+ }
57
59
 
58
- /// @dev Called by the ACL during permission validation.
59
- function disabled(address issuer, uint256 id) external view returns (bool) {
60
- return id > block.timestamp || id <= revokeAllAt[issuer] || revokedSingle[issuer][id];
61
- }
60
+ /// @dev Called by the ACL during permission validation.
61
+ function disabled(address issuer, uint256 id) external view returns (bool) {
62
+ return id > block.timestamp || id <= revokeAllAt[issuer] || revokedSingle[issuer][id];
63
+ }
62
64
  }