Skip to content

Commit f4c67c1

Browse files
Add Account Recovery module (#150)
* Added drafts of Account Recovery module * Use EnumerableSet for recovery providers * Removed zero address check for the newOwner * Added reference to EIP-7947 * Updated slot constant * Set functions to be virtual * Updated README * Added tests * small fixes * renamed the module to "account-abstraction" * Added comments for AAccountRecovery * Fixed tests * some fixes * update version & deps --------- Co-authored-by: Artem Chystiakov <artem.ch31@gmail.com>
1 parent 95969bf commit f4c67c1

File tree

9 files changed

+1307
-227
lines changed

9 files changed

+1307
-227
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ contracts
2020
│ ├── ARBAC — "A powerful implementation of a true RBAC"
2121
│ └── extensions
2222
│ └── ARBACGroupable — "Groupable extension of ARBAC"
23+
├── account-abstraction
24+
│ └── AAccountRecovery — "ERC-7947 account recovery base implementation"
2325
├── contracts—registry
2426
│ ├── AContractsRegistry — "Reference registry implementation of ERC-6224 pattern"
2527
│ ├── ADependant — "Reference dependant implementation of ERC-6224 pattern"
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
5+
6+
import {IAccountRecovery} from "../interfaces/account-abstraction/IAccountRecovery.sol";
7+
import {IRecoveryProvider} from "../interfaces/account-abstraction/IRecoveryProvider.sol";
8+
9+
/**
10+
* @notice The Account Recovery module
11+
*
12+
* Contract module which provides a basic account recovery mechanism as specified in EIP-7947.
13+
* You may use this module as a base contract for your own account recovery mechanism.
14+
*
15+
* The Account Recovery module allows to add recovery providers to the account.
16+
* The recovery providers are used to recover the account ownership.
17+
*
18+
* For more information please refer to [EIP-7947](https://eips.ethereum.org/EIPS/eip-7947).
19+
*/
20+
abstract contract AAccountRecovery is IAccountRecovery {
21+
using EnumerableSet for EnumerableSet.AddressSet;
22+
23+
struct AAccountRecoveryStorage {
24+
EnumerableSet.AddressSet recoveryProviders;
25+
}
26+
27+
// bytes32(uint256(keccak256("solarity.contract.AAccountRecovery")) - 1)
28+
bytes32 private constant A_ACCOUNT_RECOVERY_STORAGE =
29+
0x828c412330620ced6ea61864e26a29daa3e4c6ed06ccbde7b849e007ed9dd85a;
30+
31+
error ZeroAddress();
32+
error ProviderAlreadyAdded(address provider);
33+
error ProviderNotRegistered(address provider);
34+
35+
/**
36+
* @inheritdoc IAccountRecovery
37+
*/
38+
function addRecoveryProvider(address provider_, bytes memory recoveryData_) external virtual;
39+
40+
/**
41+
* @inheritdoc IAccountRecovery
42+
*/
43+
function removeRecoveryProvider(address provider_) external virtual;
44+
45+
/**
46+
* @inheritdoc IAccountRecovery
47+
*/
48+
function recoverOwnership(
49+
address newOwner,
50+
address provider,
51+
bytes memory proof
52+
) external virtual returns (bool);
53+
54+
/**
55+
* @inheritdoc IAccountRecovery
56+
*/
57+
function recoveryProviderAdded(address provider_) public view virtual returns (bool) {
58+
AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();
59+
60+
return $.recoveryProviders.contains(provider_);
61+
}
62+
63+
/**
64+
* @notice A function to get the list of all the recovery providers added to the account
65+
* @return the list of recovery providers
66+
*/
67+
function getRecoveryProviders() public view virtual returns (address[] memory) {
68+
AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();
69+
70+
return $.recoveryProviders.values();
71+
}
72+
73+
/**
74+
* @notice Should be called in the `addRecoveryProvider` function
75+
*/
76+
function _addRecoveryProvider(address provider_, bytes memory recoveryData_) internal virtual {
77+
if (provider_ == address(0)) revert ZeroAddress();
78+
79+
AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();
80+
81+
if (!$.recoveryProviders.add(provider_)) revert ProviderAlreadyAdded(provider_);
82+
83+
IRecoveryProvider(provider_).subscribe(recoveryData_);
84+
85+
emit RecoveryProviderAdded(provider_);
86+
}
87+
88+
/**
89+
* @notice Should be called in the `removeRecoveryProvider` function
90+
*/
91+
function _removeRecoveryProvider(address provider_) internal virtual {
92+
AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();
93+
94+
if (!$.recoveryProviders.remove(provider_)) revert ProviderNotRegistered(provider_);
95+
96+
IRecoveryProvider(provider_).unsubscribe();
97+
98+
emit RecoveryProviderRemoved(provider_);
99+
}
100+
101+
/**
102+
* @notice Should be called in the `recoverOwnership` function before updating the account owner
103+
*/
104+
function _validateRecovery(
105+
address newOwner_,
106+
address provider_,
107+
bytes memory proof_
108+
) internal virtual {
109+
AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();
110+
111+
if (!$.recoveryProviders.contains(provider_)) revert ProviderNotRegistered(provider_);
112+
113+
IRecoveryProvider(provider_).recover(newOwner_, proof_);
114+
}
115+
116+
/**
117+
* @dev Returns a pointer to the storage namespace
118+
*/
119+
function _getAAccountRecoveryStorage()
120+
private
121+
pure
122+
returns (AAccountRecoveryStorage storage $)
123+
{
124+
assembly {
125+
$.slot := A_ACCOUNT_RECOVERY_STORAGE
126+
}
127+
}
128+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
/**
5+
* @notice The Account Recovery module
6+
*
7+
* Defines a common account recovery interface for smart accounts to implement.
8+
*
9+
* For more information please refer to [EIP-7947](https://eips.ethereum.org/EIPS/eip-7947).
10+
*/
11+
interface IAccountRecovery {
12+
event OwnershipRecovered(address indexed oldOwner, address indexed newOwner);
13+
event RecoveryProviderAdded(address indexed provider);
14+
event RecoveryProviderRemoved(address indexed provider);
15+
16+
/**
17+
* @notice A function to add a new recovery provider.
18+
* SHOULD be access controlled.
19+
*
20+
* @param provider the address of a recovery provider (ZKP verifier) to add.
21+
* @param recoveryData custom data (commitment) for the recovery provider.
22+
*/
23+
function addRecoveryProvider(address provider, bytes memory recoveryData) external;
24+
25+
/**
26+
* @notice A function to remove an existing recovery provider.
27+
* SHOULD be access controlled.
28+
*
29+
* @param provider the address of a previously added recovery provider to remove.
30+
*/
31+
function removeRecoveryProvider(address provider) external;
32+
33+
/**
34+
* @notice A non-view function to recover ownership of a smart account.
35+
* @param newOwner the address of a new owner.
36+
* @param provider the address of a recovery provider.
37+
* @param proof an encoded proof of recovery (ZKP/ZKAI, signature, etc).
38+
* @return `true` if recovery is successful, `false` (or revert) otherwise.
39+
*/
40+
function recoverOwnership(
41+
address newOwner,
42+
address provider,
43+
bytes memory proof
44+
) external returns (bool);
45+
46+
/**
47+
* @notice A view function to check if a provider has been previously added.
48+
* @param provider the provider to check.
49+
* @return true if the provider exists in the account, false otherwise.
50+
*/
51+
function recoveryProviderAdded(address provider) external view returns (bool);
52+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
/**
5+
* @notice The Account Recovery module
6+
*
7+
* Defines a common recovery provider interface.
8+
*
9+
* For more information please refer to [EIP-7947](https://eips.ethereum.org/EIPS/eip-7947).
10+
*/
11+
interface IRecoveryProvider {
12+
event AccountSubscribed(address indexed account);
13+
event AccountUnsubscribed(address indexed account);
14+
15+
/**
16+
* @notice A function that "subscribes" a smart account (msg.sender) to a recovery provider.
17+
* SHOULD process and assign the `recoveryData` to the `msg.sender`.
18+
*
19+
* @param recoveryData a recovery commitment (hash/ZKP public output) to be used
20+
* in the `recover` function to check a recovery proof validity.
21+
*/
22+
function subscribe(bytes memory recoveryData) external;
23+
24+
/**
25+
* @notice A function that revokes a smart account subscription.
26+
*/
27+
function unsubscribe() external;
28+
29+
/**
30+
* @notice A function that checks if a recovery of a smart account (msg.sender)
31+
* to the `newOwner` is possible.
32+
* SHOULD use `msg.sender`'s `recoveryData` to check the `proof` validity.
33+
*
34+
* @param newOwner the new owner to recover the `msg.sender` ownership to.
35+
* @param proof the recovery proof.
36+
*/
37+
function recover(address newOwner, bytes memory proof) external;
38+
39+
/**
40+
* @notice A function to get a recovery data (commitment) of an account.
41+
*
42+
* @param account the account to get the recovery data of.
43+
* @return the associated recovery data.
44+
*/
45+
function getRecoveryData(address account) external view returns (bytes memory);
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
import {AAccountRecovery} from "../../account-abstraction/AAccountRecovery.sol";
5+
6+
contract AccountRecoveryMock is AAccountRecovery {
7+
function addRecoveryProvider(address provider_, bytes memory recoveryData_) external override {
8+
_addRecoveryProvider(provider_, recoveryData_);
9+
}
10+
11+
function removeRecoveryProvider(address provider_) external override {
12+
_removeRecoveryProvider(provider_);
13+
}
14+
15+
function validateRecovery(address newOwner_, address provider_, bytes memory proof_) external {
16+
_validateRecovery(newOwner_, provider_, proof_);
17+
}
18+
19+
function recoverOwnership(
20+
address,
21+
address,
22+
bytes memory
23+
) external pure override returns (bool) {}
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.21;
3+
4+
contract RecoveryProviderMock {
5+
event SubscribeCalled(bytes recoveryData_);
6+
event UnsubscribeCalled();
7+
event RecoverCalled(address newOwner, bytes proof);
8+
9+
function subscribe(bytes memory recoveryData_) external {
10+
emit SubscribeCalled(recoveryData_);
11+
}
12+
13+
function unsubscribe() external {
14+
emit UnsubscribeCalled();
15+
}
16+
17+
function recover(address newOwner_, bytes memory proof_) external {
18+
emit RecoverCalled(newOwner_, proof_);
19+
}
20+
}

0 commit comments

Comments
 (0)