Skip to content

Commit 3d88826

Browse files
authored
Create TW Managed account (#369)
1 parent aa53fbd commit 3d88826

File tree

9 files changed

+417
-15
lines changed

9 files changed

+417
-15
lines changed

contracts/smart-wallet/TWDynamicAccount.sol renamed to contracts/smart-wallet/dynamic/TWDynamicAccount.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ pragma solidity ^0.8.11;
55
/* solhint-disable no-inline-assembly */
66
/* solhint-disable reason-string */
77

8-
import "./TWAccount.sol";
9-
import "./BaseRouter.sol";
8+
import "../non-upgradeable/TWAccount.sol";
9+
import "../utils/BaseRouter.sol";
1010

1111
// $$\ $$\ $$\ $$\ $$\
1212
// $$ | $$ | \__| $$ | $$ |

contracts/smart-wallet/TWDynamicAccountFactory.sol renamed to contracts/smart-wallet/dynamic/TWDynamicAccountFactory.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
pragma solidity ^0.8.12;
33

44
// Utils
5-
import "../extension/Multicall.sol";
5+
import "../../extension/Multicall.sol";
66
import "@openzeppelin/contracts/proxy/Clones.sol";
7-
import "../lib/TWStringSet.sol";
7+
import "../../lib/TWStringSet.sol";
88

99
// Interface
10-
import "./interfaces/ITWAccountFactory.sol";
10+
import "./../interfaces/ITWAccountFactory.sol";
1111

1212
// Smart wallet implementation
1313
import "./TWDynamicAccount.sol";
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/* solhint-disable avoid-low-level-calls */
5+
/* solhint-disable no-inline-assembly */
6+
/* solhint-disable reason-string */
7+
8+
// Base
9+
import "./../utils/BaseAccount.sol";
10+
11+
// Fixed Extensions
12+
import "../../extension/Multicall.sol";
13+
import "../../dynamic-contracts/extension/Initializable.sol";
14+
15+
// Utils
16+
import "../../openzeppelin-presets/utils/cryptography/ECDSA.sol";
17+
import "../../dynamic-contracts/extension/PermissionsEnumerable.sol";
18+
19+
// $$\ $$\ $$\ $$\ $$\
20+
// $$ | $$ | \__| $$ | $$ |
21+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
22+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
23+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
24+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
25+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
26+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
27+
28+
/*///////////////////////////////////////////////////////////////
29+
Storage layout
30+
//////////////////////////////////////////////////////////////*/
31+
32+
library TWAccountStorage {
33+
bytes32 internal constant TWACCOUNT_STORAGE_POSITION = keccak256("twaccount.storage");
34+
35+
struct Data {
36+
uint256 nonce;
37+
}
38+
39+
function accountStorage() internal pure returns (Data storage twaccountData) {
40+
bytes32 position = TWACCOUNT_STORAGE_POSITION;
41+
assembly {
42+
twaccountData.slot := position
43+
}
44+
}
45+
}
46+
47+
contract TWAccountCore is Initializable, Multicall, BaseAccount {
48+
using ECDSA for bytes32;
49+
50+
/*///////////////////////////////////////////////////////////////
51+
State
52+
//////////////////////////////////////////////////////////////*/
53+
54+
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
55+
bytes32 public constant SIGNER_ROLE = keccak256("SIGNER_ROLE");
56+
57+
/// @notice EIP 4337 Entrypoint contract.
58+
IEntryPoint private immutable entrypointContract;
59+
60+
/*///////////////////////////////////////////////////////////////
61+
Constructor, Initializer, Modifiers
62+
//////////////////////////////////////////////////////////////*/
63+
64+
// solhint-disable-next-line no-empty-blocks
65+
receive() external payable virtual {}
66+
67+
constructor(IEntryPoint _entrypoint) {
68+
entrypointContract = _entrypoint;
69+
}
70+
71+
/// @notice Initializes the smart contract wallet.
72+
function initialize(address _defaultAdmin) public virtual initializer {
73+
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
74+
}
75+
76+
/*///////////////////////////////////////////////////////////////
77+
View functions
78+
//////////////////////////////////////////////////////////////*/
79+
80+
/// @notice Returns the nonce of the account.
81+
function nonce() public view virtual override returns (uint256) {
82+
TWAccountStorage.Data storage twaccountData = TWAccountStorage.accountStorage();
83+
return twaccountData.nonce;
84+
}
85+
86+
/// @notice Returns the EIP 4337 entrypoint contract.
87+
function entryPoint() public view virtual override returns (IEntryPoint) {
88+
return entrypointContract;
89+
}
90+
91+
/// @notice Returns the balance of the account in Entrypoint.
92+
function getDeposit() public view returns (uint256) {
93+
return entryPoint().balanceOf(address(this));
94+
}
95+
96+
/// @notice Returns whether a signer is authorized to perform transactions using the wallet.
97+
function isValidSigner(address _signer) public view virtual returns (bool) {
98+
return _hasRole(SIGNER_ROLE, _signer) || _hasRole(DEFAULT_ADMIN_ROLE, _signer);
99+
}
100+
101+
/*///////////////////////////////////////////////////////////////
102+
External functions
103+
//////////////////////////////////////////////////////////////*/
104+
105+
/// @notice Deposit funds for this account in Entrypoint.
106+
function addDeposit() public payable {
107+
entryPoint().depositTo{ value: msg.value }(address(this));
108+
}
109+
110+
/// @notice Withdraw funds for this account from Entrypoint.
111+
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public {
112+
require(_hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "TWAccount: not admin");
113+
entryPoint().withdrawTo(withdrawAddress, amount);
114+
}
115+
116+
/*///////////////////////////////////////////////////////////////
117+
Internal functions
118+
//////////////////////////////////////////////////////////////*/
119+
120+
/// @dev Validates the nonce of a user operation and updates account nonce.
121+
function _validateAndUpdateNonce(UserOperation calldata userOp) internal override {
122+
TWAccountStorage.Data storage data = TWAccountStorage.accountStorage();
123+
require(data.nonce == userOp.nonce, "TWAccount: invalid nonce");
124+
125+
data.nonce += 1;
126+
}
127+
128+
/// @notice Validates the signature of a user operation.
129+
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)
130+
internal
131+
virtual
132+
override
133+
returns (uint256 validationData)
134+
{
135+
bytes32 hash = userOpHash.toEthSignedMessageHash();
136+
address signer = hash.recover(userOp.signature);
137+
138+
if (!isValidSigner(signer)) return SIG_VALIDATION_FAILED;
139+
return 0;
140+
}
141+
142+
/// @notice See Permissions-hasRole
143+
function _hasRole(bytes32 _role, address _account) public view returns (bool) {
144+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
145+
return data._hasRole[_role][_account];
146+
}
147+
148+
/// @notice See Permissions-RoleGranted
149+
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
150+
151+
/// @notice See Permissions-setupRole
152+
function _setupRole(bytes32 role, address account) internal virtual {
153+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
154+
data._hasRole[role][account] = true;
155+
emit RoleGranted(role, account, msg.sender);
156+
}
157+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/* solhint-disable avoid-low-level-calls */
5+
/* solhint-disable no-inline-assembly */
6+
/* solhint-disable reason-string */
7+
8+
// Extensions
9+
import "../../dynamic-contracts/extension/PermissionsEnumerable.sol";
10+
import "../../dynamic-contracts/extension/ContractMetadata.sol";
11+
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
12+
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
13+
14+
// Utils
15+
import "../../openzeppelin-presets/utils/cryptography/ECDSA.sol";
16+
17+
// $$\ $$\ $$\ $$\ $$\
18+
// $$ | $$ | \__| $$ | $$ |
19+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
20+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
21+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
22+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
23+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
24+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
25+
26+
contract TWAccountExtension is ContractMetadata, PermissionsEnumerable, ERC721Holder, ERC1155Holder {
27+
using ECDSA for bytes32;
28+
29+
/*///////////////////////////////////////////////////////////////
30+
State
31+
//////////////////////////////////////////////////////////////*/
32+
33+
bytes32 public constant SIGNER_ROLE = keccak256("SIGNER_ROLE");
34+
35+
/// @notice EIP 4337 Entrypoint contract.
36+
address private immutable entrypointContract;
37+
38+
/*///////////////////////////////////////////////////////////////
39+
Constructor, Initializer, Modifiers
40+
//////////////////////////////////////////////////////////////*/
41+
42+
// solhint-disable-next-line no-empty-blocks
43+
receive() external payable virtual {}
44+
45+
constructor(address _entrypoint) {
46+
entrypointContract = _entrypoint;
47+
}
48+
49+
/// @notice Checks whether the caller is the EntryPoint contract or the admin.
50+
modifier onlyAdminOrEntrypoint() {
51+
require(
52+
msg.sender == entrypointContract || hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
53+
"TWAccount: not admin or EntryPoint."
54+
);
55+
_;
56+
}
57+
58+
/*///////////////////////////////////////////////////////////////
59+
View functions
60+
//////////////////////////////////////////////////////////////*/
61+
62+
/// @notice See {IERC165-supportsInterface}.
63+
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Receiver) returns (bool) {
64+
return
65+
interfaceId == type(IERC1155Receiver).interfaceId ||
66+
interfaceId == type(IERC721Receiver).interfaceId ||
67+
super.supportsInterface(interfaceId);
68+
}
69+
70+
/*///////////////////////////////////////////////////////////////
71+
External functions
72+
//////////////////////////////////////////////////////////////*/
73+
74+
/// @notice Executes a transaction (called directly from an admin, or by entryPoint)
75+
function execute(
76+
address _target,
77+
uint256 _value,
78+
bytes calldata _calldata
79+
) external virtual onlyAdminOrEntrypoint {
80+
_call(_target, _value, _calldata);
81+
}
82+
83+
/// @notice Executes a sequence transaction (called directly from an admin, or by entryPoint)
84+
function executeBatch(
85+
address[] calldata _target,
86+
uint256[] calldata _value,
87+
bytes[] calldata _calldata
88+
) external virtual onlyAdminOrEntrypoint {
89+
require(
90+
_target.length == _calldata.length && _target.length == _value.length,
91+
"TWAccount: wrong array lengths."
92+
);
93+
for (uint256 i = 0; i < _target.length; i++) {
94+
_call(_target[i], _value[i], _calldata[i]);
95+
}
96+
}
97+
98+
/*///////////////////////////////////////////////////////////////
99+
Internal functions
100+
//////////////////////////////////////////////////////////////*/
101+
102+
/// @dev Calls a target contract and reverts if it fails.
103+
function _call(
104+
address _target,
105+
uint256 value,
106+
bytes memory _calldata
107+
) internal {
108+
(bool success, bytes memory result) = _target.call{ value: value }(_calldata);
109+
if (!success) {
110+
assembly {
111+
revert(add(result, 32), mload(result))
112+
}
113+
}
114+
}
115+
116+
/// @dev Returns whether contract metadata can be set in the given execution context.
117+
function _canSetContractURI() internal view virtual override returns (bool) {
118+
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
119+
}
120+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/* solhint-disable avoid-low-level-calls */
5+
/* solhint-disable no-inline-assembly */
6+
/* solhint-disable reason-string */
7+
8+
// $$\ $$\ $$\ $$\ $$\
9+
// $$ | $$ | \__| $$ | $$ |
10+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
11+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
12+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
13+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
14+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
15+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
16+
17+
import "./TWAccountCore.sol";
18+
import "lib/dynamic-contracts/src/core/Router.sol";
19+
20+
contract TWManagedAccount is TWAccountCore, Router {
21+
address public factory;
22+
23+
constructor(IEntryPoint _entrypoint) TWAccountCore(_entrypoint) {}
24+
25+
/// @notice Initializes the smart contract wallet.
26+
function initialize(address _defaultAdmin) public virtual override initializer {
27+
factory = msg.sender;
28+
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
29+
}
30+
31+
// solhint-disable-next-line no-empty-blocks
32+
receive() external payable virtual override(Router, TWAccountCore) {}
33+
34+
/// @notice Returns the implementation contract address for a given function signature.
35+
function getImplementationForFunction(bytes4) public view virtual override returns (address) {
36+
return factory;
37+
}
38+
}

0 commit comments

Comments
 (0)