/
Account.sol
304 lines (255 loc) · 11.5 KB
/
Account.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */
// Base
import "../utils/BaseAccount.sol";
// Extensions
import "../../extension/Multicall.sol";
import "../../dynamic-contracts/extension/Initializable.sol";
import "../../dynamic-contracts/extension/AccountPermissions.sol";
import "../../dynamic-contracts/extension/ContractMetadata.sol";
import "../../openzeppelin-presets/token/ERC721/utils/ERC721Holder.sol";
import "../../openzeppelin-presets/token/ERC1155/utils/ERC1155Holder.sol";
import "../../eip/ERC1271.sol";
// Utils
import "../../openzeppelin-presets/utils/cryptography/ECDSA.sol";
import "../utils/BaseAccountFactory.sol";
// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
contract Account is
Initializable,
ERC1271,
Multicall,
BaseAccount,
ContractMetadata,
AccountPermissions,
ERC721Holder,
ERC1155Holder
{
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;
/*///////////////////////////////////////////////////////////////
State
//////////////////////////////////////////////////////////////*/
/// @notice EIP 4337 factory for this contract.
address public immutable factory;
/// @notice EIP 4337 Entrypoint contract.
IEntryPoint private immutable entrypointContract;
/*///////////////////////////////////////////////////////////////
Constructor, Initializer, Modifiers
//////////////////////////////////////////////////////////////*/
// solhint-disable-next-line no-empty-blocks
receive() external payable virtual {}
constructor(IEntryPoint _entrypoint, address _factory) EIP712("Account", "1") {
_disableInitializers();
factory = _factory;
entrypointContract = _entrypoint;
}
/// @notice Initializes the smart contract wallet.
function initialize(address _defaultAdmin, bytes calldata) public virtual initializer {
_setAdmin(_defaultAdmin, true);
}
/// @notice Checks whether the caller is the EntryPoint contract or the admin.
modifier onlyAdminOrEntrypoint() virtual {
require(msg.sender == address(entrypointContract) || isAdmin(msg.sender), "Account: not admin or EntryPoint.");
_;
}
/*///////////////////////////////////////////////////////////////
View functions
//////////////////////////////////////////////////////////////*/
/// @notice See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Receiver) returns (bool) {
return
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}
/// @notice Returns the EIP 4337 entrypoint contract.
function entryPoint() public view virtual override returns (IEntryPoint) {
return entrypointContract;
}
/// @notice Returns the balance of the account in Entrypoint.
function getDeposit() public view virtual returns (uint256) {
return entryPoint().balanceOf(address(this));
}
/// @notice Returns whether a signer is authorized to perform transactions using the wallet.
function isValidSigner(address _signer, UserOperation calldata _userOp) public view virtual returns (bool) {
// We use the underlying storage instead of high level view functions to save gas.
AccountPermissionsStorage.Data storage data = AccountPermissionsStorage.accountPermissionsStorage();
// First, check if the signer is an admin.
if (data.isAdmin[_signer]) {
return true;
}
SignerPermissionsStatic memory permissions = data.signerPermissions[_signer];
// If not an admin, check if the signer is active.
if (
permissions.startTimestamp > block.timestamp ||
block.timestamp >= permissions.endTimestamp ||
data.approvedTargets[_signer].length() == 0
) {
// Account: no active permissions.
return false;
}
// Extract the function signature from the userOp calldata and check whether the signer is attempting to call `execute` or `executeBatch`.
bytes4 sig = getFunctionSignature(_userOp.callData);
if (sig == this.execute.selector) {
// Extract the `target` and `value` arguments from the calldata for `execute`.
(address target, uint256 value) = decodeExecuteCalldata(_userOp.callData);
// Check if the value is within the allowed range and if the target is approved.
if (permissions.nativeTokenLimitPerTransaction < value || !data.approvedTargets[_signer].contains(target)) {
// Account: value too high OR Account: target not approved.
return false;
}
} else if (sig == this.executeBatch.selector) {
// Extract the `target` and `value` array arguments from the calldata for `executeBatch`.
(address[] memory targets, uint256[] memory values, ) = decodeExecuteBatchCalldata(_userOp.callData);
// For each target+value pair, check if the value is within the allowed range and if the target is approved.
for (uint256 i = 0; i < targets.length; i++) {
if (
permissions.nativeTokenLimitPerTransaction < values[i] ||
!data.approvedTargets[_signer].contains(targets[i])
) {
// Account: value too high OR Account: target not approved.
return false;
}
}
} else {
// Account: calling invalid fn.
return false;
}
return true;
}
/// @notice See EIP-1271
function isValidSignature(bytes32 _hash, bytes memory _signature)
public
view
virtual
override
returns (bytes4 magicValue)
{
address signer = _hash.recover(_signature);
if (isAdmin(signer) || isActiveSigner(signer)) {
magicValue = MAGICVALUE;
}
}
/*///////////////////////////////////////////////////////////////
External functions
//////////////////////////////////////////////////////////////*/
/// @notice Executes a transaction (called directly from an admin, or by entryPoint)
function execute(
address _target,
uint256 _value,
bytes calldata _calldata
) external virtual onlyAdminOrEntrypoint {
_registerOnFactory();
_call(_target, _value, _calldata);
}
/// @notice Executes a sequence transaction (called directly from an admin, or by entryPoint)
function executeBatch(
address[] calldata _target,
uint256[] calldata _value,
bytes[] calldata _calldata
) external virtual onlyAdminOrEntrypoint {
_registerOnFactory();
require(_target.length == _calldata.length && _target.length == _value.length, "Account: wrong array lengths.");
for (uint256 i = 0; i < _target.length; i++) {
_call(_target[i], _value[i], _calldata[i]);
}
}
/// @notice Deposit funds for this account in Entrypoint.
function addDeposit() public payable virtual {
entryPoint().depositTo{ value: msg.value }(address(this));
}
/// @notice Withdraw funds for this account from Entrypoint.
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public virtual onlyAdmin {
entryPoint().withdrawTo(withdrawAddress, amount);
}
/*///////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/
/// @dev Registers the account on the factory if it hasn't been registered yet.
function _registerOnFactory() internal virtual {
BaseAccountFactory factoryContract = BaseAccountFactory(factory);
if (!factoryContract.isRegistered(address(this))) {
factoryContract.onRegister();
}
}
/// @dev Calls a target contract and reverts if it fails.
function _call(
address _target,
uint256 value,
bytes memory _calldata
) internal virtual returns (bytes memory result) {
bool success;
(success, result) = _target.call{ value: value }(_calldata);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
function getFunctionSignature(bytes calldata data) internal pure returns (bytes4 functionSelector) {
require(data.length >= 4, "Data too short");
return bytes4(data[:4]);
}
function decodeExecuteCalldata(bytes calldata data) internal pure returns (address _target, uint256 _value) {
require(data.length >= 4 + 32 + 32, "Data too short");
// Decode the address, which is bytes 4 to 35
_target = abi.decode(data[4:36], (address));
// Decode the value, which is bytes 36 to 68
_value = abi.decode(data[36:68], (uint256));
}
function decodeExecuteBatchCalldata(bytes calldata data)
internal
pure
returns (
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _callData
)
{
require(data.length >= 4 + 32 + 32 + 32, "Data too short");
(_targets, _values, _callData) = abi.decode(data[4:], (address[], uint256[], bytes[]));
}
/// @notice Validates the signature of a user operation.
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)
internal
virtual
override
returns (uint256 validationData)
{
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);
if (!isValidSigner(signer, userOp)) return SIG_VALIDATION_FAILED;
return 0;
}
/// @notice Makes the given account an admin.
function _setAdmin(address _account, bool _isAdmin) internal virtual override {
super._setAdmin(_account, _isAdmin);
if (factory.code.length > 0) {
if (_isAdmin) {
BaseAccountFactory(factory).onSignerAdded(_account);
} else {
BaseAccountFactory(factory).onSignerRemoved(_account);
}
}
}
/// @notice Runs after every `changeRole` run.
function _afterSignerPermissionsUpdate(SignerPermissionRequest calldata _req) internal virtual override {
if (factory.code.length > 0) {
BaseAccountFactory(factory).onSignerAdded(_req.signer);
}
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual override returns (bool) {
return isAdmin(msg.sender);
}
}