@cofhe/mock-contracts 0.8.0 → 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/contracts/ACPShareRegistry.sol +227 -114
- package/contracts/ACPTimestampRevoker.sol +36 -34
- package/contracts/MockACL.sol +2 -1
- package/contracts/MockCoFHE.sol +36 -12
- package/contracts/MockERC1967Proxy.sol +13 -0
- package/contracts/MockPermissioned.sol +16 -0
- package/contracts/MockTaskManager.sol +69 -4
- package/contracts/MockThresholdNetwork.sol +5 -5
- package/contracts/Permissioned.sol +240 -143
- package/contracts/cofhe-contracts.json +10 -0
- package/dist/index.d.mts +688 -80
- package/dist/index.d.ts +688 -80
- package/dist/index.js +574 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +574 -86
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
- package/remappings.txt +1 -0
- package/src/ACPShareRegistry.ts +469 -56
- package/src/MockACL.ts +19 -12
- package/src/MockERC1967Proxy.ts +74 -0
- package/src/MockTaskManager.ts +0 -5
- package/src/index.ts +2 -0
- package/src/typechain-types/ACPShareRegistry.ts +309 -12
- package/src/typechain-types/MockACL.ts +27 -0
- package/src/typechain-types/MockERC1967Proxy.ts +85 -0
- package/src/typechain-types/index.ts +1 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity >=0.8.
|
|
2
|
+
pragma solidity >=0.8.25 <0.9.0;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
11
|
-
* creation). Every SDK-created
|
|
12
|
-
* contract call is needed —
|
|
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
|
|
19
|
+
* | id > block.timestamp | true | future-dated permissions never |
|
|
19
20
|
* | | | validate (closes the revoke-all |
|
|
20
|
-
* | | | dodge:
|
|
21
|
-
* | | | mass revocation would
|
|
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 |
|
|
26
|
+
* | otherwise | false | permission valid |
|
|
25
27
|
*
|
|
26
|
-
* Accepted trade-offs (by design, see
|
|
27
|
-
* - Two
|
|
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
|
-
*
|
|
32
|
-
*
|
|
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
|
-
|
|
36
|
-
|
|
37
|
+
/// @notice issuer => threshold; permissions with id (creation ts) at or before this are revoked
|
|
38
|
+
mapping(address => uint256) public revokeAllAt;
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
/// @notice issuer => id => revoked
|
|
41
|
+
mapping(address => mapping(uint256 => bool)) public revokedSingle;
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
event RevokedSingle(address indexed issuer, uint256 indexed id);
|
|
44
|
+
event RevokedAll(address indexed issuer, uint256 at);
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
}
|
package/contracts/MockACL.sol
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
pragma solidity >=0.8.19 <0.9.0;
|
|
3
3
|
|
|
4
4
|
import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
|
|
5
|
-
import {
|
|
5
|
+
import { ACP, SCOPE_GLOBAL, SCOPE_CONTRACT, SCOPE_HANDLES } from './Permissioned.sol';
|
|
6
|
+
import { MockPermissioned } from './MockPermissioned.sol';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @title ACL
|
package/contracts/MockCoFHE.sol
CHANGED
|
@@ -329,8 +329,13 @@ abstract contract MockCoFHE {
|
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
331
|
if (funcId == FunctionId.not) {
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
if (getIsBoolTypeFromHash(ctHash)) {
|
|
333
|
+
bool inputIsTruthy = _get(input) == 1;
|
|
334
|
+
_set(ctHash, !inputIsTruthy);
|
|
335
|
+
} else {
|
|
336
|
+
// bitwise NOT, masked to the type width by _set
|
|
337
|
+
_set(ctHash, ~_get(input));
|
|
338
|
+
}
|
|
334
339
|
if (logOps) logOperation('FHE.not', logCtHash(input), logCtHash(ctHash));
|
|
335
340
|
return;
|
|
336
341
|
}
|
|
@@ -393,8 +398,14 @@ abstract contract MockCoFHE {
|
|
|
393
398
|
return;
|
|
394
399
|
}
|
|
395
400
|
if (funcId == FunctionId.rem) {
|
|
396
|
-
|
|
397
|
-
|
|
401
|
+
// Like TFHE-rs, the remainder by zero returns the first input unchanged
|
|
402
|
+
uint256 cleartext2 = _get(input2);
|
|
403
|
+
if (cleartext2 == 0) {
|
|
404
|
+
_set(ctHash, _get(input1));
|
|
405
|
+
} else {
|
|
406
|
+
unchecked {
|
|
407
|
+
_set(ctHash, _get(input1) % cleartext2);
|
|
408
|
+
}
|
|
398
409
|
}
|
|
399
410
|
if (logOps) logOperation('FHE.rem', string.concat(logCtHash(input1), ' % ', logCtHash(input2)), logCtHash(ctHash));
|
|
400
411
|
return;
|
|
@@ -483,19 +494,32 @@ abstract contract MockCoFHE {
|
|
|
483
494
|
return;
|
|
484
495
|
}
|
|
485
496
|
if (funcId == FunctionId.rol) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
497
|
+
// rotation within the type width, amount modulo the width (TFHE-rs semantics); _set masks the result
|
|
498
|
+
uint256 bits = getUtypeBits(ctHash);
|
|
499
|
+
uint256 shift = _get(input2) % bits;
|
|
500
|
+
uint256 value = _get(input1);
|
|
501
|
+
_set(ctHash, shift == 0 ? value : (value << shift) | (value >> (bits - shift)));
|
|
489
502
|
|
|
490
|
-
if (logOps)
|
|
503
|
+
if (logOps)
|
|
504
|
+
logOperation(
|
|
505
|
+
'FHE.rol',
|
|
506
|
+
string.concat('rol(', logCtHash(input1), ', ', logCtHash(input2), ')'),
|
|
507
|
+
logCtHash(ctHash)
|
|
508
|
+
);
|
|
491
509
|
return;
|
|
492
510
|
}
|
|
493
511
|
if (funcId == FunctionId.ror) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
512
|
+
uint256 bits = getUtypeBits(ctHash);
|
|
513
|
+
uint256 shift = _get(input2) % bits;
|
|
514
|
+
uint256 value = _get(input1);
|
|
515
|
+
_set(ctHash, shift == 0 ? value : (value >> shift) | (value << (bits - shift)));
|
|
497
516
|
|
|
498
|
-
if (logOps)
|
|
517
|
+
if (logOps)
|
|
518
|
+
logOperation(
|
|
519
|
+
'FHE.ror',
|
|
520
|
+
string.concat('ror(', logCtHash(input1), ', ', logCtHash(input2), ')'),
|
|
521
|
+
logCtHash(ctHash)
|
|
522
|
+
);
|
|
499
523
|
return;
|
|
500
524
|
}
|
|
501
525
|
revert InvalidTwoInputOperation(Utils.functionIdToString(funcId));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity >=0.8.25 <0.9.0;
|
|
3
|
+
|
|
4
|
+
import { ERC1967Proxy } from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @notice OpenZeppelin's ERC-1967 proxy under a name of its own (no clash with a project's own
|
|
8
|
+
* `ERC1967Proxy` artifact). The ACP share registry is the production contract, which refuses to
|
|
9
|
+
* be initialized directly, so the mocks deploy it behind this proxy as production does.
|
|
10
|
+
*/
|
|
11
|
+
contract MockERC1967Proxy is ERC1967Proxy {
|
|
12
|
+
constructor(address implementation, bytes memory data) payable ERC1967Proxy(implementation, data) {}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity >=0.8.25 <0.9.0;
|
|
3
|
+
|
|
4
|
+
import { PermissionedUpgradeable } from './Permissioned.sol';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @notice The ACL's ACP checks, taken unchanged from cofhe-contracts (`PermissionedUpgradeable`
|
|
8
|
+
* in Permissioned.sol), on a contract deployed directly rather than behind a proxy. The EIP-712
|
|
9
|
+
* domain ("ACL", "2") is defined by code there, so it needs no initialization.
|
|
10
|
+
*/
|
|
11
|
+
contract MockPermissioned is PermissionedUpgradeable {
|
|
12
|
+
/// @dev Exposed for tests that build typed-data digests.
|
|
13
|
+
function hashTypedDataV4(bytes32 structHash) public view virtual returns (bytes32) {
|
|
14
|
+
return _hashTypedDataV4(structHash);
|
|
15
|
+
}
|
|
16
|
+
}
|