Skip to content

Commit f27b32e

Browse files
authored
Add getAllWallets to BaseAccountFactory (#412)
* Add getAllWallets to BaseAccountFactory * Remove redundant local isRegistered flag * Move execute and executeBatch from AccountCore -> AccountExtension * rename getAllWallets -> getAllAccounts + tests * Remove unused functions from BytesLib * docs update * dev build * Add getAllAccounts to IAccountFactory * dev build * pkg update
1 parent ba441f1 commit f27b32e

18 files changed

+436
-26
lines changed

contracts/lib/BytesLib.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: Apache 2.0
2+
/*
3+
* @title Solidity Bytes Arrays Utils
4+
* @author Gonçalo Sá <[email protected]>
5+
*
6+
* @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
7+
* The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
8+
*
9+
* Credits: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
10+
*/
11+
12+
pragma solidity >=0.8.0 <0.9.0;
13+
14+
library BytesLib {
15+
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
16+
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
17+
address tempAddress;
18+
19+
assembly {
20+
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
21+
}
22+
23+
return tempAddress;
24+
}
25+
}

contracts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@thirdweb-dev/contracts",
33
"description": "Collection of smart contracts deployable via the thirdweb SDK, dashboard and CLI",
4-
"version": "3.5.8",
4+
"version": "3.5.9",
55
"license": "Apache-2.0",
66
"repository": {
77
"type": "git",

contracts/smart-wallet/dynamic/DynamicAccountFactory.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ contract DynamicAccountFactory is BaseAccountFactory, ContractMetadata, Permissi
2727
//////////////////////////////////////////////////////////////*/
2828

2929
constructor(IEntryPoint _entrypoint, IExtension.Extension[] memory _defaultExtensions)
30-
BaseAccountFactory(payable(address(new DynamicAccount(_entrypoint, _defaultExtensions))))
30+
BaseAccountFactory(payable(address(new DynamicAccount(_entrypoint, _defaultExtensions))), address(_entrypoint))
3131
{
3232
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
3333
}

contracts/smart-wallet/interfaces/IAccountFactory.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ interface IAccountFactory {
3535
/// @notice Returns the address of the Account implementation.
3636
function accountImplementation() external view returns (address);
3737

38+
/// @notice Returns all accounts created on the factory.
39+
function getAllAccounts() external view returns (address[] memory);
40+
3841
/// @notice Returns the address of an Account that would be deployed with the given admin signer.
3942
function getAddress(address adminSigner, bytes calldata data) external view returns (address);
4043

contracts/smart-wallet/managed/ManagedAccountFactory.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ contract ManagedAccountFactory is BaseAccountFactory, ContractMetadata, Permissi
2828

2929
constructor(IEntryPoint _entrypoint, Extension[] memory _defaultExtensions)
3030
BaseRouter(_defaultExtensions)
31-
BaseAccountFactory(payable(address(new ManagedAccount(_entrypoint, address(this)))))
31+
BaseAccountFactory(payable(address(new ManagedAccount(_entrypoint, address(this)))), address(_entrypoint))
3232
{
3333
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
3434
}

contracts/smart-wallet/non-upgradeable/Account.sol

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pragma solidity ^0.8.11;
66
/* solhint-disable reason-string */
77

88
// Base
9-
import "./../utils/BaseAccount.sol";
9+
import "../utils/BaseAccount.sol";
1010

1111
// Extensions
1212
import "../../extension/Multicall.sol";
@@ -19,7 +19,7 @@ import "../../eip/ERC1271.sol";
1919

2020
// Utils
2121
import "../../openzeppelin-presets/utils/cryptography/ECDSA.sol";
22-
import "./AccountFactory.sol";
22+
import "../utils/BaseAccountFactory.sol";
2323

2424
// $$\ $$\ $$\ $$\ $$\
2525
// $$ | $$ | \__| $$ | $$ |
@@ -176,6 +176,7 @@ contract Account is
176176
uint256 _value,
177177
bytes calldata _calldata
178178
) external virtual onlyAdminOrEntrypoint {
179+
_registerOnFactory();
179180
_call(_target, _value, _calldata);
180181
}
181182

@@ -185,6 +186,8 @@ contract Account is
185186
uint256[] calldata _value,
186187
bytes[] calldata _calldata
187188
) external virtual onlyAdminOrEntrypoint {
189+
_registerOnFactory();
190+
188191
require(_target.length == _calldata.length && _target.length == _value.length, "Account: wrong array lengths.");
189192
for (uint256 i = 0; i < _target.length; i++) {
190193
_call(_target[i], _value[i], _calldata[i]);
@@ -205,6 +208,14 @@ contract Account is
205208
Internal functions
206209
//////////////////////////////////////////////////////////////*/
207210

211+
/// @dev Registers the account on the factory if it hasn't been registered yet.
212+
function _registerOnFactory() internal virtual {
213+
BaseAccountFactory factoryContract = BaseAccountFactory(factory);
214+
if (!factoryContract.isRegistered(address(this))) {
215+
factoryContract.onRegister();
216+
}
217+
}
218+
208219
/// @dev Calls a target contract and reverts if it fails.
209220
function _call(
210221
address _target,
@@ -268,9 +279,9 @@ contract Account is
268279
super._setAdmin(_account, _isAdmin);
269280
if (factory.code.length > 0) {
270281
if (_isAdmin) {
271-
AccountFactory(factory).onSignerAdded(_account);
282+
BaseAccountFactory(factory).onSignerAdded(_account);
272283
} else {
273-
AccountFactory(factory).onSignerRemoved(_account);
284+
BaseAccountFactory(factory).onSignerRemoved(_account);
274285
}
275286
}
276287
}
@@ -279,9 +290,9 @@ contract Account is
279290
function _afterChangeRole(RoleRequest calldata _req) internal virtual override {
280291
if (factory.code.length > 0) {
281292
if (_req.action == RoleAction.GRANT) {
282-
AccountFactory(factory).onSignerAdded(_req.target);
293+
BaseAccountFactory(factory).onSignerAdded(_req.target);
283294
} else if (_req.action == RoleAction.REVOKE) {
284-
AccountFactory(factory).onSignerRemoved(_req.target);
295+
BaseAccountFactory(factory).onSignerRemoved(_req.target);
285296
}
286297
}
287298
}

contracts/smart-wallet/non-upgradeable/AccountFactory.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ contract AccountFactory is BaseAccountFactory, ContractMetadata, PermissionsEnum
3030
Constructor
3131
//////////////////////////////////////////////////////////////*/
3232

33-
constructor(IEntryPoint _entrypoint) BaseAccountFactory(address(new Account(_entrypoint, address(this)))) {
33+
constructor(IEntryPoint _entrypoint)
34+
BaseAccountFactory(address(new Account(_entrypoint, address(this))), address(_entrypoint))
35+
{
3436
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
3537
}
3638

contracts/smart-wallet/utils/AccountExtension.sol

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import "../../openzeppelin-presets/token/ERC1155/utils/ERC1155Holder.sol";
1313

1414
// Utils
1515
import "../../openzeppelin-presets/utils/cryptography/ECDSA.sol";
16+
import "./BaseAccountFactory.sol";
17+
import "./AccountCore.sol";
1618

1719
// $$\ $$\ $$\ $$\ $$\
1820
// $$ | $$ | \__| $$ | $$ |
@@ -37,19 +39,19 @@ contract AccountExtension is ContractMetadata, AccountPermissions, ERC721Holder,
3739
Constructor, Initializer, Modifiers
3840
//////////////////////////////////////////////////////////////*/
3941

42+
/// @notice Checks whether the caller is the EntryPoint contract or the admin.
43+
modifier onlyAdminOrEntrypoint() virtual {
44+
require(msg.sender == address(entrypointContract) || isAdmin(msg.sender), "Account: not admin or EntryPoint.");
45+
_;
46+
}
47+
4048
// solhint-disable-next-line no-empty-blocks
4149
receive() external payable virtual {}
4250

4351
constructor(address _entrypoint) EIP712("Account", "1") {
4452
entrypointContract = _entrypoint;
4553
}
4654

47-
/// @notice Checks whether the caller is the EntryPoint contract or the admin.
48-
modifier onlyAdminOrEntrypoint() virtual {
49-
require(msg.sender == entrypointContract || isAdmin(msg.sender), "Account: not admin or EntryPoint.");
50-
_;
51-
}
52-
5355
/*///////////////////////////////////////////////////////////////
5456
View functions
5557
//////////////////////////////////////////////////////////////*/
@@ -72,6 +74,7 @@ contract AccountExtension is ContractMetadata, AccountPermissions, ERC721Holder,
7274
uint256 _value,
7375
bytes calldata _calldata
7476
) external virtual onlyAdminOrEntrypoint {
77+
_registerOnFactory();
7578
_call(_target, _value, _calldata);
7679
}
7780

@@ -81,6 +84,7 @@ contract AccountExtension is ContractMetadata, AccountPermissions, ERC721Holder,
8184
uint256[] calldata _value,
8285
bytes[] calldata _calldata
8386
) external virtual onlyAdminOrEntrypoint {
87+
_registerOnFactory();
8488
require(_target.length == _calldata.length && _target.length == _value.length, "Account: wrong array lengths.");
8589
for (uint256 i = 0; i < _target.length; i++) {
8690
_call(_target[i], _value[i], _calldata[i]);
@@ -91,6 +95,15 @@ contract AccountExtension is ContractMetadata, AccountPermissions, ERC721Holder,
9195
Internal functions
9296
//////////////////////////////////////////////////////////////*/
9397

98+
/// @dev Registers the account on the factory if it hasn't been registered yet.
99+
function _registerOnFactory() internal virtual {
100+
address factory = AccountCore(payable(address(this))).factory();
101+
BaseAccountFactory factoryContract = BaseAccountFactory(factory);
102+
if (!factoryContract.isRegistered(address(this))) {
103+
factoryContract.onRegister();
104+
}
105+
}
106+
94107
/// @dev Calls a target contract and reverts if it fails.
95108
function _call(
96109
address _target,

contracts/smart-wallet/utils/BaseAccountFactory.sol

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "../../openzeppelin-presets/proxy/Clones.sol";
77
import "../../openzeppelin-presets/utils/structs/EnumerableSet.sol";
88
import "../utils/BaseAccount.sol";
99
import "../../extension/interface/IAccountPermissions.sol";
10+
import "../../lib/BytesLib.sol";
1011

1112
// Interface
1213
import "../interfaces/IEntrypoint.sol";
@@ -29,16 +30,19 @@ abstract contract BaseAccountFactory is IAccountFactory, Multicall {
2930
//////////////////////////////////////////////////////////////*/
3031

3132
address public immutable accountImplementation;
33+
address public immutable entrypoint;
3234

35+
EnumerableSet.AddressSet private allAccounts;
3336
mapping(address => EnumerableSet.AddressSet) internal accountsOfSigner;
3437
mapping(address => EnumerableSet.AddressSet) internal signersOfAccount;
3538

3639
/*///////////////////////////////////////////////////////////////
3740
Constructor
3841
//////////////////////////////////////////////////////////////*/
3942

40-
constructor(address _accountImpl) {
43+
constructor(address _accountImpl, address _entrypoint) {
4144
accountImplementation = _accountImpl;
45+
entrypoint = _entrypoint;
4246
}
4347

4448
/*///////////////////////////////////////////////////////////////
@@ -57,18 +61,28 @@ abstract contract BaseAccountFactory is IAccountFactory, Multicall {
5761

5862
account = Clones.cloneDeterministic(impl, salt);
5963

64+
if (msg.sender != entrypoint) {
65+
require(allAccounts.add(account), "AccountFactory: account already registered");
66+
}
67+
6068
_initializeAccount(account, _admin, _data);
6169

6270
emit AccountCreated(account, _admin);
6371

6472
return account;
6573
}
6674

67-
/// @notice Callback function for an Account to register its signers.
68-
function onSignerAdded(address _signer) external {
75+
/// @notice Callback function for an Account to register itself on the factory.
76+
function onRegister() external {
6977
address account = msg.sender;
78+
require(_isAccountOfFactory(account), "AccountFactory: not an account.");
79+
80+
require(allAccounts.add(account), "AccountFactory: account already registered");
81+
}
7082

71-
require(account.code.length > 0, "AccountFactory: not an account.");
83+
function onSignerAdded(address _signer) external {
84+
address account = msg.sender;
85+
require(_isAccountOfFactory(account), "AccountFactory: not an account.");
7286

7387
bool isAdmin = IAccountPermissions(account).isAdmin(_signer);
7488

@@ -94,8 +108,7 @@ abstract contract BaseAccountFactory is IAccountFactory, Multicall {
94108
/// @notice Callback function for an Account to un-register its signers.
95109
function onSignerRemoved(address _signer) external {
96110
address account = msg.sender;
97-
98-
require(account.code.length > 0, "AccountFactory: not an account.");
111+
require(_isAccountOfFactory(account), "AccountFactory: not an account.");
99112

100113
bool isAccount = accountsOfSigner[_signer].remove(account);
101114
bool isSigner = signersOfAccount[account].remove(_signer);
@@ -111,6 +124,16 @@ abstract contract BaseAccountFactory is IAccountFactory, Multicall {
111124
View functions
112125
//////////////////////////////////////////////////////////////*/
113126

127+
/// @notice Returns whether an account is registered on this factory.
128+
function isRegistered(address _account) external view returns (bool) {
129+
return allAccounts.contains(_account);
130+
}
131+
132+
/// @notice Returns all accounts created on the factory.
133+
function getAllAccounts() external view returns (address[] memory) {
134+
return allAccounts.values();
135+
}
136+
114137
/// @notice Returns the address of an Account that would be deployed with the given admin signer.
115138
function getAddress(address _adminSigner, bytes calldata _data) public view returns (address) {
116139
bytes32 salt = _generateSalt(_adminSigner, _data);
@@ -131,6 +154,17 @@ abstract contract BaseAccountFactory is IAccountFactory, Multicall {
131154
Internal functions
132155
//////////////////////////////////////////////////////////////*/
133156

157+
/// @dev Returns whether the caller is an account deployed by this factory.
158+
function _isAccountOfFactory(address _account) internal view virtual returns (bool) {
159+
address impl = _getImplementation(_account);
160+
return _account.code.length > 0 && impl == accountImplementation;
161+
}
162+
163+
function _getImplementation(address cloneAddress) internal view returns (address) {
164+
bytes memory code = cloneAddress.code;
165+
return BytesLib.toAddress(code, 10);
166+
}
167+
134168
/// @dev Returns the salt used when deploying an Account.
135169
function _generateSalt(address _admin, bytes calldata) internal view virtual returns (bytes32) {
136170
return keccak256(abi.encode(_admin));

0 commit comments

Comments
 (0)