1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^ 0.8.21 ;
3
+
4
+ import {IEthMultiVault} from "src/interfaces/IEthMultiVault.sol " ;
5
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol " ;
6
+ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol " ;
7
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol " ;
8
+ import {BaseAccount, UserOperation} from "account-abstraction/contracts/core/BaseAccount.sol " ;
9
+ import {IEntryPoint} from "account-abstraction/contracts/interfaces/IEntryPoint.sol " ;
10
+ import {Errors} from "./libraries/Errors.sol " ;
11
+
12
+ /**
13
+ * @title AtomWallet
14
+ * @notice This contract is an abstract account associated with a corresponding atom.
15
+ */
16
+ contract AtomWallet is Initializable , BaseAccount , OwnableUpgradeable {
17
+ using ECDSA for bytes32 ;
18
+
19
+ /// @notice The storage slot for the AtomWallet contract ownable storage
20
+ /// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
21
+ bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300 ;
22
+
23
+ /// @notice The EthMultiVault contract address
24
+ IEthMultiVault public ethMultiVault;
25
+
26
+ /// @notice The flag to indicate if the wallet's ownership has been claimed by the user
27
+ bool public isClaimed;
28
+
29
+ /// @notice The entry point contract address
30
+ IEntryPoint private _entryPoint;
31
+
32
+ // solhint-disable-next-line no-empty-blocks
33
+ receive () external payable {}
34
+
35
+ /**
36
+ * @notice Initialize the AtomWallet contract
37
+ * @param anEntryPoint the entry point contract address
38
+ * @param anOwner the owner of the contract (`walletConfig.atomWarden` is the initial owner of all atom wallets)
39
+ * @param _ethMultiVault the EthMultiVault contract address
40
+ */
41
+ function init (IEntryPoint anEntryPoint , address anOwner , IEthMultiVault _ethMultiVault ) external initializer {
42
+ __Ownable_init (anOwner);
43
+ _entryPoint = anEntryPoint;
44
+ ethMultiVault = _ethMultiVault;
45
+ }
46
+
47
+ /// @notice Get the entry point contract address
48
+ /// @return the entry point contract address
49
+ function entryPoint () public view virtual override returns (IEntryPoint) {
50
+ return _entryPoint;
51
+ }
52
+
53
+ /**
54
+ * @notice Execute a transaction (called directly from owner, or by entryPoint)
55
+ * @param dest the target address
56
+ * @param value the value to send
57
+ * @param func the function call data
58
+ */
59
+ function execute (address dest , uint256 value , bytes calldata func ) external onlyOwnerOrEntryPoint {
60
+ _call (dest, value, func);
61
+ }
62
+
63
+ /**
64
+ * @notice Execute a sequence (batch) of transactions
65
+ * @param dest the target addresses array
66
+ * @param func the function call data array
67
+ */
68
+ function executeBatch (address [] calldata dest , bytes [] calldata func ) external onlyOwnerOrEntryPoint {
69
+ if (dest.length != func.length ) {
70
+ revert Errors.AtomWallet_WrongArrayLengths ();
71
+ }
72
+
73
+ for (uint256 i = 0 ; i < dest.length ; i++ ) {
74
+ _call (dest[i], 0 , func[i]);
75
+ }
76
+ }
77
+
78
+ /// implement template method of BaseAccount
79
+ /**
80
+ * @notice Validate the signature of the user operation
81
+ * @param userOp the user operation
82
+ * @param userOpHash the hash of the user operation
83
+ * @return validationData the validation data (0 if successful)
84
+ */
85
+ function _validateSignature (UserOperation calldata userOp , bytes32 userOpHash )
86
+ internal
87
+ virtual
88
+ override
89
+ returns (uint256 validationData )
90
+ {
91
+ bytes32 hash = keccak256 (abi.encodePacked ("\x19Ethereum Signed Message:\n32 " , userOpHash));
92
+
93
+ (address recovered ,,) = ECDSA.tryRecover (hash, userOp.signature);
94
+
95
+ if (recovered != owner ()) {
96
+ return SIG_VALIDATION_FAILED;
97
+ }
98
+
99
+ return 0 ;
100
+ }
101
+
102
+ /**
103
+ * @notice An internal method that calls a target address with value and data
104
+ * @param target the target address
105
+ * @param value the value to send
106
+ * @param data the function call data
107
+ */
108
+ function _call (address target , uint256 value , bytes memory data ) internal {
109
+ (bool success , bytes memory result ) = target.call {value: value}(data);
110
+ if (! success) {
111
+ assembly {
112
+ revert (add (result, 32 ), mload (result))
113
+ }
114
+ }
115
+ }
116
+
117
+ /// @notice Returns the deposit of the account in the entry point contract
118
+ function getDeposit () public view returns (uint256 ) {
119
+ return entryPoint ().balanceOf (address (this ));
120
+ }
121
+
122
+ /// @notice Add deposit to the account in the entry point contract
123
+ function addDeposit () public payable {
124
+ entryPoint ().depositTo {value: msg .value }(address (this ));
125
+ }
126
+
127
+ /**
128
+ * @notice Withdraws value from the account's deposit
129
+ * @param withdrawAddress target to send to
130
+ * @param amount to withdraw
131
+ */
132
+ function withdrawDepositTo (address payable withdrawAddress , uint256 amount ) public {
133
+ if (! (msg .sender == owner () || msg .sender == address (this ))) {
134
+ revert Errors.AtomWallet_OnlyOwner ();
135
+ }
136
+ entryPoint ().withdrawTo (withdrawAddress, amount);
137
+ }
138
+
139
+ /// @notice Transfer ownership of the wallet to a new owner. Ifn the wallet's ownership
140
+ /// is being transferred to the user, the wallet is considered claimed. Once claimed,
141
+ /// wallet is considered owned by the user and the action cannot be undone.
142
+ /// @param newOwner the new owner of the wallet
143
+ function transferOwnership (address newOwner ) public override onlyOwner {
144
+ if (newOwner == address (0 )) {
145
+ revert OwnableInvalidOwner (address (0 ));
146
+ }
147
+
148
+ if (! isClaimed && newOwner != ethMultiVault.getAtomWarden ()) {
149
+ isClaimed = true ;
150
+ }
151
+
152
+ _transferOwnership (newOwner);
153
+ }
154
+
155
+ /// @notice Returns the owner of the wallet. If the wallet has been claimed, the owner
156
+ /// is the user. Otherwise, the owner is the atomWarden.
157
+ function owner () public view override returns (address ) {
158
+ OwnableStorage storage $ = _getAtomWalletOwnableStorage ();
159
+ return isClaimed ? $._owner : ethMultiVault.getAtomWarden ();
160
+ }
161
+
162
+ /// @dev Get the storage slot for the AtomWallet contract ownable storage
163
+ function _getAtomWalletOwnableStorage () private pure returns (OwnableStorage storage $) {
164
+ assembly {
165
+ $.slot := OwnableStorageLocation
166
+ }
167
+ }
168
+
169
+ /// @dev Modifier to allow only the owner or entry point to call a function
170
+ modifier onlyOwnerOrEntryPoint () {
171
+ if (! (msg .sender == address (entryPoint ()) || msg .sender == owner ())) {
172
+ revert Errors.AtomWallet_OnlyOwnerOrEntryPoint ();
173
+ }
174
+ _;
175
+ }
176
+ }
0 commit comments