diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2ef0af86..ea6647f21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: - run: yarn compile - run: FORCE_COLOR=1 yarn coverage - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: solidity-coverage path: | diff --git a/contracts/core/EntryPoint.sol b/contracts/core/EntryPoint.sol index 44501524d..7d76303c5 100644 --- a/contracts/core/EntryPoint.sol +++ b/contracts/core/EntryPoint.sol @@ -30,7 +30,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, SenderCreator private immutable _senderCreator = new SenderCreator(); - function senderCreator() internal view virtual returns (SenderCreator) { + function senderCreator() public view virtual returns (ISenderCreator) { return _senderCreator; } diff --git a/contracts/core/EntryPointSimulations.sol b/contracts/core/EntryPointSimulations.sol index b8c41ea12..e0ddcaefc 100644 --- a/contracts/core/EntryPointSimulations.sol +++ b/contracts/core/EntryPointSimulations.sol @@ -24,7 +24,7 @@ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { _senderCreator = SenderCreator(createdObj); } - function senderCreator() internal view virtual override returns (SenderCreator) { + function senderCreator() public view virtual override(EntryPoint, IEntryPoint) returns (ISenderCreator) { // return the same senderCreator as real EntryPoint. // this call is slightly (100) more expensive than EntryPoint's access to immutable member return _senderCreator; @@ -35,7 +35,7 @@ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { * it as entrypoint, since the simulation functions don't check the signatures */ constructor() { - require(block.number < 100, "should not be deployed"); + require(block.number < 1000, "should not be deployed"); } /// @inheritdoc IEntryPointSimulations diff --git a/contracts/core/SenderCreator.sol b/contracts/core/SenderCreator.sol index 43ea80367..539760610 100644 --- a/contracts/core/SenderCreator.sol +++ b/contracts/core/SenderCreator.sol @@ -1,11 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; +import "../interfaces/ISenderCreator.sol"; + /** * Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address, * which is explicitly not the entryPoint itself. */ -contract SenderCreator { +contract SenderCreator is ISenderCreator { + address public immutable entryPoint; + + constructor(){ + entryPoint = msg.sender; + } + /** * Call the "initCode" factory to create and return the sender account address. * @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address, @@ -15,6 +23,9 @@ contract SenderCreator { function createSender( bytes calldata initCode ) external returns (address sender) { + if (msg.sender != entryPoint) { + revert("AA97 should call from EntryPoint"); + } address factory = address(bytes20(initCode[0:20])); bytes memory initCallData = initCode[20:]; bool success; diff --git a/contracts/interfaces/IEntryPoint.sol b/contracts/interfaces/IEntryPoint.sol index 28c26f98e..1f56879a1 100644 --- a/contracts/interfaces/IEntryPoint.sol +++ b/contracts/interfaces/IEntryPoint.sol @@ -13,6 +13,7 @@ import "./PackedUserOperation.sol"; import "./IStakeManager.sol"; import "./IAggregator.sol"; import "./INonceManager.sol"; +import "./ISenderCreator.sol"; interface IEntryPoint is IStakeManager, INonceManager { /*** @@ -220,4 +221,9 @@ interface IEntryPoint is IStakeManager, INonceManager { * @param data data to pass to target in a delegatecall */ function delegateAndRevert(address target, bytes calldata data) external; + + /** + * @notice Retrieves the immutable SenderCreator contract which is responsible for deployment of sender contracts. + */ + function senderCreator() external view returns (ISenderCreator); } diff --git a/contracts/interfaces/ISenderCreator.sol b/contracts/interfaces/ISenderCreator.sol new file mode 100644 index 000000000..bd56051e7 --- /dev/null +++ b/contracts/interfaces/ISenderCreator.sol @@ -0,0 +1,11 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ISenderCreator { + /** + * @dev Creates a new sender contract. + * @return sender Address of the newly created sender contract. + */ + function createSender(bytes calldata initCode) external returns (address sender); +} diff --git a/contracts/samples/SimpleAccountFactory.sol b/contracts/samples/SimpleAccountFactory.sol index 5afa2ce60..6b5c80f4d 100644 --- a/contracts/samples/SimpleAccountFactory.sol +++ b/contracts/samples/SimpleAccountFactory.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.23; import "@openzeppelin/contracts/utils/Create2.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../interfaces/ISenderCreator.sol"; import "./SimpleAccount.sol"; /** @@ -14,9 +15,11 @@ import "./SimpleAccount.sol"; */ contract SimpleAccountFactory { SimpleAccount public immutable accountImplementation; + ISenderCreator public immutable senderCreator; constructor(IEntryPoint _entryPoint) { accountImplementation = new SimpleAccount(_entryPoint); + senderCreator = _entryPoint.senderCreator(); } /** @@ -26,6 +29,7 @@ contract SimpleAccountFactory { * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation */ function createAccount(address owner,uint256 salt) public returns (SimpleAccount ret) { + require(msg.sender == address(senderCreator), "only callable from SenderCreator"); address addr = getAddress(owner, salt); uint256 codeSize = addr.code.length; if (codeSize > 0) { diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index 45a21ff4e..6221e0b1b 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -93,6 +93,7 @@ export class GasChecker { accountOwner: Wallet accountInterface: SimpleAccountInterface + private locked: boolean constructor () { this.accountOwner = createAccountOwner() @@ -139,24 +140,26 @@ export class GasChecker { const addr = await fact.getAddress(this.accountOwner.address, salt) if (!this.createdAccounts.has(addr)) { - // explicit call to fillUseROp with no "entryPoint", to make sure we manually fill everything and - // not attempt to fill from blockchain. - const op = signUserOp(await fillUserOp({ - sender: addr, - nonce: 0, - callGasLimit: 30000, - verificationGasLimit: 1000000, - // paymasterAndData: paymaster, - preVerificationGas: 1, - maxFeePerGas: 0 - }), this.accountOwner, this.entryPoint().address, await provider.getNetwork().then(net => net.chainId)) - creationOps.push(packUserOp(op)) + const codeSize = await provider.getCode(addr).then(code => code.length) + if (codeSize === 2) { + // explicit call to fillUseROp with no "entryPoint", to make sure we manually fill everything and + // not attempt to fill from blockchain. + const op = signUserOp(await fillUserOp({ + sender: addr, + initCode: this.accountInitCode(fact, salt), + nonce: 0, + callGasLimit: 30000, + verificationGasLimit: 1000000, + // paymasterAndData: paymaster, + preVerificationGas: 1, + maxFeePerGas: 0 + }), this.accountOwner, this.entryPoint().address, await provider.getNetwork().then(net => net.chainId)) + creationOps.push(packUserOp(op)) + } this.createdAccounts.add(addr) } this.accounts[addr] = this.accountOwner - // deploy if not already deployed. - await fact.createAccount(this.accountOwner.address, salt) const accountBalance = await GasCheckCollector.inst.entryPoint.balanceOf(addr) if (accountBalance.lte(minDepositOrBalance)) { await GasCheckCollector.inst.entryPoint.depositTo(addr, { value: minDepositOrBalance.mul(5) }) @@ -224,21 +227,30 @@ export class GasChecker { } // console.debug('== account est=', accountEst.toString()) accountEst = est.accountEst - const op = await fillSignAndPack({ - sender: account, - callData: accountExecFromEntryPoint, - maxPriorityFeePerGas: info.gasPrice, - maxFeePerGas: info.gasPrice, - callGasLimit: accountEst, - verificationGasLimit: 1000000, - paymaster: paymaster, - paymasterVerificationGasLimit: 50000, - paymasterPostOpGasLimit: 50000, - preVerificationGas: 1 - }, accountOwner, GasCheckCollector.inst.entryPoint) - // const packed = packUserOp(op, false) - // console.log('== packed cost=', callDataCost(packed), packed) - return op + while (this.locked) { + await new Promise(resolve => setTimeout(resolve, 1)) + } + try { + this.locked = true + + const op = await fillSignAndPack({ + sender: account, + callData: accountExecFromEntryPoint, + maxPriorityFeePerGas: info.gasPrice, + maxFeePerGas: info.gasPrice, + callGasLimit: accountEst, + verificationGasLimit: 1000000, + paymaster: paymaster, + paymasterVerificationGasLimit: 50000, + paymasterPostOpGasLimit: 50000, + preVerificationGas: 1 + }, accountOwner, GasCheckCollector.inst.entryPoint) + // const packed = packUserOp(op, false) + // console.log('== packed cost=', callDataCost(packed), packed) + return op + } finally { + this.locked = false + } })) const txdata = GasCheckCollector.inst.entryPoint.interface.encodeFunctionData('handleOps', [userOps, info.beneficiary]) @@ -254,6 +266,13 @@ export class GasChecker { throw e }) const ret = await GasCheckCollector.inst.entryPoint.handleOps(userOps, info.beneficiary, { gasLimit: gasEst.mul(3).div(2) }) + // "ret.wait()" is dead slow without it... + for (let count = 0; count < 100; count++) { + if (await provider.getTransactionReceipt(ret.hash) != null) { + break + } + await new Promise(resolve => setTimeout(resolve, 10)) + } const rcpt = await ret.wait() const gasUsed = rcpt.gasUsed.toNumber() const countSuccessOps = rcpt.events?.filter(e => e.event === 'UserOperationEvent' && e.args?.success).length diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index 692322bf7..958390df5 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -12,44 +12,44 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 79994 │ │ ║ +║ simple │ 1 │ 80016 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ simple - diff from previous │ 2 │ │ 42192 │ 13213 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 459921 │ │ ║ +║ simple │ 10 │ 459919 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 42223 │ 13244 ║ +║ simple - diff from previous │ 11 │ │ 42235 │ 13256 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 86113 │ │ ║ +║ simple paymaster │ 1 │ 86159 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ ║ simple paymaster with diff │ 2 │ │ 41024 │ 12045 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 455444 │ │ ║ +║ simple paymaster │ 10 │ 455670 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 41088 │ 12109 ║ +║ simple paymaster with diff │ 11 │ │ 41112 │ 12133 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 181026 │ │ ║ +║ big tx 5k │ 1 │ 181060 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 142714 │ 17490 ║ +║ big tx - diff from previous │ 2 │ │ 142702 │ 17478 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1465443 │ │ ║ +║ big tx 5k │ 10 │ 1465489 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 142686 │ 17462 ║ +║ big tx - diff from previous │ 11 │ │ 142710 │ 17486 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp │ 1 │ 87712 │ │ ║ +║ paymaster+postOp │ 1 │ 87770 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp with diff │ 2 │ │ 42671 │ 13692 ║ +║ paymaster+postOp with diff │ 2 │ │ 42659 │ 13680 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp │ 10 │ 471754 │ │ ║ +║ paymaster+postOp │ 10 │ 471836 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ paymaster+postOp with diff │ 11 │ │ 42728 │ 13749 ║ +║ paymaster+postOp with diff │ 11 │ │ 42692 │ 13713 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 1 │ 128777 │ │ ║ +║ token paymaster │ 1 │ 128821 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 2 │ │ 66386 │ 37407 ║ +║ token paymaster with diff │ 2 │ │ 66408 │ 37429 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster │ 10 │ 726504 │ │ ║ +║ token paymaster │ 10 │ 726686 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ token paymaster with diff │ 11 │ │ 66394 │ 37415 ║ +║ token paymaster with diff │ 11 │ │ 66524 │ 37545 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ diff --git a/scripts/docker-gascalc b/scripts/docker-gascalc index 05c0eb274..adfe4b247 100755 --- a/scripts/docker-gascalc +++ b/scripts/docker-gascalc @@ -1,7 +1,7 @@ # run "yarn gas-calc" using geth with docker. # (if you have geth running on localhost:8545, its faster with "HARDHAT_NETWORK=dev yarn gas-calc") -docker-compose -f `dirname $0`/docker-gascalc.yml up --abort-on-container-exit +docker compose -f `dirname $0`/docker-gascalc.yml up --abort-on-container-exit exit=$? -docker-compose -f `dirname $0`/docker-gascalc.yml down +docker compose -f `dirname $0`/docker-gascalc.yml down exit $exit diff --git a/test/UserOp.ts b/test/UserOp.ts index 2d8ddd5b2..9720f201c 100644 --- a/test/UserOp.ts +++ b/test/UserOp.ts @@ -155,8 +155,9 @@ export async function fillUserOp (op: Partial, entryPoint?: Entry } if (op1.verificationGasLimit == null) { if (provider == null) throw new Error('no entrypoint/provider') + const senderCreator = await entryPoint?.senderCreator() const initEstimate = await provider.estimateGas({ - from: entryPoint?.address, + from: senderCreator, to: initAddr, data: initCallData, gasLimit: 10e6 diff --git a/test/entrypoint.test.ts b/test/entrypoint.test.ts index 4a934c447..c1dcc732b 100644 --- a/test/entrypoint.test.ts +++ b/test/entrypoint.test.ts @@ -2,10 +2,18 @@ import './aa.init' import { BigNumber, Event, Wallet } from 'ethers' import { expect } from 'chai' import { + EntryPoint, + IEntryPoint__factory, + INonceManager__factory, + IStakeManager__factory, + MaliciousAccount__factory, + SenderCreator__factory, SimpleAccount, SimpleAccountFactory, - TestAggregatedAccount__factory, + SimpleAccountFactory__factory, + TestAggregatedAccount, TestAggregatedAccountFactory__factory, + TestAggregatedAccount__factory, TestCounter, TestCounter__factory, TestExpirePaymaster, @@ -14,20 +22,13 @@ import { TestExpiryAccount__factory, TestPaymasterAcceptAll, TestPaymasterAcceptAll__factory, + TestPaymasterRevertCustomError__factory, + TestPaymasterWithPostOp, + TestPaymasterWithPostOp__factory, TestRevertAccount__factory, - TestAggregatedAccount, TestSignatureAggregator, TestSignatureAggregator__factory, - MaliciousAccount__factory, - TestWarmColdAccount__factory, - TestPaymasterRevertCustomError__factory, - IEntryPoint__factory, - SimpleAccountFactory__factory, - IStakeManager__factory, - INonceManager__factory, - EntryPoint, - TestPaymasterWithPostOp__factory, - TestPaymasterWithPostOp + TestWarmColdAccount__factory } from '../typechain' import { AddressZero, @@ -834,6 +835,14 @@ describe('EntryPoint', function () { let createOp: PackedUserOperation const beneficiaryAddress = createAddress() // 1 + it('should reject create if SenderCreator not called from EntryPoint', async () => { + const senderCreatorAddress = await entryPoint.senderCreator() + const senderCreator = SenderCreator__factory.connect(senderCreatorAddress, ethersSigner) + await expect( + senderCreator.createSender('0xdeadbeef', { gasLimit: 1000000 }) + ).to.be.revertedWith('AA97 should call from EntryPoint') + }) + it('should reject create if sender address is wrong', async () => { const op = await fillSignAndPack({ initCode: getAccountInitCode(accountOwner.address, simpleAccountFactory), diff --git a/test/entrypointsimulations.test.ts b/test/entrypointsimulations.test.ts index fe3205256..12f1af1c4 100644 --- a/test/entrypointsimulations.test.ts +++ b/test/entrypointsimulations.test.ts @@ -235,6 +235,7 @@ describe('EntryPointSimulations', function () { const { proxy: account } = await createAccount(ethersSigner, await accountOwner.getAddress(), entryPoint.address) const sender = createAddress() const op1 = await fillSignAndPack({ + verificationGasLimit: 150000, // providing default value as gas estimation will fail initCode: hexConcat([ account.address, account.interface.encodeFunctionData('execute', [sender, 0, '0x']) diff --git a/test/simple-wallet.test.ts b/test/simple-wallet.test.ts index 844bd37e8..91e6e3711 100644 --- a/test/simple-wallet.test.ts +++ b/test/simple-wallet.test.ts @@ -1,8 +1,11 @@ import { Wallet } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' +import { toHex } from 'hardhat/internal/util/bigint' + import { ERC1967Proxy__factory, + EntryPoint, SimpleAccount, SimpleAccountFactory__factory, SimpleAccount__factory, @@ -12,27 +15,29 @@ import { TestUtil__factory } from '../typechain' import { + HashZero, + ONE_ETH, createAccount, - createAddress, createAccountOwner, + createAddress, + deployEntryPoint, getBalance, - isDeployed, - ONE_ETH, - HashZero, deployEntryPoint + isDeployed } from './testutils' import { fillUserOpDefaults, getUserOpHash, encodeUserOp, signUserOp, packUserOp } from './UserOp' import { parseEther } from 'ethers/lib/utils' import { UserOperation } from './UserOperation' +import { JsonRpcProvider } from '@ethersproject/providers' describe('SimpleAccount', function () { - let entryPoint: string + let entryPoint: EntryPoint let accounts: string[] let testUtil: TestUtil let accountOwner: Wallet const ethersSigner = ethers.provider.getSigner() before(async function () { - entryPoint = await deployEntryPoint().then(e => e.address) + entryPoint = await deployEntryPoint() accounts = await ethers.provider.listAccounts() // ignore in geth.. this is just a sanity test. should be refactored to use a single-account mode.. if (accounts.length < 2) this.skip() @@ -41,12 +46,12 @@ describe('SimpleAccount', function () { }) it('owner should be able to call transfer', async () => { - const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint) + const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint.address) await ethersSigner.sendTransaction({ from: accounts[0], to: account.address, value: parseEther('2') }) await account.execute(accounts[2], ONE_ETH, '0x') }) it('other account should not be able to call transfer', async () => { - const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint) + const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint.address) await expect(account.connect(ethers.provider.getSigner(1)).execute(accounts[2], ONE_ETH, '0x')) .to.be.revertedWith('account: not Owner or EntryPoint') }) @@ -62,7 +67,7 @@ describe('SimpleAccount', function () { let account: SimpleAccount let counter: TestCounter before(async () => { - ({ proxy: account } = await createAccount(ethersSigner, await ethersSigner.getAddress(), entryPoint)) + ({ proxy: account } = await createAccount(ethersSigner, await ethersSigner.getAddress(), entryPoint.address)) counter = await new TestCounter__factory(ethersSigner).deploy() }) @@ -155,9 +160,18 @@ describe('SimpleAccount', function () { }) context('SimpleAccountFactory', () => { - it('sanity: check deployer', async () => { + it('should reject calls coming from any address that is not SenderCreator', async () => { const ownerAddr = createAddress() - const deployer = await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint) + let deployer = await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint.address) + await expect(deployer.createAccount(ownerAddr, 1234)) + .to.be.revertedWith('only callable from SenderCreator') + + // switch deployer contract to an impersonating signer + const senderCreator = await entryPoint.senderCreator() + await (ethersSigner.provider as JsonRpcProvider).send('hardhat_setBalance', [senderCreator, toHex(100e18)]) + const senderCreatorSigner = await ethers.getImpersonatedSigner(senderCreator) + deployer = deployer.connect(senderCreatorSigner) + const target = await deployer.callStatic.createAccount(ownerAddr, 1234) expect(await isDeployed(target)).to.eq(false) await deployer.createAccount(ownerAddr, 1234) diff --git a/test/testutils.ts b/test/testutils.ts index 3e18f7848..72ede6525 100644 --- a/test/testutils.ts +++ b/test/testutils.ts @@ -1,7 +1,9 @@ import { ethers } from 'hardhat' +import { toHex } from 'hardhat/internal/util/bigint' import { arrayify, - hexConcat, hexDataSlice, + hexConcat, + hexDataSlice, hexlify, hexZeroPad, Interface, @@ -20,6 +22,7 @@ import { TestAggregatedAccountFactory, TestPaymasterRevertCustomError__factory, TestERC20__factory } from '../typechain' import { BytesLike, Hexable } from '@ethersproject/bytes' +import { JsonRpcProvider } from '@ethersproject/providers' import { expect } from 'chai' import { Create2Factory } from '../src/Create2Factory' import { debugTransaction } from './debugTx' @@ -223,8 +226,8 @@ export async function checkForGeth (): Promise { // --allow-insecure-unlock if (currentNode.match(/geth/i) != null) { for (let i = 0; i < 2; i++) { - const acc = await provider.request({ method: 'personal_newAccount', params: ['pass'] }).catch(rethrow) - await provider.request({ method: 'personal_unlockAccount', params: [acc, 'pass'] }).catch(rethrow) + const acc = await provider.request({ method: 'personal_newAccount', params: ['pass'] }).catch(rethrow()) + await provider.request({ method: 'personal_unlockAccount', params: [acc, 'pass'] }).catch(rethrow()) await fund(acc, '10') } } @@ -298,7 +301,11 @@ export async function createAccount ( }> { const accountFactory = _factory ?? await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint) const implementation = await accountFactory.accountImplementation() - await accountFactory.createAccount(accountOwner, 0) + const entryPointContract = EntryPoint__factory.connect(entryPoint, ethersSigner) + const senderCreator = await entryPointContract.senderCreator() + await (ethersSigner.provider as JsonRpcProvider).send('hardhat_setBalance', [senderCreator, toHex(100e18)]) + const senderCreatorSigner = await ethers.getImpersonatedSigner(senderCreator) + await accountFactory.connect(senderCreatorSigner).createAccount(accountOwner, 0) const accountAddress = await accountFactory.getAddress(accountOwner, 0) const proxy = SimpleAccount__factory.connect(accountAddress, ethersSigner) return {