forked from bcnmy/scw-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
WalletFactoryFlat.sol
632 lines (518 loc) · 25.7 KB
/
WalletFactoryFlat.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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
// Sources flattened with hardhat v2.11.1 https://hardhat.org
// File contracts/smart-contract-wallet/Proxy.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.12;
/**
* @title Proxy // This is the user's wallet
* @notice Basic proxy that delegates all calls to a fixed implementing contract.
*/
contract Proxy {
/* This is the keccak-256 hash of "biconomy.scw.proxy.implementation" subtracted by 1, and is validated in the constructor */
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x37722d148fb373b961a84120b6c8d209709b45377878a466db32bbc40d95af26;
event Received(uint indexed value, address indexed sender, bytes data);
constructor(address _implementation) {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("biconomy.scw.proxy.implementation")) - 1));
assembly {
sstore(_IMPLEMENTATION_SLOT,_implementation)
}
}
fallback() external payable {
address target;
// solhint-disable-next-line no-inline-assembly
assembly {
target := sload(_IMPLEMENTATION_SLOT)
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {revert(0, returndatasize())}
default {return (0, returndatasize())}
}
}
receive() external payable {
emit Received(msg.value, msg.sender, "");
}
}
// File contracts/smart-contract-wallet/aa-4337/interfaces/UserOperation.sol
pragma solidity 0.8.12;
/* solhint-disable no-inline-assembly */
/**
* User Operation struct
* @param sender the sender account of this request
* @param nonce unique value the sender uses to verify it is not a replay.
* @param initCode if set, the account contract will be created by this constructor
* @param callData the method call to execute on this account.
* @param verificationGasLimit gas used for validateUserOp and validatePaymasterUserOp
* @param preVerificationGas gas not calculated by the handleOps method, but added to the gas paid. Covers batch overhead.
* @param maxFeePerGas same as EIP-1559 gas parameter
* @param maxPriorityFeePerGas same as EIP-1559 gas parameter
* @param paymasterAndData if set, this field hold the paymaster address and "paymaster-specific-data". the paymaster will pay for the transaction instead of the sender
* @param signature sender-verified signature over the entire request, the EntryPoint address and the chain ID.
*/
struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}
library UserOperationLib {
function getSender(UserOperation calldata userOp) internal pure returns (address) {
address data;
//read sender from userOp, which is first userOp member (saves 800 gas...)
assembly {data := calldataload(userOp)}
return address(uint160(data));
}
//relayer/miner might submit the TX with higher priorityFee, but the user should not
// pay above what he signed for.
function gasPrice(UserOperation calldata userOp) internal view returns (uint256) {
unchecked {
uint256 maxFeePerGas = userOp.maxFeePerGas;
uint256 maxPriorityFeePerGas = userOp.maxPriorityFeePerGas;
if (maxFeePerGas == maxPriorityFeePerGas) {
//legacy mode (for networks that don't support basefee opcode)
return maxFeePerGas;
}
return min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
}
}
function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) {
//lighter signature scheme. must match UserOp.ts#packUserOp
bytes calldata sig = userOp.signature;
// copy directly the userOp from calldata up to (but not including) the signature.
// this encoding depends on the ABI encoding of calldata, but is much lighter to copy
// than referencing each field separately.
assembly {
let ofs := userOp
let len := sub(sub(sig.offset, ofs), 32)
ret := mload(0x40)
mstore(0x40, add(ret, add(len, 32)))
mstore(ret, len)
calldatacopy(add(ret, 32), ofs, len)
}
}
function hash(UserOperation calldata userOp) internal pure returns (bytes32) {
return keccak256(pack(userOp));
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// File contracts/smart-contract-wallet/aa-4337/interfaces/IStakeManager.sol
pragma solidity 0.8.12;
/**
* manage deposits and stakes.
* deposit is just a balance used to pay for UserOperations (either by a paymaster or a wallet)
* stake is value locked for at least "unstakeDelay" by a paymaster.
*/
interface IStakeManager {
event Deposited(
address indexed account,
uint256 totalDeposit
);
event Withdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
/// Emitted once a stake is scheduled for withdrawal
event StakeLocked(
address indexed account,
uint256 totalStaked,
uint256 withdrawTime
);
/// Emitted once a stake is scheduled for withdrawal
event StakeUnlocked(
address indexed account,
uint256 withdrawTime
);
event StakeWithdrawn(
address indexed account,
address withdrawAddress,
uint256 amount
);
/**
* minimum time (in seconds) required to lock a paymaster stake before it can be withdraw.
*/
function unstakeDelaySec() external returns (uint32);
/**
* minimum value required to stake for a paymaster
*/
function paymasterStake() external returns (uint256);
/**
* @param deposit the account's deposit
* @param staked true if this account is staked as a paymaster
* @param stake actual amount of ether staked for this paymaster. must be above paymasterStake
* @param unstakeDelaySec minimum delay to withdraw the stake. must be above the global unstakeDelaySec
* @param withdrawTime - first block timestamp where 'withdrawStake' will be callable, or zero if already locked
* @dev sizes were chosen so that (deposit,staked) fit into one cell (used during handleOps)
* and the rest fit into a 2nd cell.
* 112 bit allows for 2^15 eth
* 64 bit for full timestamp
* 32 bit allow 150 years for unstake delay
*/
struct DepositInfo {
uint112 deposit;
bool staked;
uint112 stake;
uint32 unstakeDelaySec;
uint64 withdrawTime;
}
function getDepositInfo(address account) external view returns (DepositInfo memory info);
/// return the deposit (for gas payment) of the account
function balanceOf(address account) external view returns (uint256);
/**
* add to the deposit of the given account
*/
function depositTo(address account) external payable;
/**
* add to the account's stake - amount and delay
* any pending unstake is first cancelled.
* @param _unstakeDelaySec the new lock duration before the deposit can be withdrawn.
*/
function addStake(uint32 _unstakeDelaySec) external payable;
/**
* attempt to unlock the stake.
* the value can be withdrawn (using withdrawStake) after the unstake delay.
*/
function unlockStake() external;
/**
* withdraw from the (unlocked) stake.
* must first call unlockStake and wait for the unstakeDelay to pass
* @param withdrawAddress the address to send withdrawn value.
*/
function withdrawStake(address payable withdrawAddress) external;
/**
* withdraw from the deposit.
* @param withdrawAddress the address to send withdrawn value.
* @param withdrawAmount the amount to withdraw.
*/
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;
}
// File contracts/smart-contract-wallet/aa-4337/interfaces/IAggregator.sol
pragma solidity 0.8.12;
/**
* Aggregated Signatures validator.
*/
interface IAggregator {
/**
* validate aggregated signature.
* revert if the aggregated signature does not match the given list of operations.
*/
function validateSignatures(UserOperation[] calldata userOps, bytes calldata signature) external view;
/**
* validate signature of a single userOp
* This method is called by EntryPoint.simulateUserOperation() if the wallet has an aggregator.
* First it validates the signature over the userOp. then it return data to be used when creating the handleOps:
* @param userOp the userOperation received from the user.
* @param offChainSigCheck if true, don't check signature, and leave it for the Bundler to use an off-chain native library.
* @return sigForUserOp the value to put into the signature field of the userOp when calling handleOps.
* (usually empty, unless wallet and aggregator support some kind of "multisig"
* @return sigForAggregation the value to pass (for all wallets) to aggregateSignatures()
* @return offChainSigInfo in case offChainSigCheck is true, this value should be used by the off-chain signature code (e.g. it contains the sender's publickey)
*/
function validateUserOpSignature(UserOperation calldata userOp, bool offChainSigCheck)
external view returns (bytes memory sigForUserOp, bytes memory sigForAggregation, bytes memory offChainSigInfo);
/**
* aggregate multiple signatures into a single value.
* This method is called off-chain to calculate the signature to pass with handleOps()
* bundler MAY use optimized custom code perform this aggregation
* @param sigsForAggregation array of values returned by validateUserOpSignature() for each op
* @return aggregatesSignature the aggregated signature
*/
function aggregateSignatures(bytes[] calldata sigsForAggregation) external view returns (bytes memory aggregatesSignature);
}
// File contracts/smart-contract-wallet/aa-4337/interfaces/IEntryPoint.sol
/**
** Account-Abstraction (EIP-4337) singleton EntryPoint implementation.
** Only one instance required on each chain.
**/
pragma solidity 0.8.12;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */
interface IEntryPoint is IStakeManager {
/***
* An event emitted after each successful request
* @param requestId - unique identifier for the request (hash its entire content, except signature).
* @param sender - the account that generates this request.
* @param paymaster - if non-null, the paymaster that pays for this request.
* @param nonce - the nonce value from the request
* @param actualGasCost - the total cost (in gas) of this request.
* @param actualGasPrice - the actual gas price the sender agreed to pay.
* @param success - true if the sender transaction succeeded, false if reverted.
*/
event UserOperationEvent(bytes32 indexed requestId, address indexed sender, address indexed paymaster, uint256 nonce, uint256 actualGasCost, uint256 actualGasPrice, bool success);
/**
* An event emitted if the UserOperation "callData" reverted with non-zero length
* @param requestId the request unique identifier.
* @param sender the sender of this request
* @param nonce the nonce used in the request
* @param revertReason - the return bytes from the (reverted) call to "callData".
*/
event UserOperationRevertReason(bytes32 indexed requestId, address indexed sender, uint256 nonce, bytes revertReason);
/**
* a custom revert error of handleOps, to identify the offending op.
* NOTE: if simulateValidation passes successfully, there should be no reason for handleOps to fail on it.
* @param opIndex - index into the array of ops to the failed one (in simulateValidation, this is always zero)
* @param paymaster - if paymaster.validatePaymasterUserOp fails, this will be the paymaster's address. if validateUserOp failed,
* this value will be zero (since it failed before accessing the paymaster)
* @param reason - revert reason
* Should be caught in off-chain handleOps simulation and not happen on-chain.
* Useful for mitigating DoS attempts against batchers or for troubleshooting of wallet/paymaster reverts.
*/
error FailedOp(uint256 opIndex, address paymaster, string reason);
/**
* error case when a signature aggregator fails to verify the aggregated signature it had created.
*/
error SignatureValidationFailed(address aggregator);
//UserOps handled, per aggregator
struct UserOpsPerAggregator {
UserOperation[] userOps;
// aggregator address
IAggregator aggregator;
// aggregated signature
bytes signature;
}
/**
* Execute a batch of UserOperation.
* no signature aggregator is used.
* if any wallet requires an aggregator (that is, it returned an "actualAggregator" when
* performing simulateValidation), then handleAggregatedOps() must be used instead.
* @param ops the operations to execute
* @param beneficiary the address to receive the fees
*/
function handleOps(UserOperation[] calldata ops, address payable beneficiary) external;
/**
* Execute a batch of UserOperation with Aggregators
* @param opsPerAggregator the operations to execute, grouped by aggregator (or address(0) for no-aggregator wallets)
* @param beneficiary the address to receive the fees
*/
function handleAggregatedOps(
UserOpsPerAggregator[] calldata opsPerAggregator,
address payable beneficiary
) external;
/**
* generate a request Id - unique identifier for this request.
* the request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid.
*/
function getRequestId(UserOperation calldata userOp) external view returns (bytes32);
/**
* Simulate a call to wallet.validateUserOp and paymaster.validatePaymasterUserOp.
* Validation succeeds if the call doesn't revert.
* @dev The node must also verify it doesn't use banned opcodes, and that it doesn't reference storage outside the wallet's data.
* In order to split the running opcodes of the wallet (validateUserOp) from the paymaster's validatePaymasterUserOp,
* it should look for the NUMBER opcode at depth=1 (which itself is a banned opcode)
* @param userOp the user operation to validate.
* @param offChainSigCheck if the wallet has an aggregator, skip on-chain aggregation check. In thus case, the bundler must
* perform the equivalent check using an off-chain library code
* @return preOpGas total gas used by validation (including contract creation)
* @return prefund the amount the wallet had to prefund (zero in case a paymaster pays)
* @return actualAggregator the aggregator used by this userOp. if a non-zero aggregator is returned, the bundler must get its params using
* aggregator.
* @return sigForUserOp - only if has actualAggregator: this value is returned from IAggregator.validateUserOpSignature, and should be placed in the userOp.signature when creating a bundle.
* @return sigForAggregation - only if has actualAggregator: this value is returned from IAggregator.validateUserOpSignature, and should be passed to aggregator.aggregateSignatures
* @return offChainSigInfo - if has actualAggregator, and offChainSigCheck is true, this value should be used by the off-chain signature code (e.g. it contains the sender's publickey)
*/
function simulateValidation(UserOperation calldata userOp, bool offChainSigCheck)
external returns (uint256 preOpGas, uint256 prefund, address actualAggregator, bytes memory sigForUserOp, bytes memory sigForAggregation, bytes memory offChainSigInfo);
/**
* Get counterfactual sender address.
* Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation.
* must be called from zero-address.
* @param initCode the constructor code to be passed into the UserOperation.
*/
function getSenderAddress(bytes memory initCode) external returns (address);
/**
* return the storage cells used internally by the EntryPoint for this sender address.
* During `simulateValidation`, allow these storage cells to be accessed
* (that is, a wallet/paymaster are allowed to access their own deposit balance on the
* EntryPoint's storage, but no other account)
*/
function getSenderStorage(address sender) external view returns (uint256[] memory senderStorageCells);
}
// File contracts/smart-contract-wallet/aa-4337/interfaces/IWallet.sol
pragma solidity 0.8.12;
interface IWallet {
/**
* Validate user's signature and nonce
* the entryPoint will make the call to the recipient only if this validation call returns successfully.
*
* @dev Must validate caller is the entryPoint.
* Must validate the signature and nonce
* @param userOp the operation that is about to be executed.
* @param requestId hash of the user's request data. can be used as the basis for signature.
* @param aggregator the aggregator used to validate the signature. NULL for non-aggregated signature wallets.
* @param missingWalletFunds missing funds on the wallet's deposit in the entrypoint.
* This is the minimum amount to transfer to the sender(entryPoint) to be able to make the call.
* The excess is left as a deposit in the entrypoint, for future calls.
* can be withdrawn anytime using "entryPoint.withdrawTo()"
* In case there is a paymaster in the request (or the current deposit is high enough), this value will be zero.
*/
function validateUserOp(UserOperation calldata userOp, bytes32 requestId, address aggregator, uint256 missingWalletFunds) external;
}
// File contracts/smart-contract-wallet/common/Enum.sol
pragma solidity 0.8.12;
/// @title Enum - Collection of enums
contract Enum {
enum Operation {Call, DelegateCall}
}
// File contracts/smart-contract-wallet/BaseSmartWallet.sol
pragma solidity 0.8.12;
/* solhint-disable avoid-low-level-calls */
/* solhint-disable no-inline-assembly */
/* solhint-disable reason-string */
struct Transaction {
address to;
uint256 value;
bytes data;
Enum.Operation operation;
uint256 targetTxGas;
}
struct FeeRefund {
uint256 baseGas;
uint256 gasPrice; //gasPrice or tokenGasPrice
uint256 tokenGasPriceFactor;
address gasToken;
address payable refundReceiver;
}
/**
* this contract provides the basic logic for implementing the IWallet interface - validateUserOp
* specific wallet implementation should inherit it and provide the wallet-specific logic
*/
abstract contract BaseSmartWallet is IWallet {
using UserOperationLib for UserOperation;
/**
* return the wallet nonce.
* subclass should return a nonce value that is used both by _validateAndUpdateNonce, and by the external provider (to read the current nonce)
*/
function nonce() public view virtual returns (uint256);
/**
* return the entryPoint used by this wallet.
* subclass should return the current entryPoint used by this wallet.
*/
function entryPoint() public view virtual returns (IEntryPoint);
/**
* Validate user's signature and nonce.
*/
function validateUserOp(UserOperation calldata userOp, bytes32 requestId, address aggregator, uint256 missingWalletFunds) external override {
_requireFromEntryPoint();
_validateSignature(userOp, requestId, aggregator);
//during construction, the "nonce" field hold the salt.
// if we assert it is zero, then we allow only a single wallet per owner.
if (userOp.initCode.length == 0) {
_validateAndUpdateNonce(userOp);
}
_payPrefund(missingWalletFunds);
}
/**
* ensure the request comes from the known entrypoint.
*/
function _requireFromEntryPoint() internal virtual view {
require(msg.sender == address(entryPoint()), "wallet: not from EntryPoint");
}
/**
* validate the signature is valid for this message.
* @param userOp validate the userOp.signature field
* @param requestId convenient field: the hash of the request, to check the signature against
* (also hashes the entrypoint and chain-id)
* @param aggregator the current aggregator. can be ignored by wallets that don't use aggregators
*/
function _validateSignature(UserOperation calldata userOp, bytes32 requestId, address aggregator) internal virtual view;
/**
* validate the current nonce matches the UserOperation nonce.
* then it should update the wallet's state to prevent replay of this UserOperation.
* called only if initCode is empty (since "nonce" field is used as "salt" on wallet creation)
* @param userOp the op to validate.
*/
function _validateAndUpdateNonce(UserOperation calldata userOp) internal virtual;
/**
* sends to the entrypoint (msg.sender) the missing funds for this transaction.
* subclass MAY override this method for better funds management
* (e.g. send to the entryPoint more than the minimum required, so that in future transactions
* it will not be required to send again)
* @param missingWalletFunds the minimum value this method should send the entrypoint.
* this value MAY be zero, in case there is enough deposit, or the userOp has a paymaster.
*/
function _payPrefund(uint256 missingWalletFunds) internal virtual {
if (missingWalletFunds != 0) {
(bool success,) = payable(msg.sender).call{value : missingWalletFunds, gas : type(uint256).max}("");
(success);
//ignore failure (its EntryPoint's job to verify, not wallet.)
}
}
function init(address _owner, address _entryPointAddress, address _handler) external virtual;
function execTransaction(
Transaction memory _tx,
uint256 batchId,
FeeRefund memory refundInfo,
bytes memory signatures) public payable virtual returns (bool success);
}
// File contracts/smart-contract-wallet/WalletFactory.sol
pragma solidity 0.8.12;
contract WalletFactory {
address immutable public _defaultImpl;
// EOA + Version tracking
string public constant VERSION = "1.0.1";
//states : registry
mapping (address => bool) public isWalletExist;
constructor(address _baseImpl) {
require(_baseImpl != address(0), "base wallet address can not be zero");
_defaultImpl = _baseImpl;
}
// event WalletCreated(address indexed _proxy, address indexed _implementation, address indexed _owner);
// EOA + Version tracking
event WalletCreated(address indexed _proxy, address indexed _implementation, address indexed _owner, string version, uint256 _index);
/**
* @notice Deploys wallet using create2 and points it to _defaultImpl
* @param _owner EOA signatory of the wallet
* @param _entryPoint AA 4337 entry point address
* @param _handler fallback handler address
* @param _index extra salt that allows to deploy more wallets if needed for same EOA (default 0)
*/
function deployCounterFactualWallet(address _owner, address _entryPoint, address _handler, uint _index) public returns(address proxy){
bytes32 salt = keccak256(abi.encodePacked(_owner, address(uint160(_index))));
bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
// solhint-disable-next-line no-inline-assembly
assembly {
proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
}
require(address(proxy) != address(0), "Create2 call failed");
// EOA + Version tracking
emit WalletCreated(proxy,_defaultImpl,_owner, VERSION, _index);
BaseSmartWallet(proxy).init(_owner, _entryPoint, _handler);
isWalletExist[proxy] = true;
}
/**
* @notice Deploys wallet using create and points it to _defaultImpl
* @param _owner EOA signatory of the wallet
* @param _entryPoint AA 4337 entry point address
* @param _handler fallback handler address
*/
function deployWallet(address _owner, address _entryPoint, address _handler) public returns(address proxy){
bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
// solhint-disable-next-line no-inline-assembly
assembly {
proxy := create(0x0, add(0x20, deploymentData), mload(deploymentData))
}
BaseSmartWallet(proxy).init(_owner, _entryPoint, _handler);
isWalletExist[proxy] = true;
}
/**
* @notice Allows to find out wallet address prior to deployment
* @param _owner EOA signatory of the wallet
* @param _index extra salt that allows to deploy more wallets if needed for same EOA (default 0)
*/
function getAddressForCounterfactualWallet(address _owner, uint _index) external view returns (address _wallet) {
bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
bytes32 salt = keccak256(abi.encodePacked(_owner, address(uint160(_index))));
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(code)));
_wallet = address(uint160(uint(hash)));
}
}