@lukso/lsp11-contracts 0.1.2
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/README.md +17 -0
- package/artifacts/ILSP11SocialRecovery.json +670 -0
- package/artifacts/LSP11SocialRecovery.json +1171 -0
- package/contracts/ILSP11SocialRecovery.sol +326 -0
- package/contracts/LSP11Constants.sol +8 -0
- package/contracts/LSP11Errors.sol +181 -0
- package/contracts/LSP11SocialRecovery.sol +970 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +3 -0
- package/package.json +53 -0
@@ -0,0 +1,970 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.9;
|
3
|
+
|
4
|
+
// Interfaces
|
5
|
+
import {ILSP11SocialRecovery} from "./ILSP11SocialRecovery.sol";
|
6
|
+
|
7
|
+
// Libraries
|
8
|
+
import {
|
9
|
+
EnumerableSet
|
10
|
+
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
11
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
12
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
13
|
+
import {
|
14
|
+
ILSP25ExecuteRelayCall
|
15
|
+
} from "@lukso/lsp25-contracts/contracts/ILSP25ExecuteRelayCall.sol";
|
16
|
+
import {
|
17
|
+
LSP25MultiChannelNonce
|
18
|
+
} from "@lukso/lsp25-contracts/contracts/LSP25MultiChannelNonce.sol";
|
19
|
+
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
20
|
+
|
21
|
+
// Constants
|
22
|
+
// solhint-disable no-global-import
|
23
|
+
import {_INTERFACEID_LSP11, LSP11_VERSION} from "./LSP11Constants.sol";
|
24
|
+
|
25
|
+
// Errors
|
26
|
+
// solhint-disable no-global-import
|
27
|
+
import "./LSP11Errors.sol";
|
28
|
+
|
29
|
+
/**
|
30
|
+
* @title LSP11SocialRecovery
|
31
|
+
* @notice Contract providing a mechanism for account recovery through a designated set of guardians.
|
32
|
+
*/
|
33
|
+
contract LSP11SocialRecovery is
|
34
|
+
ERC165,
|
35
|
+
ILSP11SocialRecovery,
|
36
|
+
ILSP25ExecuteRelayCall,
|
37
|
+
LSP25MultiChannelNonce
|
38
|
+
{
|
39
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
40
|
+
using ECDSA for *;
|
41
|
+
|
42
|
+
/// @dev The default recovery delay set to 40 minutes.
|
43
|
+
uint256 public constant DEFAULT_RECOVERY_DELAY = 40 minutes;
|
44
|
+
|
45
|
+
/// @dev The delay between the commitment and the recovery process.
|
46
|
+
uint256 public constant COMMITMEMT_DELAY = 1 minutes;
|
47
|
+
|
48
|
+
/**
|
49
|
+
* @dev Stores the hash of a commitment along with a timestamp.
|
50
|
+
* The commitment is the keccak256 hash of the address to be recovered and
|
51
|
+
* the secret hash abi-encoded.
|
52
|
+
*/
|
53
|
+
struct CommitmentInfo {
|
54
|
+
bytes32 commitment;
|
55
|
+
uint256 timestamp;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* @dev This mapping stores the set of guardians associated with each account.
|
60
|
+
*/
|
61
|
+
mapping(address => EnumerableSet.AddressSet) internal _guardiansOf;
|
62
|
+
|
63
|
+
/**
|
64
|
+
* @dev This mapping stores the guardian threshold for each account.
|
65
|
+
*/
|
66
|
+
mapping(address => uint256) internal _guardiansThresholdOf;
|
67
|
+
|
68
|
+
/**
|
69
|
+
* @dev This mapping stores the delay associated with each account.
|
70
|
+
*/
|
71
|
+
mapping(address => uint256) internal _recoveryDelayOf;
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @dev This mapping stores if the account use the default recovery.
|
75
|
+
*/
|
76
|
+
mapping(address => bool) internal _defaultRecoveryRemoved;
|
77
|
+
|
78
|
+
/**
|
79
|
+
* @dev This mapping stores the secret hash associated with each account.
|
80
|
+
*/
|
81
|
+
mapping(address => bytes32) internal _secretHashOf;
|
82
|
+
|
83
|
+
/**
|
84
|
+
* @dev This mapping stores the successful recovery counter for each account.
|
85
|
+
*/
|
86
|
+
mapping(address => uint256) internal _recoveryCounterOf;
|
87
|
+
|
88
|
+
/**
|
89
|
+
* @dev This mapping stores the voted address for recovery by guardians for each account in a specific recovery counter.
|
90
|
+
*/
|
91
|
+
mapping(address => mapping(uint256 => mapping(address => address)))
|
92
|
+
internal _guardiansVotedFor;
|
93
|
+
|
94
|
+
/**
|
95
|
+
* @dev This mapping stores the number of votes an address has received from guardians for each account in a specific recovery counter.
|
96
|
+
*/
|
97
|
+
mapping(address => mapping(uint256 => mapping(address => uint256)))
|
98
|
+
internal _votesOfguardianVotedAddress;
|
99
|
+
|
100
|
+
/**
|
101
|
+
* @dev This mapping stores the commitment associated with an address for recovery for each account in a specific recovery counter.
|
102
|
+
*/
|
103
|
+
mapping(address => mapping(uint256 => mapping(address => CommitmentInfo)))
|
104
|
+
internal _commitmentInfoOf;
|
105
|
+
|
106
|
+
/**
|
107
|
+
* @dev First recovery timestamp in a recovery counter
|
108
|
+
*/
|
109
|
+
mapping(address => mapping(uint256 => uint256))
|
110
|
+
internal _firstRecoveryTimestamp;
|
111
|
+
|
112
|
+
/**
|
113
|
+
* @notice Modifier to ensure that a function is called only by designated guardians for a specific account.
|
114
|
+
* @dev Throws if called by any account other than the guardians
|
115
|
+
* @param account The account to check against for guardian status.
|
116
|
+
*/
|
117
|
+
modifier onlyGuardians(address account, address guardian) virtual {
|
118
|
+
if (guardian != msg.sender)
|
119
|
+
revert CallerIsNotGuardian(guardian, msg.sender);
|
120
|
+
|
121
|
+
if (!(_guardiansOf[account].contains(guardian)))
|
122
|
+
revert NotAGuardianOfTheAccount(account, guardian);
|
123
|
+
_;
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* @notice Modifier to ensure that the account provided is the same as the caller
|
128
|
+
* @dev Throws if the caller is writing to another account
|
129
|
+
* @param account The account to check against the caller.
|
130
|
+
*/
|
131
|
+
modifier accountIsCaller(address account) virtual {
|
132
|
+
if (account != msg.sender)
|
133
|
+
revert CallerIsNotTheAccount(account, msg.sender);
|
134
|
+
_;
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* @notice Executes multiple calls in a single transaction.
|
139
|
+
* @param data An array of calldata bytes to be executed.
|
140
|
+
* @return results An array of bytes containing the results of each executed call.
|
141
|
+
* @dev This function allows for multiple calls to be made in a single transaction, improving efficiency.
|
142
|
+
* If a call fails, the function will attempt to bubble up the revert reason or revert with a default message.
|
143
|
+
*/
|
144
|
+
function batchCalls(
|
145
|
+
bytes[] calldata data
|
146
|
+
) public virtual returns (bytes[] memory results) {
|
147
|
+
results = new bytes[](data.length);
|
148
|
+
for (uint256 i; i < data.length; ) {
|
149
|
+
(bool success, bytes memory result) = address(this).delegatecall(
|
150
|
+
data[i]
|
151
|
+
);
|
152
|
+
|
153
|
+
if (!success) {
|
154
|
+
// Look for revert reason and bubble it up if present
|
155
|
+
if (result.length != 0) {
|
156
|
+
// The easiest way to bubble the revert reason is using memory via assembly
|
157
|
+
// solhint-disable no-inline-assembly
|
158
|
+
/// @solidity memory-safe-assembly
|
159
|
+
assembly {
|
160
|
+
let returndata_size := mload(result)
|
161
|
+
revert(add(32, result), returndata_size)
|
162
|
+
}
|
163
|
+
} else {
|
164
|
+
revert BatchCallsFailed(i);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
results[i] = result;
|
169
|
+
|
170
|
+
unchecked {
|
171
|
+
++i;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
/**
|
177
|
+
* @inheritdoc ILSP25ExecuteRelayCall
|
178
|
+
*/
|
179
|
+
function executeRelayCall(
|
180
|
+
bytes calldata signature,
|
181
|
+
uint256 nonce,
|
182
|
+
uint256 validityTimestamps,
|
183
|
+
bytes calldata payload
|
184
|
+
) public payable returns (bytes memory) {
|
185
|
+
return
|
186
|
+
_executeRelayCall(
|
187
|
+
signature,
|
188
|
+
nonce,
|
189
|
+
validityTimestamps,
|
190
|
+
msg.value,
|
191
|
+
payload
|
192
|
+
);
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* @inheritdoc ILSP25ExecuteRelayCall
|
197
|
+
*/
|
198
|
+
function executeRelayCallBatch(
|
199
|
+
bytes[] calldata signatures,
|
200
|
+
uint256[] calldata nonces,
|
201
|
+
uint256[] calldata validityTimestamps,
|
202
|
+
uint256[] calldata values,
|
203
|
+
bytes[] calldata payloads
|
204
|
+
) public payable virtual override returns (bytes[] memory) {
|
205
|
+
if (
|
206
|
+
signatures.length != nonces.length ||
|
207
|
+
nonces.length != validityTimestamps.length ||
|
208
|
+
validityTimestamps.length != values.length ||
|
209
|
+
values.length != payloads.length
|
210
|
+
) {
|
211
|
+
revert BatchExecuteRelayCallParamsLengthMismatch();
|
212
|
+
}
|
213
|
+
|
214
|
+
bytes[] memory results = new bytes[](payloads.length);
|
215
|
+
uint256 totalValues;
|
216
|
+
|
217
|
+
for (uint256 ii; ii < payloads.length; ) {
|
218
|
+
if ((totalValues += values[ii]) > msg.value) {
|
219
|
+
revert LSP11BatchInsufficientValueSent(totalValues, msg.value);
|
220
|
+
}
|
221
|
+
|
222
|
+
results[ii] = _executeRelayCall(
|
223
|
+
signatures[ii],
|
224
|
+
nonces[ii],
|
225
|
+
validityTimestamps[ii],
|
226
|
+
values[ii],
|
227
|
+
payloads[ii]
|
228
|
+
);
|
229
|
+
|
230
|
+
unchecked {
|
231
|
+
++ii;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
if (totalValues < msg.value) {
|
236
|
+
revert LSP11BatchExcessiveValueSent(totalValues, msg.value);
|
237
|
+
}
|
238
|
+
|
239
|
+
return results;
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* @inheritdoc ILSP25ExecuteRelayCall
|
244
|
+
*/
|
245
|
+
function getNonce(
|
246
|
+
address from,
|
247
|
+
uint128 channelId
|
248
|
+
) external view override returns (uint256) {
|
249
|
+
return _getNonce(from, channelId);
|
250
|
+
}
|
251
|
+
|
252
|
+
function supportsInterface(
|
253
|
+
bytes4 interfaceId
|
254
|
+
) public view override returns (bool) {
|
255
|
+
return
|
256
|
+
interfaceId == _INTERFACEID_LSP11 ||
|
257
|
+
interfaceId == type(ILSP25ExecuteRelayCall).interfaceId ||
|
258
|
+
super.supportsInterface(interfaceId);
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* @notice Get the array of addresses representing guardians associated with an account.
|
263
|
+
* @param account The account for which guardians are queried.
|
264
|
+
* @return An array of addresses representing guardians for the given account.
|
265
|
+
*/
|
266
|
+
function getGuardiansOf(
|
267
|
+
address account
|
268
|
+
) public view returns (address[] memory) {
|
269
|
+
return _guardiansOf[account].values();
|
270
|
+
}
|
271
|
+
|
272
|
+
/**
|
273
|
+
* @notice Check if an address is a guardian for a specific account.
|
274
|
+
* @param account The account to check for guardian status.
|
275
|
+
* @param guardianAddress The address to verify if it's a guardian for the given account..
|
276
|
+
* @return A boolean indicating whether the address is a guardian for the given account.
|
277
|
+
*/
|
278
|
+
function isGuardianOf(
|
279
|
+
address account,
|
280
|
+
address guardianAddress
|
281
|
+
) public view returns (bool) {
|
282
|
+
return _guardiansOf[account].contains(guardianAddress);
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* @notice Get the guardian threshold for a specific account.
|
287
|
+
* @param account The account for which the guardian threshold is queried.
|
288
|
+
* @return The guardian threshold set for the given account.
|
289
|
+
*/
|
290
|
+
function getGuardiansThresholdOf(
|
291
|
+
address account
|
292
|
+
) public view returns (uint256) {
|
293
|
+
return _guardiansThresholdOf[account];
|
294
|
+
}
|
295
|
+
|
296
|
+
/**
|
297
|
+
* @notice Get the secret hash associated with a specific account.
|
298
|
+
* @param account The account for which the secret hash is queried.
|
299
|
+
* @return The secret hash associated with the given account.
|
300
|
+
*/
|
301
|
+
function getSecretHashOf(address account) public view returns (bytes32) {
|
302
|
+
return _secretHashOf[account];
|
303
|
+
}
|
304
|
+
|
305
|
+
/**
|
306
|
+
* @notice Get the recovery delay associated with a specific account.
|
307
|
+
* @param account The account for which the recovery delay is queried.
|
308
|
+
* @return The recovery delay associated with the given account.
|
309
|
+
*/
|
310
|
+
function getRecoveryDelayOf(address account) public view returns (uint256) {
|
311
|
+
if (!_defaultRecoveryRemoved[account]) return DEFAULT_RECOVERY_DELAY;
|
312
|
+
return _recoveryDelayOf[account];
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* @notice Get the successful recovery counter for a specific account.
|
317
|
+
* @param account The account for which the recovery counter is queried.
|
318
|
+
* @return The successful recovery counter for the given account.
|
319
|
+
*/
|
320
|
+
function getRecoveryCounterOf(
|
321
|
+
address account
|
322
|
+
) public view returns (uint256) {
|
323
|
+
return _recoveryCounterOf[account];
|
324
|
+
}
|
325
|
+
|
326
|
+
/**
|
327
|
+
* @notice Get the timestamp of the first recovery timestamp of the vote for a specific account and recovery counter.
|
328
|
+
* @param account The account for which the vote is queried.
|
329
|
+
* @param recoveryCounter The recovery counter for which the vote is queried.
|
330
|
+
* @return The timestamp of the first recovery timestamp of the vote for a specific account and recovery counter.
|
331
|
+
*/
|
332
|
+
function getFirstRecoveryTimestampOf(
|
333
|
+
address account,
|
334
|
+
uint256 recoveryCounter
|
335
|
+
) public view returns (uint256) {
|
336
|
+
return _firstRecoveryTimestamp[account][recoveryCounter];
|
337
|
+
}
|
338
|
+
|
339
|
+
/**
|
340
|
+
* @notice Get the address voted for recovery by a guardian for a specific account and recovery counter.
|
341
|
+
* @param account The account for which the vote is queried.
|
342
|
+
* @param recoveryCounter The recovery counter for which the vote is queried.
|
343
|
+
* @param guardian The guardian whose vote is queried.
|
344
|
+
* @return The address voted for recovery by the specified guardian for the given account and recovery counter.
|
345
|
+
*/
|
346
|
+
function getVotedAddressByGuardian(
|
347
|
+
address account,
|
348
|
+
uint256 recoveryCounter,
|
349
|
+
address guardian
|
350
|
+
) public view returns (address) {
|
351
|
+
return _guardiansVotedFor[account][recoveryCounter][guardian];
|
352
|
+
}
|
353
|
+
|
354
|
+
/**
|
355
|
+
* @notice Get the number of votes an address has received from guardians for a specific account and recovery counter.
|
356
|
+
* @param account The account for which the votes are queried.
|
357
|
+
* @param recoveryCounter The recovery counter for which the votes are queried.
|
358
|
+
* @param votedAddress The address for which the votes are queried.
|
359
|
+
* @return The number of votes the specified address has received from guardians for the given account and recovery counter.
|
360
|
+
*/
|
361
|
+
function getVotesOfGuardianVotedAddress(
|
362
|
+
address account,
|
363
|
+
uint256 recoveryCounter,
|
364
|
+
address votedAddress
|
365
|
+
) public view returns (uint256) {
|
366
|
+
return
|
367
|
+
_votesOfguardianVotedAddress[account][recoveryCounter][
|
368
|
+
votedAddress
|
369
|
+
];
|
370
|
+
}
|
371
|
+
|
372
|
+
/**
|
373
|
+
* @notice Get the commitment associated with an address for recovery for a specific account and recovery counter.
|
374
|
+
* @param account The account for which the commitment is queried.
|
375
|
+
* @param recoveryCounter The recovery counter for which the commitment is queried.
|
376
|
+
* @param committedBy The address who made the commitment.
|
377
|
+
* @return The bytes32 commitment and its timestamp associated with the specified address for recovery for the given account and recovery counter.
|
378
|
+
*/
|
379
|
+
function getCommitmentInfoOf(
|
380
|
+
address account,
|
381
|
+
uint256 recoveryCounter,
|
382
|
+
address committedBy
|
383
|
+
) public view returns (bytes32, uint256) {
|
384
|
+
CommitmentInfo memory _commitment = _commitmentInfoOf[account][
|
385
|
+
recoveryCounter
|
386
|
+
][committedBy];
|
387
|
+
return (_commitment.commitment, _commitment.timestamp);
|
388
|
+
}
|
389
|
+
|
390
|
+
/**
|
391
|
+
* @notice Checks if the votes received by a given address from guardians have reached the threshold necessary for account recovery.
|
392
|
+
* @param account The account for which the threshold check is performed.
|
393
|
+
* @param recoveryCounter The recovery counter for which the threshold check is performed.
|
394
|
+
* @param votedAddress The address for which the votes are counted.
|
395
|
+
* @return A boolean indicating whether the votes for the specified address have reached the necessary threshold for the given account and recovery counter.
|
396
|
+
* @dev This function evaluates if the number of votes from guardians for a specific voted address meets or exceeds the required threshold for account recovery.
|
397
|
+
* This is part of the account recovery process where guardians vote for the legitimacy of a recovery address.
|
398
|
+
*/
|
399
|
+
function hasReachedThreshold(
|
400
|
+
address account,
|
401
|
+
uint256 recoveryCounter,
|
402
|
+
address votedAddress
|
403
|
+
) public view returns (bool) {
|
404
|
+
return
|
405
|
+
_guardiansThresholdOf[account] ==
|
406
|
+
_votesOfguardianVotedAddress[account][recoveryCounter][
|
407
|
+
votedAddress
|
408
|
+
];
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* @notice Adds a new guardian to the calling account.
|
413
|
+
* @param account The address of the account to which the guardian will be added.
|
414
|
+
* @param newGuardian The address of the new guardian to be added.
|
415
|
+
* @dev This function allows the account holder to add a new guardian to their account.
|
416
|
+
* If the provided address is already a guardian for the account, the function will revert.
|
417
|
+
* Emits a `GuardianAdded` event upon successful addition of the guardian.
|
418
|
+
*/
|
419
|
+
function addGuardian(
|
420
|
+
address account,
|
421
|
+
address newGuardian
|
422
|
+
) public virtual accountIsCaller(account) {
|
423
|
+
bool guardianAdded = _guardiansOf[account].add(newGuardian);
|
424
|
+
if (!guardianAdded) revert GuardianAlreadyExists(account, newGuardian);
|
425
|
+
emit GuardianAdded(account, newGuardian);
|
426
|
+
}
|
427
|
+
|
428
|
+
/**
|
429
|
+
* @notice Removes an existing guardian from the calling account.
|
430
|
+
* @param account The address of the account to which the guardian will be removed.
|
431
|
+
* @param existingGuardian The address of the existing guardian to be removed.
|
432
|
+
* @dev This function allows the account holder to remove an existing guardian from their account.
|
433
|
+
* If the provided address is not a current guardian or the removal would violate the guardian threshold, the function will revert.
|
434
|
+
* Emits a `GuardianRemoved` event upon successful removal of the guardian.
|
435
|
+
*/
|
436
|
+
function removeGuardian(
|
437
|
+
address account,
|
438
|
+
address existingGuardian
|
439
|
+
) public virtual accountIsCaller(account) {
|
440
|
+
if (!_guardiansOf[account].contains(existingGuardian))
|
441
|
+
revert GuardianNotFound(account, existingGuardian);
|
442
|
+
|
443
|
+
if (_guardiansOf[account].length() == _guardiansThresholdOf[account])
|
444
|
+
revert GuardianNumberCannotGoBelowThreshold(
|
445
|
+
account,
|
446
|
+
_guardiansThresholdOf[account]
|
447
|
+
);
|
448
|
+
|
449
|
+
_guardiansOf[account].remove(existingGuardian);
|
450
|
+
emit GuardianRemoved(account, existingGuardian);
|
451
|
+
}
|
452
|
+
|
453
|
+
/**
|
454
|
+
* @notice Sets the guardian threshold for the calling account.
|
455
|
+
* @param account The address of the account to which the threshold will be set.
|
456
|
+
* @param newThreshold The new guardian threshold to be set for the calling account.
|
457
|
+
* @dev This function allows the account holder to set the guardian threshold for their account.
|
458
|
+
* If the provided threshold exceeds the number of current guardians, the function will revert.
|
459
|
+
* Emits a `GuardiansThresholdChanged` event upon successful threshold modification.
|
460
|
+
*/
|
461
|
+
function setGuardiansThreshold(
|
462
|
+
address account,
|
463
|
+
uint256 newThreshold
|
464
|
+
) public virtual accountIsCaller(account) {
|
465
|
+
if (newThreshold > _guardiansOf[account].length())
|
466
|
+
revert ThresholdExceedsGuardianNumber(account, newThreshold);
|
467
|
+
|
468
|
+
_guardiansThresholdOf[account] = newThreshold;
|
469
|
+
emit GuardiansThresholdChanged(account, newThreshold);
|
470
|
+
}
|
471
|
+
|
472
|
+
/**
|
473
|
+
* @notice Sets the recovery secret hash for the calling account.
|
474
|
+
* @param account The address of the account to which the recovery secret hash will be set.
|
475
|
+
* @param newRecoverSecretHash The new recovery secret hash to be set for the calling account.
|
476
|
+
* @dev This function allows the account holder to set a new recovery secret hash for their account.
|
477
|
+
* In this implementation, the secret hash MUST be set salted with the account address, using
|
478
|
+
* keccak256(abi.encode(account, secretHash)).
|
479
|
+
* Emits a `SecretHashChanged` event upon successful secret hash modification.
|
480
|
+
*/
|
481
|
+
function setRecoverySecretHash(
|
482
|
+
address account,
|
483
|
+
bytes32 newRecoverSecretHash
|
484
|
+
) public virtual accountIsCaller(account) {
|
485
|
+
_secretHashOf[account] = newRecoverSecretHash;
|
486
|
+
emit SecretHashChanged(account, newRecoverSecretHash);
|
487
|
+
}
|
488
|
+
|
489
|
+
/**
|
490
|
+
* @notice Sets the recovery delay for the calling account.
|
491
|
+
* @param account The address of the account to which the recovery delay will be set.
|
492
|
+
* @param recoveryDelay The new recovery delay in seconds to be set for the calling account.
|
493
|
+
* @dev This function allows the account to set a new recovery delay for their account.
|
494
|
+
* Emits a `RecoveryDelayChanged` event upon successful secret hash modification.
|
495
|
+
*/
|
496
|
+
function setRecoveryDelay(
|
497
|
+
address account,
|
498
|
+
uint256 recoveryDelay
|
499
|
+
) public virtual accountIsCaller(account) {
|
500
|
+
_recoveryDelayOf[account] = recoveryDelay;
|
501
|
+
emit RecoveryDelayChanged(account, recoveryDelay);
|
502
|
+
|
503
|
+
if (!_defaultRecoveryRemoved[account]) {
|
504
|
+
_defaultRecoveryRemoved[account] = true;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
/**
|
509
|
+
* @notice Allows a guardian to vote for an address for a recovery process
|
510
|
+
* @param account The account for which the vote is being cast.
|
511
|
+
* @param guardianVotedAddress The address voted by the guardian for recovery.
|
512
|
+
* @dev This function allows a guardian to vote for an address to be recovered in a recovery process.
|
513
|
+
* If the guardian has already voted for the provided address, the function will revert.
|
514
|
+
* Emits a `GuardianVotedFor` event upon successful vote.
|
515
|
+
*/
|
516
|
+
function voteForRecovery(
|
517
|
+
address account,
|
518
|
+
address guardian,
|
519
|
+
address guardianVotedAddress
|
520
|
+
) public virtual onlyGuardians(account, guardian) {
|
521
|
+
uint256 counter = _recoveryCounterOf[account];
|
522
|
+
|
523
|
+
uint256 recoveryTimestamp = _firstRecoveryTimestamp[account][counter];
|
524
|
+
|
525
|
+
if (recoveryTimestamp == 0) {
|
526
|
+
// solhint-disable not-rely-on-time
|
527
|
+
_firstRecoveryTimestamp[account][counter] = block.timestamp;
|
528
|
+
}
|
529
|
+
|
530
|
+
address previousVotedForAddressByGuardian = _guardiansVotedFor[account][
|
531
|
+
counter
|
532
|
+
][guardian];
|
533
|
+
|
534
|
+
// Cannot vote to the same person twice
|
535
|
+
if (guardianVotedAddress == previousVotedForAddressByGuardian)
|
536
|
+
revert CannotVoteToAddressTwice(
|
537
|
+
account,
|
538
|
+
guardian,
|
539
|
+
guardianVotedAddress
|
540
|
+
);
|
541
|
+
|
542
|
+
// If didn't vote before or reset
|
543
|
+
if (previousVotedForAddressByGuardian == address(0)) {
|
544
|
+
_guardiansVotedFor[account][counter][
|
545
|
+
guardian
|
546
|
+
] = guardianVotedAddress;
|
547
|
+
_votesOfguardianVotedAddress[account][counter][
|
548
|
+
guardianVotedAddress
|
549
|
+
]++;
|
550
|
+
}
|
551
|
+
|
552
|
+
if (
|
553
|
+
guardianVotedAddress != previousVotedForAddressByGuardian &&
|
554
|
+
previousVotedForAddressByGuardian != address(0)
|
555
|
+
) {
|
556
|
+
_guardiansVotedFor[account][counter][
|
557
|
+
guardian
|
558
|
+
] = guardianVotedAddress;
|
559
|
+
_votesOfguardianVotedAddress[account][counter][
|
560
|
+
previousVotedForAddressByGuardian
|
561
|
+
]--;
|
562
|
+
|
563
|
+
// If the voted address for is address(0) the intention is to reset, not vote for address 0
|
564
|
+
if (guardianVotedAddress != address(0)) {
|
565
|
+
_votesOfguardianVotedAddress[account][counter][
|
566
|
+
guardianVotedAddress
|
567
|
+
]++;
|
568
|
+
}
|
569
|
+
}
|
570
|
+
|
571
|
+
emit GuardianVotedFor(account, counter, guardian, guardianVotedAddress);
|
572
|
+
}
|
573
|
+
|
574
|
+
/**
|
575
|
+
* @notice Cancels the ongoing recovery process for the account by increasing the recovery counter.
|
576
|
+
* @param account The address of the account to which the recovery process will be canceled.
|
577
|
+
* @dev This function allows the account holder to cancel the ongoing recovery process by incrementing the recovery counter.
|
578
|
+
* Emits a `RecoveryCancelled` event upon successful cancellation of the recovery process.
|
579
|
+
*/
|
580
|
+
function cancelRecoveryProcess(
|
581
|
+
address account
|
582
|
+
) public accountIsCaller(account) {
|
583
|
+
uint256 previousRecoveryCounter = _recoveryCounterOf[account]++;
|
584
|
+
emit RecoveryCancelled(account, previousRecoveryCounter);
|
585
|
+
}
|
586
|
+
|
587
|
+
/**
|
588
|
+
* @notice Commits a secret hash for an address to be recovered.
|
589
|
+
* @param account The account for which the secret hash is being committed.
|
590
|
+
* @param commitment The commitment associated with the secret hash.
|
591
|
+
* @dev This function allows an address to commit a secret hash for the recovery process.
|
592
|
+
* If the guardian has not voted for the provided address, the function will revert.
|
593
|
+
* The commitment in this implementation is `keccak256(abi.encode(votedAddress, secretHash)`.
|
594
|
+
*/
|
595
|
+
function commitToRecover(
|
596
|
+
address account,
|
597
|
+
address votedAddress,
|
598
|
+
bytes32 commitment
|
599
|
+
) public {
|
600
|
+
if (votedAddress != msg.sender)
|
601
|
+
revert CallerIsNotVotedAddress(votedAddress, msg.sender);
|
602
|
+
|
603
|
+
uint256 recoveryCounter = _recoveryCounterOf[account];
|
604
|
+
|
605
|
+
_commitToRecover(account, recoveryCounter, votedAddress, commitment);
|
606
|
+
}
|
607
|
+
|
608
|
+
/**
|
609
|
+
* @notice Initiates the account recovery process.
|
610
|
+
* @param account The account for which the recovery is being initiated.
|
611
|
+
* @param secretHash The secret hash associated with the recovery process unsalted with the account address.
|
612
|
+
* @param newSecretHash The new secret hash to be set for the account.
|
613
|
+
* @param calldataToExecute The calldata to be executed during the recovery process.
|
614
|
+
* @dev This function initiates the account recovery process and executes the provided calldata.
|
615
|
+
* If the new secret hash is zero or the number of votes is less than the guardian threshold, the function will revert.
|
616
|
+
* Emits a `RecoveryProcessSuccessful` event upon successful recovery process.
|
617
|
+
*/
|
618
|
+
function recoverAccess(
|
619
|
+
address account,
|
620
|
+
address votedAddress,
|
621
|
+
bytes32 secretHash,
|
622
|
+
bytes32 newSecretHash,
|
623
|
+
bytes calldata calldataToExecute
|
624
|
+
) public payable returns (bytes memory) {
|
625
|
+
if (votedAddress != msg.sender)
|
626
|
+
revert CallerIsNotVotedAddress(votedAddress, msg.sender);
|
627
|
+
|
628
|
+
// retrieve current recovery counter
|
629
|
+
uint256 accountRecoveryCounter = _recoveryCounterOf[account];
|
630
|
+
|
631
|
+
return
|
632
|
+
_recoverAccess(
|
633
|
+
account,
|
634
|
+
accountRecoveryCounter,
|
635
|
+
votedAddress,
|
636
|
+
secretHash,
|
637
|
+
newSecretHash,
|
638
|
+
msg.value,
|
639
|
+
calldataToExecute
|
640
|
+
);
|
641
|
+
}
|
642
|
+
|
643
|
+
/**
|
644
|
+
* @dev Internal function to commit a new recovery process. It stores a new commitment for a recovery process.
|
645
|
+
* @param account The account for which recovery is being committed.
|
646
|
+
* @param recoveryCounter The current recovery counter for the account.
|
647
|
+
* @param votedAddress The address that is being proposed for recovery by the guardian.
|
648
|
+
* @param commitment The commitment hash representing the recovery process.
|
649
|
+
*/
|
650
|
+
function _commitToRecover(
|
651
|
+
address account,
|
652
|
+
uint256 recoveryCounter,
|
653
|
+
address votedAddress,
|
654
|
+
bytes32 commitment
|
655
|
+
) internal {
|
656
|
+
// solhint-disable not-rely-on-time
|
657
|
+
CommitmentInfo memory _commitment = CommitmentInfo(
|
658
|
+
commitment,
|
659
|
+
block.timestamp
|
660
|
+
);
|
661
|
+
_commitmentInfoOf[account][recoveryCounter][votedAddress] = _commitment;
|
662
|
+
|
663
|
+
emit SecretHashCommitted(
|
664
|
+
account,
|
665
|
+
recoveryCounter,
|
666
|
+
votedAddress,
|
667
|
+
commitment
|
668
|
+
);
|
669
|
+
}
|
670
|
+
|
671
|
+
/**
|
672
|
+
* @dev Internal function to execute relay calls for `commitToRecover` and `recoverAccess`.
|
673
|
+
* @param signature The signature of the relay call.
|
674
|
+
* @param nonce The nonce for replay protection.
|
675
|
+
* @param validityTimestamps Timestamps defining the validity period of the call.
|
676
|
+
* @param msgValue The message value (in ether).
|
677
|
+
* @param payload The payload of the call.
|
678
|
+
* @return result Returns the bytes memory result of the executed call.
|
679
|
+
*/
|
680
|
+
function _executeRelayCall(
|
681
|
+
bytes calldata signature,
|
682
|
+
uint256 nonce,
|
683
|
+
uint256 validityTimestamps,
|
684
|
+
uint256 msgValue,
|
685
|
+
bytes calldata payload
|
686
|
+
) internal returns (bytes memory result) {
|
687
|
+
bytes4 functionSelector = bytes4(payload);
|
688
|
+
if (functionSelector == this.commitToRecover.selector) {
|
689
|
+
_verifyCanCommitToRecover(
|
690
|
+
signature,
|
691
|
+
nonce,
|
692
|
+
validityTimestamps,
|
693
|
+
msgValue,
|
694
|
+
payload
|
695
|
+
);
|
696
|
+
} else if (functionSelector == this.recoverAccess.selector) {
|
697
|
+
return
|
698
|
+
_verifyCanRecoverAccess(
|
699
|
+
signature,
|
700
|
+
nonce,
|
701
|
+
validityTimestamps,
|
702
|
+
msgValue,
|
703
|
+
payload
|
704
|
+
);
|
705
|
+
} else {
|
706
|
+
revert RelayCallNotSupported(functionSelector);
|
707
|
+
}
|
708
|
+
}
|
709
|
+
|
710
|
+
/**
|
711
|
+
* @dev Internal function to verify the signature and execute the `commitToRecover` function.
|
712
|
+
* @param signature The signature to verify.
|
713
|
+
* @param nonce The nonce used for replay protection.
|
714
|
+
* @param validityTimestamps Timestamps for the validity of the signature.
|
715
|
+
* @param msgValue The message value.
|
716
|
+
* @param commitToRecoverPayload The payload specific to the `commitToRecover` function.
|
717
|
+
*/
|
718
|
+
function _verifyCanCommitToRecover(
|
719
|
+
bytes memory signature,
|
720
|
+
uint256 nonce,
|
721
|
+
uint256 validityTimestamps,
|
722
|
+
uint256 msgValue,
|
723
|
+
bytes calldata commitToRecoverPayload
|
724
|
+
) internal {
|
725
|
+
(address account, address votedAddress, bytes32 commitment) = abi
|
726
|
+
.decode(commitToRecoverPayload[4:], (address, address, bytes32));
|
727
|
+
|
728
|
+
uint256 recoveryCounter = _recoveryCounterOf[account];
|
729
|
+
|
730
|
+
_sigChecks(
|
731
|
+
signature,
|
732
|
+
nonce,
|
733
|
+
validityTimestamps,
|
734
|
+
msgValue,
|
735
|
+
recoveryCounter,
|
736
|
+
commitToRecoverPayload,
|
737
|
+
votedAddress
|
738
|
+
);
|
739
|
+
|
740
|
+
_commitToRecover(account, recoveryCounter, votedAddress, commitment);
|
741
|
+
}
|
742
|
+
|
743
|
+
/**
|
744
|
+
* @dev Internal function to verify the signature and execute the `recoverAccess` function.
|
745
|
+
* @param signature The signature to verify.
|
746
|
+
* @param nonce The nonce used for replay protection.
|
747
|
+
* @param validityTimestamp The timestamp for the validity of the signature.
|
748
|
+
* @param msgValue The message value.
|
749
|
+
* @param recoverAccessPayload The payload specific to the `recoverAccess` function.
|
750
|
+
*/
|
751
|
+
function _verifyCanRecoverAccess(
|
752
|
+
bytes memory signature,
|
753
|
+
uint256 nonce,
|
754
|
+
uint256 validityTimestamp,
|
755
|
+
uint256 msgValue,
|
756
|
+
bytes calldata recoverAccessPayload
|
757
|
+
) internal returns (bytes memory) {
|
758
|
+
(
|
759
|
+
address account,
|
760
|
+
address votedAddress,
|
761
|
+
bytes32 secretHash,
|
762
|
+
bytes32 newSecretHash,
|
763
|
+
bytes memory calldataToExecute
|
764
|
+
) = abi.decode(
|
765
|
+
recoverAccessPayload[4:],
|
766
|
+
(address, address, bytes32, bytes32, bytes)
|
767
|
+
);
|
768
|
+
|
769
|
+
uint256 recoveryCounter = _recoveryCounterOf[account];
|
770
|
+
|
771
|
+
_sigChecks(
|
772
|
+
signature,
|
773
|
+
nonce,
|
774
|
+
validityTimestamp,
|
775
|
+
msgValue,
|
776
|
+
recoveryCounter,
|
777
|
+
recoverAccessPayload,
|
778
|
+
votedAddress
|
779
|
+
);
|
780
|
+
|
781
|
+
return
|
782
|
+
_recoverAccess(
|
783
|
+
account,
|
784
|
+
recoveryCounter,
|
785
|
+
votedAddress,
|
786
|
+
secretHash,
|
787
|
+
newSecretHash,
|
788
|
+
msgValue,
|
789
|
+
calldataToExecute
|
790
|
+
);
|
791
|
+
}
|
792
|
+
|
793
|
+
/**
|
794
|
+
* @dev Internal function to perform signature and nonce checks, and to verify the validity timestamp.
|
795
|
+
* @param signature The signature to check.
|
796
|
+
* @param nonce The nonce for replay protection.
|
797
|
+
* @param validityTimestamp The validity timestamp for the signature.
|
798
|
+
* @param msgValue The message value.
|
799
|
+
* @param recoveryCounter The recovery counter.
|
800
|
+
* @param payload The payload of the call.
|
801
|
+
* @param votedAddress The address voted for recovery.
|
802
|
+
*/
|
803
|
+
function _sigChecks(
|
804
|
+
bytes memory signature,
|
805
|
+
uint256 nonce,
|
806
|
+
uint256 validityTimestamp,
|
807
|
+
uint256 msgValue,
|
808
|
+
uint256 recoveryCounter,
|
809
|
+
bytes memory payload,
|
810
|
+
address votedAddress
|
811
|
+
) internal {
|
812
|
+
address recoveredAddress = _recoverSignerFromLSP11Signature(
|
813
|
+
signature,
|
814
|
+
nonce,
|
815
|
+
validityTimestamp,
|
816
|
+
msgValue,
|
817
|
+
recoveryCounter,
|
818
|
+
payload
|
819
|
+
);
|
820
|
+
|
821
|
+
if (!_isValidNonce(recoveredAddress, nonce)) {
|
822
|
+
revert InvalidRelayNonce(recoveredAddress, nonce, signature);
|
823
|
+
}
|
824
|
+
|
825
|
+
// increase nonce after successful verification
|
826
|
+
_nonceStore[recoveredAddress][nonce >> 128]++;
|
827
|
+
|
828
|
+
LSP25MultiChannelNonce._verifyValidityTimestamps(validityTimestamp);
|
829
|
+
|
830
|
+
if (votedAddress != recoveredAddress)
|
831
|
+
revert SignerIsNotVotedAddress(votedAddress, recoveredAddress);
|
832
|
+
}
|
833
|
+
|
834
|
+
/**
|
835
|
+
* @dev Internal function to recover the signer from a LSP11 signature.
|
836
|
+
* @param signature The signature to recover from.
|
837
|
+
* @param nonce The nonce for the signature.
|
838
|
+
* @param validityTimestamps The validity timestamps for the signature.
|
839
|
+
* @param msgValue The message value.
|
840
|
+
* @param recoveryCounter The recovery counter.
|
841
|
+
* @param callData The call data.
|
842
|
+
* @return The address of the signer.
|
843
|
+
*/
|
844
|
+
function _recoverSignerFromLSP11Signature(
|
845
|
+
bytes memory signature,
|
846
|
+
uint256 nonce,
|
847
|
+
uint256 validityTimestamps,
|
848
|
+
uint256 msgValue,
|
849
|
+
uint256 recoveryCounter,
|
850
|
+
bytes memory callData
|
851
|
+
) internal view returns (address) {
|
852
|
+
bytes memory lsp25EncodedMessage = abi.encodePacked(
|
853
|
+
LSP11_VERSION,
|
854
|
+
block.chainid,
|
855
|
+
nonce,
|
856
|
+
validityTimestamps,
|
857
|
+
msgValue,
|
858
|
+
recoveryCounter,
|
859
|
+
callData
|
860
|
+
);
|
861
|
+
|
862
|
+
bytes32 eip191Hash = address(this).toDataWithIntendedValidatorHash(
|
863
|
+
lsp25EncodedMessage
|
864
|
+
);
|
865
|
+
|
866
|
+
return eip191Hash.recover(signature);
|
867
|
+
}
|
868
|
+
|
869
|
+
/**
|
870
|
+
* @dev Internal function to recover access to an account.
|
871
|
+
* @param account The account to recover.
|
872
|
+
* @param recoveryCounter The recovery counter.
|
873
|
+
* @param votedAddress The address voted by the guardian.
|
874
|
+
* @param secretHash The hash of the secret.
|
875
|
+
* @param newSecretHash The new secret hash for the account.
|
876
|
+
* @param msgValue The message value.
|
877
|
+
* @param calldataToExecute The call data to be executed as part of the recovery.
|
878
|
+
*/
|
879
|
+
function _recoverAccess(
|
880
|
+
address account,
|
881
|
+
uint256 recoveryCounter,
|
882
|
+
address votedAddress,
|
883
|
+
bytes32 secretHash,
|
884
|
+
bytes32 newSecretHash,
|
885
|
+
uint256 msgValue,
|
886
|
+
bytes memory calldataToExecute
|
887
|
+
) internal returns (bytes memory) {
|
888
|
+
if (
|
889
|
+
// solhint-disable not-rely-on-time
|
890
|
+
block.timestamp <
|
891
|
+
_firstRecoveryTimestamp[account][recoveryCounter] +
|
892
|
+
getRecoveryDelayOf(account)
|
893
|
+
) revert CannotRecoverBeforeDelay(account, getRecoveryDelayOf(account));
|
894
|
+
|
895
|
+
// retrieve current secret hash
|
896
|
+
bytes32 currentSecretHash = _secretHashOf[account];
|
897
|
+
|
898
|
+
// retrieve current guardians threshold
|
899
|
+
uint256 guardiansThresholdOfAccount = _guardiansThresholdOf[account];
|
900
|
+
|
901
|
+
// if there is no guardians, disallow recovering
|
902
|
+
if (guardiansThresholdOfAccount == 0) revert AccountNotSetupYet();
|
903
|
+
|
904
|
+
// use block scope to discard local variable after check and prevent stack too deep
|
905
|
+
{
|
906
|
+
// retrieve number of votes caller have
|
907
|
+
uint256 votesOfGuardianVotedAddress_ = _votesOfguardianVotedAddress[
|
908
|
+
account
|
909
|
+
][recoveryCounter][votedAddress];
|
910
|
+
|
911
|
+
// if the threshold is 0, and the caller does not have votes will rely on the hash
|
912
|
+
if (votesOfGuardianVotedAddress_ < guardiansThresholdOfAccount)
|
913
|
+
revert CallerVotesHaveNotReachedThreshold(
|
914
|
+
account,
|
915
|
+
votedAddress
|
916
|
+
);
|
917
|
+
}
|
918
|
+
|
919
|
+
// if there is a secret require a commitment first
|
920
|
+
if (currentSecretHash != bytes32(0)) {
|
921
|
+
bytes32 saltedHash = keccak256(abi.encode(account, secretHash));
|
922
|
+
bytes32 commitment = keccak256(
|
923
|
+
abi.encode(votedAddress, saltedHash)
|
924
|
+
);
|
925
|
+
|
926
|
+
// Check that the commitment is valid
|
927
|
+
if (
|
928
|
+
commitment !=
|
929
|
+
_commitmentInfoOf[account][recoveryCounter][votedAddress]
|
930
|
+
.commitment
|
931
|
+
) revert InvalidCommitment(account, votedAddress);
|
932
|
+
|
933
|
+
// Check that the commitment is not too early
|
934
|
+
// solhint-disable not-rely-on-time
|
935
|
+
if (
|
936
|
+
_commitmentInfoOf[account][recoveryCounter][votedAddress]
|
937
|
+
.timestamp +
|
938
|
+
60 >
|
939
|
+
block.timestamp
|
940
|
+
) revert CannotRecoverAfterDirectCommit(account, votedAddress);
|
941
|
+
|
942
|
+
// Check that the secret hash is valid
|
943
|
+
if (saltedHash != currentSecretHash)
|
944
|
+
revert InvalidSecretHash(account, secretHash);
|
945
|
+
}
|
946
|
+
|
947
|
+
_recoveryCounterOf[account]++;
|
948
|
+
_secretHashOf[account] = newSecretHash;
|
949
|
+
emit SecretHashChanged(account, newSecretHash);
|
950
|
+
|
951
|
+
emit RecoveryProcessSuccessful(
|
952
|
+
account,
|
953
|
+
recoveryCounter,
|
954
|
+
votedAddress,
|
955
|
+
calldataToExecute
|
956
|
+
);
|
957
|
+
|
958
|
+
(bool success, bytes memory returnedData) = account.call{
|
959
|
+
value: msgValue
|
960
|
+
}(calldataToExecute);
|
961
|
+
|
962
|
+
Address.verifyCallResult(
|
963
|
+
success,
|
964
|
+
returnedData,
|
965
|
+
"LSP11: Failed to call function on account"
|
966
|
+
);
|
967
|
+
|
968
|
+
return returnedData;
|
969
|
+
}
|
970
|
+
}
|