Skip to content

Commit 943d665

Browse files
committed
chore: improve contract visibility & overridability, added SocialRecoveryExecutorMock
1 parent 6f7f60f commit 943d665

File tree

7 files changed

+147
-169
lines changed

7 files changed

+147
-169
lines changed

contracts/account/extensions/ERC7579Modules/SocialRecoveryExecutor.sol renamed to contracts/account/extensions/ERC7579Modules/ERC7579SocialRecoveryExecutor.sol

Lines changed: 73 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

contracts/mocks/account/modules/ERC7579ReconfigurableValidatorMock.sol

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {ERC7579SocialRecoveryExecutor} from "../../../account/extensions/ERC7579Modules/ERC7579SocialRecoveryExecutor.sol";
5+
6+
contract ERC7579SocialRecoveryExecutorMock is ERC7579SocialRecoveryExecutor {
7+
constructor(string memory name, string memory version) ERC7579SocialRecoveryExecutor(name, version) {}
8+
9+
// helper for testing signature validation
10+
function validateGuardianSignatures(
11+
address account,
12+
GuardianSignature[] calldata guardianSignatures,
13+
bytes32 digest
14+
) public view virtual {
15+
super._validateGuardianSignatures(account, guardianSignatures, digest);
16+
}
17+
}

contracts/mocks/account/modules/ERC7579ValidatorMock.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ abstract contract ERC7579ValidatorMock is ERC7579ModuleMock(MODULE_TYPE_VALIDATO
2222
super.onUninstall(data);
2323
}
2424

25+
function updateSigner(address newSigner) public virtual {
26+
_associatedSigners[msg.sender] = newSigner;
27+
}
28+
29+
function getSigner(address sender) public view virtual returns (address) {
30+
return _associatedSigners[sender];
31+
}
32+
2533
function validateUserOp(
2634
PackedUserOperation calldata userOp,
2735
bytes32 userOpHash

0 commit comments

Comments
 (0)