@@ -23,7 +23,7 @@ import {AccountERC7579} from "../AccountERC7579.sol";
2323 * - Configure guardian designations and thresholds
2424 * - Protect against unauthorized recovery attempts
2525 */
26- contract SocialRecoveryExecutor is IERC7579Module , EIP712 , Nonces {
26+ contract ERC7579SocialRecoveryExecutor is IERC7579Module , EIP712 , Nonces {
2727 using EnumerableSet for EnumerableSet.AddressSet;
2828
2929 enum RecoveryStatus {
@@ -123,40 +123,42 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
123123
124124 constructor (string memory name , string memory version ) EIP712 (name, version) {}
125125
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+
126133 /// @notice Initializes the module with initial recovery configuration
127134 /// @dev Called by the ERC-7579 Account during module installation
128135 /// @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 {
130137 address account = msg .sender ;
131138
132139 (address [] memory _guardians , uint256 _threshold , uint256 _timelock ) = abi.decode (
133140 data,
134141 (address [], uint256 , uint256 )
135142 );
143+
136144 if (_guardians.length == 0 ) {
137145 revert InvalidGuardians ();
138146 }
139- if (_threshold == 0 || _threshold > _guardians.length ) {
140- revert InvalidThreshold ();
141- }
142- if (_timelock == 0 ) {
143- revert InvalidTimelock ();
144- }
145147
146148 for (uint256 i = 0 ; i < _guardians.length ; i++ ) {
147149 _addGuardian (account, _guardians[i]);
148150 }
149151
150- _recoveryConfigs[ account].threshold = _threshold;
151- _recoveryConfigs[ account].timelock = _timelock;
152+ _setThreshold ( account, _threshold) ;
153+ _setTimelock ( account, _timelock) ;
152154
153155 emit ModuleInstalledReceived (account, data);
154156 }
155157
156158 /// @notice Uninstalls the module, clearing all recovery configuration
157159 /// @dev Called by the ERC-7579 Account during module uninstallation
158160 /// @param data Additional data
159- function onUninstall (bytes calldata data ) external virtual override {
161+ function onUninstall (bytes calldata data ) public virtual override {
160162 address account = msg .sender ;
161163
162164 // clear the guardian EnumerableSet.
@@ -182,7 +184,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
182184 address account ,
183185 GuardianSignature[] calldata guardianSignatures ,
184186 bytes calldata executionCalldata
185- ) external virtual whenRecoveryIsNotStarted (account) {
187+ ) public virtual whenRecoveryIsNotStarted (account) {
186188 bytes32 digest = _getStartRecoveryDigest (account, executionCalldata);
187189 _validateGuardianSignatures (account, guardianSignatures, digest);
188190 _useNonce (account);
@@ -202,7 +204,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
202204 function executeRecovery (
203205 address account ,
204206 bytes calldata executionCalldata
205- ) external virtual whenRecoveryIsReady (account) {
207+ ) public virtual whenRecoveryIsReady (account) {
206208 if (keccak256 (executionCalldata) != _recoveryConfigs[account].pendingExecutionHash) {
207209 revert ExecutionDiffersFromPending ();
208210 }
@@ -221,7 +223,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
221223 /// @notice Cancels an ongoing recovery process
222224 /// @dev Can only be called by the account itself
223225 /// @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 ) {
225227 _cancelRecovery (msg .sender );
226228 }
227229
@@ -233,57 +235,44 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
233235 function cancelRecovery (
234236 address account ,
235237 GuardianSignature[] calldata guardianSignatures
236- ) external virtual whenRecoveryIsStartedOrReady (account) {
238+ ) public virtual whenRecoveryIsStartedOrReady (account) {
237239 bytes32 digest = _getCancelRecoveryDigest (account, nonces (account));
238240 _validateGuardianSignatures (account, guardianSignatures, digest);
239241 _useNonce (account);
240242
241243 _cancelRecovery (account);
242244 }
243245
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-
257246 /*//////////////////////////////////////////////////////////////////////////
258247 MODULE MANAGEMENT
259248 //////////////////////////////////////////////////////////////////////////*/
260249
261250 /// @notice Adds a new guardian to the account's recovery configuration
262251 /// @dev Only callable by the account itself
263252 /// @param guardian Address of the new guardian
264- function addGuardian (address guardian ) external {
253+ function addGuardian (address guardian ) public virtual {
265254 _addGuardian (msg .sender , guardian);
266255 }
267256
268257 /// @notice Removes a guardian from the account's recovery configuration
269258 /// @dev Only callable by the account itself
270259 /// @param guardian Address of the guardian to remove
271- function removeGuardian (address guardian ) external {
260+ function removeGuardian (address guardian ) public virtual {
272261 _removeGuardian (msg .sender , guardian);
273262 }
274263
275264 /// @notice Changes the number of required guardian signatures
276265 /// @dev Only callable by the account itself
277266 /// @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);
280269 }
281270
282271 /// @notice Changes the timelock duration for recovery
283272 /// @dev Only callable by the account itself
284273 /// @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);
287276 }
288277
289278 /*//////////////////////////////////////////////////////////////////////////
@@ -304,22 +293,37 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
304293 return RecoveryStatus.Ready;
305294 }
306295
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 ) {
308301 return _recoveryConfigs[account].guardians.contains (guardian);
309302 }
310303
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 ) {
312308 return _recoveryConfigs[account].guardians.values ();
313309 }
314310
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 ) {
316315 return _recoveryConfigs[account].threshold;
317316 }
318317
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 ) {
320322 return _recoveryConfigs[account].timelock;
321323 }
322324
325+ /// @notice Gets the maximum number of guardians for an ERC-7579 Account
326+ /// @return The maximum number of guardians
323327 function maxGuardians () public pure virtual returns (uint256 ) {
324328 return 32 ;
325329 }
@@ -328,6 +332,29 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
328332 INTERNAL FUNCTIONS
329333 //////////////////////////////////////////////////////////////////////////*/
330334
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+
331358 /// @notice Verifies multiple guardian signatures for a given digest
332359 /// @dev Ensures signatures are unique, and threshold is met
333360 /// @param account The account the signatures are for
@@ -346,44 +373,22 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
346373 revert ThresholdNotMet ();
347374 }
348375
376+ address lastSigner = address (0 );
349377 for (uint256 i = 0 ; i < guardianSignatures.length ; i++ ) {
378+ address signer = guardianSignatures[i].signer;
350379 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)
357382 ) {
358383 revert InvalidGuardianSignature ();
359384 }
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)) {
362386 revert DuplicatedOrUnsortedGuardianSignatures ();
363387 }
388+ lastSigner = signer;
364389 }
365390 }
366391
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-
387392 /// @notice Cancels an ongoing recovery process
388393 /// @param account The ERC-7579 Account to cancel recovery for
389394 function _cancelRecovery (address account ) internal virtual {
@@ -426,7 +431,7 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
426431 /// @dev Cannot be set to zero and cannot be greater than the current number of guardians
427432 /// @param account The ERC-7579 Account to change the threshold for
428433 /// @param threshold New threshold value
429- function _changeThreshold (address account , uint256 threshold ) internal virtual {
434+ function _setThreshold (address account , uint256 threshold ) internal virtual {
430435 if (threshold == 0 || threshold > _recoveryConfigs[account].guardians.length ()) {
431436 revert InvalidThreshold ();
432437 }
@@ -438,22 +443,11 @@ contract SocialRecoveryExecutor is IERC7579Module, EIP712, Nonces {
438443 /// @dev Cannot be set to zero
439444 /// @param account The ERC-7579 Account to change the timelock for
440445 /// @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 {
442447 if (timelock == 0 ) {
443448 revert InvalidTimelock ();
444449 }
445450 _recoveryConfigs[account].timelock = timelock;
446451 emit TimelockChanged (account, timelock);
447452 }
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- }
459453}
0 commit comments