-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathKeep.sol
More file actions
496 lines (404 loc) · 14.8 KB
/
Keep.sol
File metadata and controls
496 lines (404 loc) · 14.8 KB
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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {ERC1155TokenReceiver, KeepToken} from "./KeepToken.sol";
import {Multicallable} from "./utils/Multicallable.sol";
/// @title Keep
/// @notice Tokenized multisig wallet.
/// @author z0r0z.eth
/// @custom:coauthor @ControlCplusControlV
/// @custom:coauthor boredretard.eth
/// @custom:coauthor vectorized.eth
/// @custom:coauthor horsefacts.eth
/// @custom:coauthor shivanshi.eth
/// @custom:coauthor @0xAlcibiades
/// @custom:coauthor LeXpunK Army
/// @custom:coauthor @0xmichalis
/// @custom:coauthor @iFrostizz
/// @custom:coauthor @m1guelpf
/// @custom:coauthor @asnared
/// @custom:coauthor @0xPhaze
/// @custom:coauthor out.eth
enum Operation {
call,
delegatecall,
create
}
struct Call {
Operation op;
address to;
uint256 value;
bytes data;
}
struct Signature {
address user;
uint8 v;
bytes32 r;
bytes32 s;
}
contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable {
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
/// @dev Emitted when Keep executes call.
event Executed(
uint256 indexed nonce,
Operation op,
address to,
uint256 value,
bytes data
);
/// @dev Emitted when Keep relays call.
event Relayed(Call call);
/// @dev Emitted when Keep relays calls.
event Multirelayed(Call[] calls);
/// @dev Emitted when quorum threshold is updated.
event QuorumSet(uint256 threshold);
/// -----------------------------------------------------------------------
/// Custom Errors
/// -----------------------------------------------------------------------
/// @dev Throws if `initialize()` is called more than once.
error AlreadyInit();
/// @dev Throws if quorum exceeds `totalSupply(SIGN_KEY)`.
error QuorumOverSupply();
/// @dev Throws if quorum with `threshold = 0` is set.
error InvalidThreshold();
/// @dev Throws if `execute()` doesn't complete operation.
error ExecuteFailed();
/// -----------------------------------------------------------------------
/// Keep Storage/Logic
/// -----------------------------------------------------------------------
/// @dev Core ID key permission.
uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId);
/// @dev Default metadata fetcher for `uri()`.
Keep internal immutable uriFetcher;
/// @dev Record of states verifying `execute()`.
uint120 public nonce;
/// @dev SIGN_KEY threshold to `execute()`.
uint120 public quorum;
/// @dev Internal ID metadata mapping.
mapping(uint256 => string) internal _uris;
/// @dev ID metadata fetcher.
/// @param id ID to fetch from.
/// @return tokenURI Metadata.
function uri(uint256 id) public view virtual returns (string memory) {
string memory tokenURI = _uris[id];
if (bytes(tokenURI).length > 0) return tokenURI;
else return uriFetcher.uri(id);
}
/// @dev Access control check for ID key balance holders.
/// Initializes with `address(this)` having implicit permission
/// without writing to storage by checking `totalSupply()` is zero.
/// Otherwise, this permission can be set to additional accounts,
/// including retaining `address(this)`, via `mint()`.
function _authorized() internal view virtual returns (bool) {
if (
(totalSupply[CORE_KEY] == 0 && msg.sender == address(this)) ||
balanceOf[msg.sender][CORE_KEY] != 0 ||
balanceOf[msg.sender][uint32(msg.sig)] != 0
) return true;
else revert Unauthorized();
}
/// -----------------------------------------------------------------------
/// ERC165 Logic
/// -----------------------------------------------------------------------
/// @dev ERC165 interface detection.
/// @param interfaceId ID to check.
/// @return Fetch detection success.
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
// ERC165 Interface ID for ERC721TokenReceiver.
interfaceId == this.onERC721Received.selector ||
// ERC165 Interface ID for ERC1155TokenReceiver.
interfaceId == type(ERC1155TokenReceiver).interfaceId ||
// ERC165 interface ID for ERC1155MetadataURI.
interfaceId == this.uri.selector ||
// ERC165 Interface IDs for ERC1155.
super.supportsInterface(interfaceId);
}
/// -----------------------------------------------------------------------
/// ERC721 Receiver Logic
/// -----------------------------------------------------------------------
function onERC721Received(
address,
address,
uint256,
bytes calldata
) public payable virtual returns (bytes4) {
return this.onERC721Received.selector;
}
/// -----------------------------------------------------------------------
/// Initialization Logic
/// -----------------------------------------------------------------------
/// @notice Create Keep template.
/// @param _uriFetcher Metadata default.
constructor(Keep _uriFetcher) payable {
uriFetcher = _uriFetcher;
// Deploy as singleton.
quorum = 1;
}
/// @notice Initialize Keep configuration.
/// @param calls Initial Keep operations.
/// @param signers Initial signer set.
/// @param threshold Initial quorum.
function initialize(
Call[] calldata calls,
address[] calldata signers,
uint256 threshold
) public payable virtual {
if (quorum != 0) revert AlreadyInit();
if (threshold == 0) revert InvalidThreshold();
if (threshold > signers.length) revert QuorumOverSupply();
if (calls.length != 0) {
for (uint256 i; i < calls.length; ) {
_execute(
calls[i].op,
calls[i].to,
calls[i].value,
calls[i].data
);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
}
address previous;
address signer;
uint256 supply;
for (uint256 i; i < signers.length; ) {
signer = signers[i];
// Prevent zero and duplicate signers.
if (previous >= signer) revert InvalidSig();
previous = signer;
emit TransferSingle(tx.origin, address(0), signer, SIGN_KEY, 1);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++balanceOf[signer][SIGN_KEY];
++supply;
++i;
}
}
totalSupply[SIGN_KEY] = supply;
quorum = uint120(threshold);
}
/// -----------------------------------------------------------------------
/// Execution Logic
/// -----------------------------------------------------------------------
/// @notice Execute operation from Keep with signatures.
/// @param op Enum operation to execute.
/// @param to Address to send operation to.
/// @param value Amount of ETH to send in operation.
/// @param data Payload to send in operation.
/// @param sigs Array of Keep signatures in ascending order by addresses.
function execute(
Operation op,
address to,
uint256 value,
bytes calldata data,
Signature[] calldata sigs
) public payable virtual {
uint120 txNonce;
// Unchecked because the only math done is incrementing
// Keep nonce which cannot realistically overflow.
unchecked {
emit Executed(txNonce = nonce++, op, to, value, data);
}
// Begin signature validation with hashed inputs.
bytes32 hash = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Execute(uint8 op,address to,uint256 value,bytes data,uint120 nonce)"
),
op,
to,
value,
keccak256(data),
txNonce
)
)
)
);
// Start zero in loop to ensure ascending addresses.
address previous;
// Validation is length of quorum threshold.
uint256 threshold = quorum;
// Store outside loop for gas optimization.
Signature calldata sig;
for (uint256 i; i < threshold; ) {
// Load signature items.
sig = sigs[i];
address user = sig.user;
// Check SIGN_KEY balance.
// This also confirms non-zero `user`.
if (balanceOf[user][SIGN_KEY] == 0) revert InvalidSig();
// Check signature recovery.
_recoverSig(hash, user, sig.v, sig.r, sig.s);
// Check against duplicates.
if (previous >= user) revert InvalidSig();
// Memo signature for next iteration until quorum.
previous = user;
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
_execute(op, to, value, data);
}
/// @notice Relay operation from Keep via `execute()` or as ID key holder.
/// @param call Keep operation as struct of `op, to, value, data`.
function relay(Call calldata call) public payable virtual {
_authorized();
_execute(call.op, call.to, call.value, call.data);
emit Relayed(call);
}
/// @notice Relay operations from Keep via `execute()` or as ID key holder.
/// @param calls Keep operations as struct arrays of `op, to, value, data`.
function multirelay(Call[] calldata calls) public payable virtual {
_authorized();
for (uint256 i; i < calls.length; ) {
_execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit Multirelayed(calls);
}
function _execute(
Operation op,
address to,
uint256 value,
bytes memory data
) internal virtual {
if (op == Operation.call) {
bool success;
assembly {
success := call(
gas(),
to,
value,
add(data, 0x20),
mload(data),
0,
0
)
}
if (!success) revert ExecuteFailed();
} else if (op == Operation.delegatecall) {
bool success;
assembly {
success := delegatecall(
gas(),
to,
add(data, 0x20),
mload(data),
0,
0
)
}
if (!success) revert ExecuteFailed();
} else {
assembly {
to := create(value, add(data, 0x20), mload(data))
}
if (to == address(0)) revert ExecuteFailed();
}
}
/// -----------------------------------------------------------------------
/// Mint/Burn Logic
/// -----------------------------------------------------------------------
/// @notice ID minter.
/// @param to Recipient of mint.
/// @param id ID to mint.
/// @param amount ID balance to mint.
/// @param data Optional data payload.
function mint(
address to,
uint256 id,
uint256 amount,
bytes calldata data
) public payable virtual {
_authorized();
_mint(to, id, amount, data);
}
/// @notice ID burner.
/// @param from Account to burn from.
/// @param id ID to burn.
/// @param amount Balance to burn.
function burn(
address from,
uint256 id,
uint256 amount
) public payable virtual {
if (msg.sender != from)
if (!isApprovedForAll[from][msg.sender])
if (!_authorized()) revert Unauthorized();
_burn(from, id, amount);
if (id == SIGN_KEY)
if (quorum > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
}
/// -----------------------------------------------------------------------
/// Threshold Setting Logic
/// -----------------------------------------------------------------------
/// @notice Update Keep quorum threshold.
/// @param threshold Signature threshold for `execute()`.
function setQuorum(uint256 threshold) public payable virtual {
_authorized();
if (threshold == 0) revert InvalidThreshold();
if (threshold > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
quorum = uint120(threshold);
emit QuorumSet(threshold);
}
/// -----------------------------------------------------------------------
/// ID Setting Logic
/// -----------------------------------------------------------------------
/// @notice ID transferability setting.
/// @param id ID to set transferability for.
/// @param on Transferability setting.
function setTransferability(uint256 id, bool on) public payable virtual {
_authorized();
_setTransferability(id, on);
}
/// @notice ID transfer permission toggle.
/// @param id ID to set permission for.
/// @param on Permission setting.
/// @dev This sets account-based ID restriction globally.
function setPermission(uint256 id, bool on) public payable virtual {
_authorized();
_setPermission(id, on);
}
/// @notice ID transfer permission setting.
/// @param to Account to set permission for.
/// @param id ID to set permission for.
/// @param on Permission setting.
/// @dev This sets account-based ID restriction specifically.
function setUserPermission(
address to,
uint256 id,
bool on
) public payable virtual {
_authorized();
_setUserPermission(to, id, on);
}
/// @notice ID metadata setting.
/// @param id ID to set metadata for.
/// @param tokenURI Metadata setting.
function setURI(
uint256 id,
string calldata tokenURI
) public payable virtual {
_authorized();
_uris[id] = tokenURI;
emit URI(tokenURI, id);
}
}