Skip to content

Commit c26afb0

Browse files
authored
Add getter functions to account factory. (#367)
* Make all top level functions virtual on ContractKit bases * pkg update * Make MAX_BPS private in Marketplace plugins * Rename salt to accountId * Add method to fetch all wallets created by signer * Update account factory with getters * resolve lint error
1 parent 2caceed commit c26afb0

File tree

6 files changed

+366
-142
lines changed

6 files changed

+366
-142
lines changed

contracts/smart-wallet/TWAccount.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
1919
// Utils
2020
import "../openzeppelin-presets/utils/cryptography/ECDSA.sol";
2121

22+
// $$\ $$\ $$\ $$\ $$\
23+
// $$ | $$ | \__| $$ | $$ |
24+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
25+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
26+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
27+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
28+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
29+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
30+
31+
/*///////////////////////////////////////////////////////////////
32+
Storage layout
33+
//////////////////////////////////////////////////////////////*/
34+
2235
library TWAccountStorage {
2336
bytes32 internal constant TWACCOUNT_STORAGE_POSITION = keccak256("twaccount.storage");
2437

@@ -46,7 +59,7 @@ contract TWAccount is
4659
using ECDSA for bytes32;
4760

4861
/*///////////////////////////////////////////////////////////////
49-
State (constant, immutable)
62+
State
5063
//////////////////////////////////////////////////////////////*/
5164

5265
bytes32 public constant SIGNER_ROLE = keccak256("SIGNER_ROLE");

contracts/smart-wallet/TWAccountFactory.sol

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,131 @@ pragma solidity ^0.8.12;
44
// Utils
55
import "../extension/Multicall.sol";
66
import "@openzeppelin/contracts/proxy/Clones.sol";
7+
import "../lib/TWStringSet.sol";
78

89
// Interface
910
import "./interfaces/ITWAccountFactory.sol";
1011

1112
// Smart wallet implementation
1213
import "./TWAccount.sol";
1314

15+
// $$\ $$\ $$\ $$\ $$\
16+
// $$ | $$ | \__| $$ | $$ |
17+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
18+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
19+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
20+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
21+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
22+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
23+
24+
/*///////////////////////////////////////////////////////////////
25+
Storage layout
26+
//////////////////////////////////////////////////////////////*/
27+
28+
library TWAccountFactoryStorage {
29+
bytes32 internal constant TWACCOUNT_FACTORY_STORAGE_POSITION = keccak256("twaccount.factory.storage");
30+
31+
struct Data {
32+
TWStringSet.Set allAccounts;
33+
mapping(address => TWStringSet.Set) accountsOfSigner;
34+
}
35+
36+
function factoryStorage() internal pure returns (Data storage twaccountFactoryData) {
37+
bytes32 position = TWACCOUNT_FACTORY_STORAGE_POSITION;
38+
assembly {
39+
twaccountFactoryData.slot := position
40+
}
41+
}
42+
}
43+
1444
contract TWAccountFactory is ITWAccountFactory, Multicall {
45+
using TWStringSet for TWStringSet.Set;
46+
47+
/*///////////////////////////////////////////////////////////////
48+
State
49+
//////////////////////////////////////////////////////////////*/
50+
1551
TWAccount private immutable _accountImplementation;
1652

53+
/*///////////////////////////////////////////////////////////////
54+
Constructor
55+
//////////////////////////////////////////////////////////////*/
56+
1757
constructor(IEntryPoint _entrypoint) {
1858
_accountImplementation = new TWAccount(_entrypoint);
1959
}
2060

21-
/// @notice Returns the implementation of the Account.
22-
function accountImplementation() external view override returns (address) {
23-
return address(_accountImplementation);
24-
}
61+
/*///////////////////////////////////////////////////////////////
62+
External functions
63+
//////////////////////////////////////////////////////////////*/
2564

26-
/// @notice Deploys a new Account with the given admin and salt.
27-
function createAccount(address _admin, bytes32 _salt) external returns (address) {
65+
/// @notice Deploys a new Account with the given admin and accountId used as salt.
66+
function createAccount(address _admin, string memory _accountId) external returns (address) {
2867
address impl = address(_accountImplementation);
29-
address account = Clones.predictDeterministicAddress(impl, _salt);
68+
bytes32 salt = keccak256(abi.encode(_accountId));
69+
address account = Clones.predictDeterministicAddress(impl, salt);
3070

3171
if (account.code.length > 0) {
3272
return account;
3373
}
3474

35-
account = Clones.cloneDeterministic(impl, _salt);
75+
account = Clones.cloneDeterministic(impl, salt);
3676

3777
TWAccount(payable(account)).initialize(_admin);
3878

39-
emit AccountCreated(account, _admin, _salt);
79+
_setupAccount(_admin, _accountId);
80+
81+
emit AccountCreated(account, _admin, _accountId);
4082

4183
return account;
4284
}
4385

44-
/// @notice Returns the address of an Account that would be deployed with the given salt.
45-
function getAddress(bytes32 _salt) external view returns (address) {
46-
return Clones.predictDeterministicAddress(address(_accountImplementation), _salt);
86+
/*///////////////////////////////////////////////////////////////
87+
View functions
88+
//////////////////////////////////////////////////////////////*/
89+
90+
/// @notice Returns the implementation of the Account.
91+
function accountImplementation() external view override returns (address) {
92+
return address(_accountImplementation);
93+
}
94+
95+
/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
96+
function getAddress(string memory _accountId) public view returns (address) {
97+
bytes32 salt = keccak256(abi.encode(_accountId));
98+
return Clones.predictDeterministicAddress(address(_accountImplementation), salt);
99+
}
100+
101+
/// @notice Returns the list of accounts created by a signer.
102+
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory) {
103+
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
104+
return _formatAccounts(data.accountsOfSigner[_signer].values());
105+
}
106+
107+
/// @notice Returns the list of all accounts.
108+
function getAllAccounts() external view returns (AccountInfo[] memory accounts) {
109+
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
110+
return _formatAccounts(data.allAccounts.values());
111+
}
112+
113+
/*///////////////////////////////////////////////////////////////
114+
Internal functions
115+
//////////////////////////////////////////////////////////////*/
116+
117+
/// @dev Formats a list of accountIds to a list of `AccountInfo` (account id + account address).
118+
function _formatAccounts(string[] memory _accountIds) internal view returns (AccountInfo[] memory accounts) {
119+
uint256 len = _accountIds.length;
120+
accounts = new AccountInfo[](len);
121+
for (uint256 i = 0; i < len; i += 1) {
122+
string memory accountId = _accountIds[i];
123+
address account = getAddress(accountId);
124+
accounts[i] = AccountInfo(accountId, account);
125+
}
126+
}
127+
128+
/// @dev Adds an account to the list of accounts created by a signer.
129+
function _setupAccount(address _signer, string memory _accountId) internal {
130+
TWAccountFactoryStorage.Data storage data = TWAccountFactoryStorage.factoryStorage();
131+
data.allAccounts.add(_accountId);
132+
data.accountsOfSigner[_signer].add(_accountId);
47133
}
48134
}

contracts/smart-wallet/TWDynamicAccount.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ pragma solidity ^0.8.11;
88
import "./TWAccount.sol";
99
import "./BaseRouter.sol";
1010

11+
// $$\ $$\ $$\ $$\ $$\
12+
// $$ | $$ | \__| $$ | $$ |
13+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
14+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
15+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
16+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
17+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
18+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
19+
1120
contract TWDynamicAccount is TWAccount, BaseRouter {
1221
/*///////////////////////////////////////////////////////////////
1322
Constants

contracts/smart-wallet/TWDynamicAccountFactory.sol

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,132 @@ pragma solidity ^0.8.12;
44
// Utils
55
import "../extension/Multicall.sol";
66
import "@openzeppelin/contracts/proxy/Clones.sol";
7+
import "../lib/TWStringSet.sol";
78

89
// Interface
910
import "./interfaces/ITWAccountFactory.sol";
1011

1112
// Smart wallet implementation
1213
import "./TWDynamicAccount.sol";
1314

15+
// $$\ $$\ $$\ $$\ $$\
16+
// $$ | $$ | \__| $$ | $$ |
17+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
18+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
19+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
20+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
21+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
22+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
23+
24+
/*///////////////////////////////////////////////////////////////
25+
Storage layout
26+
//////////////////////////////////////////////////////////////*/
27+
28+
library TWDynamicAccountFactoryStorage {
29+
bytes32 internal constant DYNAMIC_ACCOUNT_FACTORY_STORAGE_POSITION =
30+
keccak256("tw.dynamic.account.factory.storage");
31+
32+
struct Data {
33+
TWStringSet.Set allAccounts;
34+
mapping(address => TWStringSet.Set) accountsOfSigner;
35+
}
36+
37+
function factoryStorage() internal pure returns (Data storage dynamicAccountFactoryData) {
38+
bytes32 position = DYNAMIC_ACCOUNT_FACTORY_STORAGE_POSITION;
39+
assembly {
40+
dynamicAccountFactoryData.slot := position
41+
}
42+
}
43+
}
44+
1445
contract TWDynamicAccountFactory is ITWAccountFactory, Multicall {
46+
using TWStringSet for TWStringSet.Set;
47+
48+
/*///////////////////////////////////////////////////////////////
49+
State
50+
//////////////////////////////////////////////////////////////*/
51+
1552
TWDynamicAccount private immutable _accountImplementation;
1653

54+
/*///////////////////////////////////////////////////////////////
55+
Constructor
56+
//////////////////////////////////////////////////////////////*/
57+
1758
constructor(IEntryPoint _entrypoint) {
1859
_accountImplementation = new TWDynamicAccount(_entrypoint);
1960
}
2061

21-
/// @notice Returns the implementation of the Account.
22-
function accountImplementation() external view override returns (address) {
23-
return address(_accountImplementation);
24-
}
62+
/*///////////////////////////////////////////////////////////////
63+
External functions
64+
//////////////////////////////////////////////////////////////*/
2565

26-
/// @notice Deploys a new Account with the given admin and salt.
27-
function createAccount(address _admin, bytes32 _salt) external returns (address) {
66+
/// @notice Deploys a new Account with the given admin and accountId used as salt.
67+
function createAccount(address _admin, string memory _accountId) external returns (address) {
2868
address impl = address(_accountImplementation);
29-
address account = Clones.predictDeterministicAddress(impl, _salt);
69+
bytes32 salt = keccak256(abi.encode(_accountId));
70+
address account = Clones.predictDeterministicAddress(impl, salt);
3071

3172
if (account.code.length > 0) {
3273
return account;
3374
}
3475

35-
account = Clones.cloneDeterministic(impl, _salt);
76+
account = Clones.cloneDeterministic(impl, salt);
3677

3778
TWAccount(payable(account)).initialize(_admin);
3879

39-
emit AccountCreated(account, _admin, _salt);
80+
_setupAccount(_admin, _accountId);
81+
82+
emit AccountCreated(account, _admin, _accountId);
4083

4184
return account;
4285
}
4386

44-
/// @notice Returns the address of an Account that would be deployed with the given salt.
45-
function getAddress(bytes32 _salt) external view returns (address) {
46-
return Clones.predictDeterministicAddress(address(_accountImplementation), _salt);
87+
/*///////////////////////////////////////////////////////////////
88+
View functions
89+
//////////////////////////////////////////////////////////////*/
90+
91+
/// @notice Returns the implementation of the Account.
92+
function accountImplementation() external view override returns (address) {
93+
return address(_accountImplementation);
94+
}
95+
96+
/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
97+
function getAddress(string memory _accountId) public view returns (address) {
98+
bytes32 salt = keccak256(abi.encode(_accountId));
99+
return Clones.predictDeterministicAddress(address(_accountImplementation), salt);
100+
}
101+
102+
/// @notice Returns the list of accounts created by a signer.
103+
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory) {
104+
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
105+
return _formatAccounts(data.accountsOfSigner[_signer].values());
106+
}
107+
108+
/// @notice Returns the list of all accounts.
109+
function getAllAccounts() external view returns (AccountInfo[] memory accounts) {
110+
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
111+
return _formatAccounts(data.allAccounts.values());
112+
}
113+
114+
/*///////////////////////////////////////////////////////////////
115+
Internal functions
116+
//////////////////////////////////////////////////////////////*/
117+
118+
/// @dev Formats a list of accountIds to a list of `AccountInfo` (account id + account address).
119+
function _formatAccounts(string[] memory _accountIds) internal view returns (AccountInfo[] memory accounts) {
120+
uint256 len = _accountIds.length;
121+
accounts = new AccountInfo[](len);
122+
for (uint256 i = 0; i < len; i += 1) {
123+
string memory accountId = _accountIds[i];
124+
address account = getAddress(accountId);
125+
accounts[i] = AccountInfo(accountId, account);
126+
}
127+
}
128+
129+
/// @dev Adds an account to the list of accounts created by a signer.
130+
function _setupAccount(address _signer, string memory _accountId) internal {
131+
TWDynamicAccountFactoryStorage.Data storage data = TWDynamicAccountFactoryStorage.factoryStorage();
132+
data.allAccounts.add(_accountId);
133+
data.accountsOfSigner[_signer].add(_accountId);
47134
}
48135
}

contracts/smart-wallet/interfaces/ITWAccountFactory.sol

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,43 @@
22
pragma solidity ^0.8.12;
33

44
interface ITWAccountFactory {
5-
event AccountCreated(address indexed account, address indexed accountAdmin, bytes32 indexed salt);
5+
/*///////////////////////////////////////////////////////////////
6+
Structs
7+
//////////////////////////////////////////////////////////////*/
8+
9+
/// @notice Smart account details: address and ID (used as salt).
10+
struct AccountInfo {
11+
string id;
12+
address account;
13+
}
14+
15+
/*///////////////////////////////////////////////////////////////
16+
Events
17+
//////////////////////////////////////////////////////////////*/
18+
19+
/// @notice Emitted when a new Account is created.
20+
event AccountCreated(address indexed account, address indexed accountAdmin, string indexed accountId);
21+
22+
/*///////////////////////////////////////////////////////////////
23+
Extension Functions
24+
//////////////////////////////////////////////////////////////*/
25+
26+
/// @notice Deploys a new Account with the given admin and accountId used as salt.
27+
function createAccount(address admin, string memory accountId) external returns (address account);
28+
29+
/*///////////////////////////////////////////////////////////////
30+
View Functions
31+
//////////////////////////////////////////////////////////////*/
632

733
/// @notice Returns the address of the Account implementation.
834
function accountImplementation() external view returns (address);
935

10-
/// @notice Deploys a new Account with the given admin and salt.
11-
function createAccount(address admin, bytes32 salt) external returns (address account);
36+
/// @notice Returns the address of an Account that would be deployed with the given accountId as salt.
37+
function getAddress(string memory accountId) external view returns (address);
38+
39+
/// @notice Returns the list of accounts created by a signer.
40+
function getAccountsOfSigner(address _signer) external view returns (AccountInfo[] memory);
1241

13-
/// @notice Returns the address of an Account that would be deployed with the given salt.
14-
function getAddress(bytes32 salt) external view returns (address);
42+
/// @notice Returns the list of all accounts.
43+
function getAllAccounts() external view returns (AccountInfo[] memory);
1544
}

0 commit comments

Comments
 (0)