-
Notifications
You must be signed in to change notification settings - Fork 122
/
KeyRegistry.sol
410 lines (351 loc) · 13.3 KB
/
KeyRegistry.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
100
101
102
103
104
105
106
107
108
109
110
111
112
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {Ownable2Step} from "openzeppelin/contracts/access/Ownable2Step.sol";
import {IKeyRegistry} from "./interfaces/IKeyRegistry.sol";
import {IMetadataValidator} from "./interfaces/IMetadataValidator.sol";
import {IdRegistryLike} from "./interfaces/IdRegistryLike.sol";
import {EIP712} from "./abstract/EIP712.sol";
import {Migration} from "./abstract/Migration.sol";
import {Nonces} from "./abstract/Nonces.sol";
import {Signatures} from "./abstract/Signatures.sol";
import {EnumerableKeySet, KeySet} from "./libraries/EnumerableKeySet.sol";
/**
* @title Farcaster KeyRegistry
*
* @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview.
*
* @custom:security-contact security@merklemanufactory.com
*/
contract KeyRegistry is IKeyRegistry, Migration, Signatures, EIP712, Nonces {
using EnumerableKeySet for KeySet;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
string public constant VERSION = "2023.11.15";
/**
* @inheritdoc IKeyRegistry
*/
bytes32 public constant REMOVE_TYPEHASH =
keccak256("Remove(address owner,bytes key,uint256 nonce,uint256 deadline)");
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
IdRegistryLike public idRegistry;
/**
* @inheritdoc IKeyRegistry
*/
address public keyGateway;
/**
* @inheritdoc IKeyRegistry
*/
bool public gatewayFrozen;
/**
* @inheritdoc IKeyRegistry
*/
uint256 public maxKeysPerFid;
/**
* @dev Internal enumerable set tracking active keys by fid.
*/
mapping(uint256 fid => KeySet activeKeys) internal _activeKeysByFid;
/**
* @dev Internal enumerable set tracking removed keys by fid.
*/
mapping(uint256 fid => KeySet removedKeys) internal _removedKeysByFid;
/**
* @dev Mapping of fid to a key to the key's data.
*
* @custom:param fid The fid associated with the key.
* @custom:param key Bytes of the key.
* @custom:param data Struct with the state and key type. In the initial migration
* all keys will have data.keyType == 1.
*/
mapping(uint256 fid => mapping(bytes key => KeyData data)) public keys;
/**
* @dev Mapping of keyType to metadataType to validator contract.
*
* @custom:param keyType Numeric keyType.
* @custom:param metadataType Metadata metadataType.
* @custom:param validator Validator contract implementing IMetadataValidator.
*/
mapping(uint32 keyType => mapping(uint8 metadataType => IMetadataValidator validator)) public validators;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Set the IdRegistry and owner.
*
* @param _idRegistry IdRegistry contract address.
* @param _migrator Migrator address.
* @param _initialOwner Initial contract owner address.
* @param _maxKeysPerFid Maximum number of keys per fid.
*/
constructor(
address _idRegistry,
address _migrator,
address _initialOwner,
uint256 _maxKeysPerFid
) Migration(24 hours, _migrator, _initialOwner) EIP712("Farcaster KeyRegistry", "1") {
idRegistry = IdRegistryLike(_idRegistry);
maxKeysPerFid = _maxKeysPerFid;
emit SetIdRegistry(address(0), _idRegistry);
emit SetMaxKeysPerFid(0, _maxKeysPerFid);
}
/*//////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
function totalKeys(uint256 fid, KeyState state) public view virtual returns (uint256) {
return _keysByState(fid, state).length();
}
/**
* @inheritdoc IKeyRegistry
*/
function keyAt(uint256 fid, KeyState state, uint256 index) external view returns (bytes memory) {
return _keysByState(fid, state).at(index);
}
/**
* @inheritdoc IKeyRegistry
*/
function keysOf(uint256 fid, KeyState state) external view returns (bytes[] memory) {
return _keysByState(fid, state).values();
}
/**
* @inheritdoc IKeyRegistry
*/
function keysOf(
uint256 fid,
KeyState state,
uint256 startIdx,
uint256 batchSize
) external view returns (bytes[] memory page, uint256 nextIdx) {
KeySet storage _keys = _keysByState(fid, state);
uint256 len = _keys.length();
if (startIdx >= len) return (new bytes[](0), 0);
uint256 remaining = len - startIdx;
uint256 adjustedBatchSize = remaining < batchSize ? remaining : batchSize;
page = new bytes[](adjustedBatchSize);
for (uint256 i = 0; i < adjustedBatchSize; i++) {
page[i] = _keys.at(startIdx + i);
}
nextIdx = startIdx + adjustedBatchSize;
if (nextIdx >= len) nextIdx = 0;
return (page, nextIdx);
}
/**
* @inheritdoc IKeyRegistry
*/
function keyDataOf(uint256 fid, bytes calldata key) external view returns (KeyData memory) {
return keys[fid][key];
}
/*//////////////////////////////////////////////////////////////
REGISTRATION
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
function add(
address fidOwner,
uint32 keyType,
bytes calldata key,
uint8 metadataType,
bytes calldata metadata
) external whenNotPaused {
if (msg.sender != keyGateway) revert Unauthorized();
_add(_fidOf(fidOwner), keyType, key, metadataType, metadata);
}
/**
* @inheritdoc IKeyRegistry
*/
function remove(bytes calldata key) external whenNotPaused {
_remove(_fidOf(msg.sender), key);
}
/**
* @inheritdoc IKeyRegistry
*/
function removeFor(
address fidOwner,
bytes calldata key,
uint256 deadline,
bytes calldata sig
) external whenNotPaused {
_verifyRemoveSig(fidOwner, key, deadline, sig);
_remove(_fidOf(fidOwner), key);
}
/*//////////////////////////////////////////////////////////////
MIGRATION
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
function bulkAddKeysForMigration(BulkAddData[] calldata items) external onlyMigrator {
// Safety: i and j can be incremented unchecked since they are bound by items.length and
// items[i].keys.length respectively.
unchecked {
for (uint256 i = 0; i < items.length; i++) {
BulkAddData calldata item = items[i];
for (uint256 j = 0; j < item.keys.length; j++) {
_add(item.fid, 1, item.keys[j].key, 1, item.keys[j].metadata, false);
}
}
}
}
/**
* @inheritdoc IKeyRegistry
*/
function bulkResetKeysForMigration(BulkResetData[] calldata items) external onlyMigrator {
// Safety: i and j can be incremented unchecked since they are bound by items.length and
// items[i].keys.length respectively.
unchecked {
for (uint256 i = 0; i < items.length; i++) {
BulkResetData calldata item = items[i];
for (uint256 j = 0; j < item.keys.length; j++) {
_reset(item.fid, item.keys[j]);
}
}
}
}
/*//////////////////////////////////////////////////////////////
ADMIN
//////////////////////////////////////////////////////////////*/
/**
* @inheritdoc IKeyRegistry
*/
function setValidator(uint32 keyType, uint8 metadataType, IMetadataValidator validator) external onlyOwner {
if (keyType == 0) revert InvalidKeyType();
if (metadataType == 0) revert InvalidMetadataType();
emit SetValidator(keyType, metadataType, address(validators[keyType][metadataType]), address(validator));
validators[keyType][metadataType] = validator;
}
/**
* @inheritdoc IKeyRegistry
*/
function setIdRegistry(address _idRegistry) external onlyOwner {
emit SetIdRegistry(address(idRegistry), _idRegistry);
idRegistry = IdRegistryLike(_idRegistry);
}
/**
* @inheritdoc IKeyRegistry
*/
function setKeyGateway(address _keyGateway) external onlyOwner {
if (gatewayFrozen) revert GatewayFrozen();
emit SetKeyGateway(keyGateway, _keyGateway);
keyGateway = _keyGateway;
}
/**
* @inheritdoc IKeyRegistry
*/
function freezeKeyGateway() external onlyOwner {
if (gatewayFrozen) revert GatewayFrozen();
emit FreezeKeyGateway(keyGateway);
gatewayFrozen = true;
}
/**
* @inheritdoc IKeyRegistry
*/
function setMaxKeysPerFid(uint256 _maxKeysPerFid) external onlyOwner {
if (_maxKeysPerFid <= maxKeysPerFid) revert InvalidMaxKeys();
emit SetMaxKeysPerFid(maxKeysPerFid, _maxKeysPerFid);
maxKeysPerFid = _maxKeysPerFid;
}
/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/
function _add(
uint256 fid,
uint32 keyType,
bytes calldata key,
uint8 metadataType,
bytes calldata metadata
) internal {
_add(fid, keyType, key, metadataType, metadata, true);
}
function _add(
uint256 fid,
uint32 keyType,
bytes calldata key,
uint8 metadataType,
bytes calldata metadata,
bool validate
) internal {
KeyData storage keyData = keys[fid][key];
if (keyData.state != KeyState.NULL) revert InvalidState();
if (totalKeys(fid, KeyState.ADDED) >= maxKeysPerFid) revert ExceedsMaximum();
IMetadataValidator validator = validators[keyType][metadataType];
if (validator == IMetadataValidator(address(0))) {
revert ValidatorNotFound(keyType, metadataType);
}
_addToKeySet(fid, key);
keyData.state = KeyState.ADDED;
keyData.keyType = keyType;
emit Add(fid, keyType, key, key, metadataType, metadata);
if (validate) {
bool isValid = validator.validate(fid, key, metadata);
if (!isValid) revert InvalidMetadata();
}
}
function _remove(uint256 fid, bytes calldata key) internal {
KeyData storage keyData = keys[fid][key];
if (keyData.state != KeyState.ADDED) revert InvalidState();
_removeFromKeySet(fid, key);
keyData.state = KeyState.REMOVED;
emit Remove(fid, key, key);
}
function _reset(uint256 fid, bytes calldata key) internal {
KeyData storage keyData = keys[fid][key];
if (keyData.state != KeyState.ADDED) revert InvalidState();
_resetFromKeySet(fid, key);
keyData.state = KeyState.NULL;
delete keyData.keyType;
emit AdminReset(fid, key, key);
}
/*//////////////////////////////////////////////////////////////
SIGNATURE VERIFICATION HELPERS
//////////////////////////////////////////////////////////////*/
function _verifyRemoveSig(address fidOwner, bytes calldata key, uint256 deadline, bytes calldata sig) internal {
_verifySig(
_hashTypedDataV4(
keccak256(abi.encode(REMOVE_TYPEHASH, fidOwner, keccak256(key), _useNonce(fidOwner), deadline))
),
fidOwner,
deadline,
sig
);
}
/*//////////////////////////////////////////////////////////////
FID HELPERS
//////////////////////////////////////////////////////////////*/
function _fidOf(address fidOwner) internal view returns (uint256 fid) {
fid = idRegistry.idOf(fidOwner);
if (fid == 0) revert Unauthorized();
}
/*//////////////////////////////////////////////////////////////
KEY SET HELPERS
//////////////////////////////////////////////////////////////*/
function _addToKeySet(uint256 fid, bytes calldata key) internal virtual {
_activeKeysByFid[fid].add(key);
}
function _removeFromKeySet(uint256 fid, bytes calldata key) internal virtual {
_activeKeysByFid[fid].remove(key);
_removedKeysByFid[fid].add(key);
}
function _resetFromKeySet(uint256 fid, bytes calldata key) internal virtual {
_activeKeysByFid[fid].remove(key);
}
function _keysByState(uint256 fid, KeyState state) internal view returns (KeySet storage) {
if (state == KeyState.ADDED) {
return _activeKeysByFid[fid];
} else if (state == KeyState.REMOVED) {
return _removedKeysByFid[fid];
} else {
revert InvalidState();
}
}
}