@@ -23,7 +23,7 @@ import {AccountERC7579} from "../AccountERC7579.sol";
23
23
* - Configure guardian designations and thresholds
24
24
* - Protect against unauthorized recovery attempts
25
25
*/
26
- contract SocialRecoveryExecutor is IERC7579Module , EIP712 , Nonces {
26
+ contract ERC7579SocialRecoveryExecutor is IERC7579Module , EIP712 , Nonces {
27
27
using EnumerableSet for EnumerableSet.AddressSet;
28
28
29
29
enum RecoveryStatus {
@@ -123,40 +123,42 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
123
123
124
124
constructor (string memory name , string memory version ) EIP712 (name, version) {}
125
125
126
+ /// @notice Checks if the module is of a certain type
127
+ /// @param moduleTypeId The module type ID to check
128
+ /// @return true if the module is of the given type, false otherwise
129
+ function isModuleType (uint256 moduleTypeId ) external pure returns (bool ) {
130
+ return moduleTypeId == MODULE_TYPE_EXECUTOR;
131
+ }
132
+
126
133
/// @notice Initializes the module with initial recovery configuration
127
134
/// @dev Called by the ERC-7579 Account during module installation
128
135
/// @param data initData ABI encoded (address[] guardians, uint256 threshold, uint256 timelock)
129
- function onInstall (bytes calldata data ) external virtual override {
136
+ function onInstall (bytes calldata data ) public virtual override {
130
137
address account = msg .sender ;
131
138
132
139
(address [] memory _guardians , uint256 _threshold , uint256 _timelock ) = abi.decode (
133
140
data,
134
141
(address [], uint256 , uint256 )
135
142
);
143
+
136
144
if (_guardians.length == 0 ) {
137
145
revert InvalidGuardians ();
138
146
}
139
- if (_threshold == 0 || _threshold > _guardians.length ) {
140
- revert InvalidThreshold ();
141
- }
142
- if (_timelock == 0 ) {
143
- revert InvalidTimelock ();
144
- }
145
147
146
148
for (uint256 i = 0 ; i < _guardians.length ; i++ ) {
147
149
_addGuardian (account, _guardians[i]);
148
150
}
149
151
150
- _recoveryConfigs[ account].threshold = _threshold;
151
- _recoveryConfigs[ account].timelock = _timelock;
152
+ _setThreshold ( account, _threshold) ;
153
+ _setTimelock ( account, _timelock) ;
152
154
153
155
emit ModuleInstalledReceived (account, data);
154
156
}
155
157
156
158
/// @notice Uninstalls the module, clearing all recovery configuration
157
159
/// @dev Called by the ERC-7579 Account during module uninstallation
158
160
/// @param data Additional data
159
- function onUninstall (bytes calldata data ) external virtual override {
161
+ function onUninstall (bytes calldata data ) public virtual override {
160
162
address account = msg .sender ;
161
163
162
164
// clear the guardian EnumerableSet.
@@ -182,7 +184,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
182
184
address account ,
183
185
GuardianSignature[] calldata guardianSignatures ,
184
186
bytes calldata executionCalldata
185
- ) external virtual whenRecoveryIsNotStarted (account) {
187
+ ) public virtual whenRecoveryIsNotStarted (account) {
186
188
bytes32 digest = _getStartRecoveryDigest (account, executionCalldata);
187
189
_validateGuardianSignatures (account, guardianSignatures, digest);
188
190
_useNonce (account);
@@ -202,7 +204,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
202
204
function executeRecovery (
203
205
address account ,
204
206
bytes calldata executionCalldata
205
- ) external virtual whenRecoveryIsReady (account) {
207
+ ) public virtual whenRecoveryIsReady (account) {
206
208
if (keccak256 (executionCalldata) != _recoveryConfigs[account].pendingExecutionHash) {
207
209
revert ExecutionDiffersFromPending ();
208
210
}
@@ -221,7 +223,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
221
223
/// @notice Cancels an ongoing recovery process
222
224
/// @dev Can only be called by the account itself
223
225
/// @custom:security Direct account control takes precedence over recovery process
224
- function cancelRecovery () external virtual whenRecoveryIsStartedOrReady (msg .sender ) {
226
+ function cancelRecovery () public virtual whenRecoveryIsStartedOrReady (msg .sender ) {
225
227
_cancelRecovery (msg .sender );
226
228
}
227
229
@@ -233,57 +235,44 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
233
235
function cancelRecovery (
234
236
address account ,
235
237
GuardianSignature[] calldata guardianSignatures
236
- ) external virtual whenRecoveryIsStartedOrReady (account) {
238
+ ) public virtual whenRecoveryIsStartedOrReady (account) {
237
239
bytes32 digest = _getCancelRecoveryDigest (account, nonces (account));
238
240
_validateGuardianSignatures (account, guardianSignatures, digest);
239
241
_useNonce (account);
240
242
241
243
_cancelRecovery (account);
242
244
}
243
245
244
- /// @notice Validates guardian signatures for a given digest
245
- /// @dev Helper function for clients to validate signatures
246
- /// @param account The ERC-7579 Account to validate signatures for
247
- /// @param guardianSignatures Array of guardian signatures to validate
248
- /// @param digest The digest to validate the signatures against
249
- function validateGuardianSignatures (
250
- address account ,
251
- GuardianSignature[] calldata guardianSignatures ,
252
- bytes32 digest
253
- ) external view {
254
- _validateGuardianSignatures (account, guardianSignatures, digest);
255
- }
256
-
257
246
/*//////////////////////////////////////////////////////////////////////////
258
247
MODULE MANAGEMENT
259
248
//////////////////////////////////////////////////////////////////////////*/
260
249
261
250
/// @notice Adds a new guardian to the account's recovery configuration
262
251
/// @dev Only callable by the account itself
263
252
/// @param guardian Address of the new guardian
264
- function addGuardian (address guardian ) external {
253
+ function addGuardian (address guardian ) public virtual {
265
254
_addGuardian (msg .sender , guardian);
266
255
}
267
256
268
257
/// @notice Removes a guardian from the account's recovery configuration
269
258
/// @dev Only callable by the account itself
270
259
/// @param guardian Address of the guardian to remove
271
- function removeGuardian (address guardian ) external {
260
+ function removeGuardian (address guardian ) public virtual {
272
261
_removeGuardian (msg .sender , guardian);
273
262
}
274
263
275
264
/// @notice Changes the number of required guardian signatures
276
265
/// @dev Only callable by the account itself
277
266
/// @param threshold New threshold value
278
- function changeThreshold (uint256 threshold ) external {
279
- _changeThreshold (msg .sender , threshold);
267
+ function updateThreshold (uint256 threshold ) public virtual {
268
+ _setThreshold (msg .sender , threshold);
280
269
}
281
270
282
271
/// @notice Changes the timelock duration for recovery
283
272
/// @dev Only callable by the account itself
284
273
/// @param timelock New timelock duration in seconds
285
- function changeTimelock (uint256 timelock ) external {
286
- _changeTimelock (msg .sender , timelock);
274
+ function updateTimelock (uint256 timelock ) public virtual {
275
+ _setTimelock (msg .sender , timelock);
287
276
}
288
277
289
278
/*//////////////////////////////////////////////////////////////////////////
@@ -304,22 +293,37 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
304
293
return RecoveryStatus.Ready;
305
294
}
306
295
307
- function isGuardian (address account , address guardian ) public view returns (bool ) {
296
+ /// @notice Checks if an address is a guardian for an ERC-7579 Account
297
+ /// @param account The ERC-7579 Account to check guardians for
298
+ /// @param guardian The address to check as a guardian
299
+ /// @return true if the address is a guardian, false otherwise
300
+ function isGuardian (address account , address guardian ) public view virtual returns (bool ) {
308
301
return _recoveryConfigs[account].guardians.contains (guardian);
309
302
}
310
303
311
- function getGuardians (address account ) public view returns (address [] memory ) {
304
+ /// @notice Gets all guardians for an ERC-7579 Account
305
+ /// @param account The ERC-7579 Account to get guardians for
306
+ /// @return An array of all guardians
307
+ function getGuardians (address account ) public view virtual returns (address [] memory ) {
312
308
return _recoveryConfigs[account].guardians.values ();
313
309
}
314
310
315
- function getThreshold (address account ) public view returns (uint256 ) {
311
+ /// @notice Gets the threshold for an ERC-7579 Account
312
+ /// @param account The ERC-7579 Account to get the threshold for
313
+ /// @return The threshold value
314
+ function getThreshold (address account ) public view virtual returns (uint256 ) {
316
315
return _recoveryConfigs[account].threshold;
317
316
}
318
317
319
- function getTimelock (address account ) public view returns (uint256 ) {
318
+ /// @notice Gets the timelock for an ERC-7579 Account
319
+ /// @param account The ERC-7579 Account to get the timelock for
320
+ /// @return The timelock value
321
+ function getTimelock (address account ) public view virtual returns (uint256 ) {
320
322
return _recoveryConfigs[account].timelock;
321
323
}
322
324
325
+ /// @notice Gets the maximum number of guardians for an ERC-7579 Account
326
+ /// @return The maximum number of guardians
323
327
function maxGuardians () public pure virtual returns (uint256 ) {
324
328
return 32 ;
325
329
}
@@ -328,6 +332,29 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
328
332
INTERNAL FUNCTIONS
329
333
//////////////////////////////////////////////////////////////////////////*/
330
334
335
+ /// @notice EIP-712 digest for starting recovery
336
+ /// @param account The ERC-7579 Account to start recovery for
337
+ /// @param executionCalldata The calldata to execute during recovery
338
+ /// @return The EIP-712 digest for starting recovery
339
+ function _getStartRecoveryDigest (
340
+ address account ,
341
+ bytes calldata executionCalldata
342
+ ) internal view returns (bytes32 ) {
343
+ bytes32 structHash = keccak256 (
344
+ abi.encode (START_RECOVERY_TYPEHASH, account, keccak256 (executionCalldata), nonces (account))
345
+ );
346
+ return _hashTypedDataV4 (structHash);
347
+ }
348
+
349
+ /// @notice EIP-712 digest for cancelling recovery
350
+ /// @param account The ERC-7579 Account to cancel recovery for
351
+ /// @param nonce The nonce of the account
352
+ /// @return The EIP-712 digest for cancelling recovery
353
+ function _getCancelRecoveryDigest (address account , uint256 nonce ) internal view returns (bytes32 ) {
354
+ bytes32 structHash = keccak256 (abi.encode (CANCEL_RECOVERY_TYPEHASH, account, nonce));
355
+ return _hashTypedDataV4 (structHash);
356
+ }
357
+
331
358
/// @notice Verifies multiple guardian signatures for a given digest
332
359
/// @dev Ensures signatures are unique, and threshold is met
333
360
/// @param account The account the signatures are for
@@ -346,44 +373,22 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
346
373
revert ThresholdNotMet ();
347
374
}
348
375
376
+ address lastSigner = address (0 );
349
377
for (uint256 i = 0 ; i < guardianSignatures.length ; i++ ) {
378
+ address signer = guardianSignatures[i].signer;
350
379
if (
351
- ! isGuardian (account, guardianSignatures[i].signer) ||
352
- ! SignatureChecker.isValidSignatureNow (
353
- guardianSignatures[i].signer,
354
- digest,
355
- guardianSignatures[i].signature
356
- )
380
+ ! isGuardian (account, signer) ||
381
+ ! SignatureChecker.isValidSignatureNow (signer, digest, guardianSignatures[i].signature)
357
382
) {
358
383
revert InvalidGuardianSignature ();
359
384
}
360
- // ensures signers are unique in O(n), requires sorted signatures by signer address in ascending order
361
- if (i > 0 && uint160 (guardianSignatures[i].signer) <= uint160 (guardianSignatures[i - 1 ].signer)) {
385
+ if (uint160 (signer) <= uint160 (lastSigner)) {
362
386
revert DuplicatedOrUnsortedGuardianSignatures ();
363
387
}
388
+ lastSigner = signer;
364
389
}
365
390
}
366
391
367
- /// @notice EIP-712 digest for starting recovery
368
- /// @param account The ERC-7579 Account to start recovery for
369
- /// @param executionCalldata The calldata to execute during recovery
370
- /// @return The EIP-712 digest for starting recovery
371
- function _getStartRecoveryDigest (address account , bytes memory executionCalldata ) internal view returns (bytes32 ) {
372
- bytes32 structHash = keccak256 (
373
- abi.encode (START_RECOVERY_TYPEHASH, account, keccak256 (executionCalldata), nonces (account))
374
- );
375
- return _hashTypedDataV4 (structHash);
376
- }
377
-
378
- /// @notice EIP-712 digest for cancelling recovery
379
- /// @param account The ERC-7579 Account to cancel recovery for
380
- /// @param nonce The nonce of the account
381
- /// @return The EIP-712 digest for cancelling recovery
382
- function _getCancelRecoveryDigest (address account , uint256 nonce ) internal view returns (bytes32 ) {
383
- bytes32 structHash = keccak256 (abi.encode (CANCEL_RECOVERY_TYPEHASH, account, nonce));
384
- return _hashTypedDataV4 (structHash);
385
- }
386
-
387
392
/// @notice Cancels an ongoing recovery process
388
393
/// @param account The ERC-7579 Account to cancel recovery for
389
394
function _cancelRecovery (address account ) internal virtual {
@@ -426,7 +431,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
426
431
/// @dev Cannot be set to zero and cannot be greater than the current number of guardians
427
432
/// @param account The ERC-7579 Account to change the threshold for
428
433
/// @param threshold New threshold value
429
- function _changeThreshold (address account , uint256 threshold ) internal virtual {
434
+ function _setThreshold (address account , uint256 threshold ) internal virtual {
430
435
if (threshold == 0 || threshold > _recoveryConfigs[account].guardians.length ()) {
431
436
revert InvalidThreshold ();
432
437
}
@@ -438,22 +443,11 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
438
443
/// @dev Cannot be set to zero
439
444
/// @param account The ERC-7579 Account to change the timelock for
440
445
/// @param timelock New timelock duration in seconds
441
- function _changeTimelock (address account , uint256 timelock ) internal virtual {
446
+ function _setTimelock (address account , uint256 timelock ) internal virtual {
442
447
if (timelock == 0 ) {
443
448
revert InvalidTimelock ();
444
449
}
445
450
_recoveryConfigs[account].timelock = timelock;
446
451
emit TimelockChanged (account, timelock);
447
452
}
448
-
449
- /*//////////////////////////////////////////////////////////////////////////
450
- MODULE METADATA
451
- //////////////////////////////////////////////////////////////////////////*/
452
-
453
- /// @notice Checks if the module is of a certain type
454
- /// @param moduleTypeId The module type ID to check
455
- /// @return true if the module is of the given type, false otherwise
456
- function isModuleType (uint256 moduleTypeId ) external pure override returns (bool ) {
457
- return moduleTypeId == MODULE_TYPE_EXECUTOR;
458
- }
459
453
}
0 commit comments