diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index 0af7a1ff9..4689b96fa 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -8,6 +8,11 @@ import {Math} from "../../utils/math/Math.sol"; import {Calldata} from "../../utils/Calldata.sol"; import {Packing} from "../../utils/Packing.sol"; +/// @dev This is available on all entrypoint since v0.4.0, but is not formally part of the ERC. +interface IEntryPointExtra { + function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32); +} + /** * @dev Library with common ERC-4337 utility functions. * @@ -19,6 +24,9 @@ library ERC4337Utils { /// @dev Address of the entrypoint v0.7.0 IEntryPoint internal constant ENTRYPOINT_V07 = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032); + /// @dev Address of the entrypoint v0.8.0 + IEntryPoint internal constant ENTRYPOINT_V08 = IEntryPoint(0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108); + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. uint256 internal constant SIG_VALIDATION_SUCCESS = 0; @@ -77,31 +85,16 @@ library ERC4337Utils { return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); } - /// @dev Computes the hash of a user operation for a given entrypoint and chainid. - function hash( - PackedUserOperation calldata self, - address entrypoint, - uint256 chainid - ) internal pure returns (bytes32) { - bytes32 result = keccak256( - abi.encode( - keccak256( - abi.encode( - self.sender, - self.nonce, - keccak256(self.initCode), - keccak256(self.callData), - self.accountGasLimits, - self.preVerificationGas, - self.gasFees, - keccak256(self.paymasterAndData) - ) - ), - entrypoint, - chainid - ) - ); - return result; + /// @dev Get the hash of a user operation for a given entrypoint + function hash(PackedUserOperation calldata self, address entrypoint) internal view returns (bytes32) { + // NOTE: getUserOpHash is available since v0.4.0 + // + // Prior to v0.8.0, this was easy to replicate for any entrypoint and chainId. Since v0.8.0 of the + // entrypoint, this depends on the Entrypoint's domain separator, which cannot be hardcoded and is complex + // to recompute. Domain separator could be fetch using the `getDomainSeparatorV4` getter, or recomputed from + // the ERC-5267 getter, but both operation would require doing a view call to the entrypoint. Overall it feels + // simpler and less error prone to get that functionality from the entrypoint directly. + return IEntryPointExtra(entrypoint).getUserOpHash(self); } /// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted. diff --git a/hardhat/common-contracts.js b/hardhat/common-contracts.js index 33248ec91..fd4ef1dba 100644 --- a/hardhat/common-contracts.js +++ b/hardhat/common-contracts.js @@ -6,41 +6,58 @@ const fs = require('fs'); const path = require('path'); const INSTANCES = { - // Entrypoint v0.7.0 + // ERC-4337 Entrypoints entrypoint: { - address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', - abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.abi'), 'utf-8')), - bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.bytecode'), 'hex'), + v07: { + address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.bytecode'), 'hex'), + }, + v08: { + address: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint080.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint080.bytecode'), 'hex'), + }, }, senderCreator: { - address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', - abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.abi'), 'utf-8')), - bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.bytecode'), 'hex'), + v07: { + address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.bytecode'), 'hex'), + }, + v08: { + address: '0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator080.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator080.bytecode'), 'hex'), + }, }, - // Arachnid's deterministic deployment proxy - // See: https://github.com/Arachnid/deterministic-deployment-proxy/tree/master - arachnidDeployer: { - address: '0x4e59b44847b379578588920cA78FbF26c0B4956C', - abi: [], - bytecode: - '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3', - }, - // Micah's deployer - micahDeployer: { - address: '0x7A0D94F55792C434d74a40883C6ed8545E406D12', - abi: [], - bytecode: '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3', + deployer: { + // Arachnid's deterministic deployment proxy + // See: https://github.com/Arachnid/deterministic-deployment-proxy/tree/master + arachnid: { + address: '0x4e59b44847b379578588920cA78FbF26c0B4956C', + abi: [], + bytecode: + '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3', + }, + // Micah's deployer + micah: { + address: '0x7A0D94F55792C434d74a40883C6ed8545E406D12', + abi: [], + bytecode: '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3', + }, }, }; +const setup = (input, ethers) => + input.address && input.abi && input.bytecode + ? setCode(input.address, '0x' + input.bytecode.replace(/0x/, '')).then(() => + ethers.getContractAt(input.abi, input.address), + ) + : Promise.all( + Object.entries(input).map(([name, entry]) => setup(entry, ethers).then(result => [name, result])), + ).then(Object.fromEntries); + task(TASK_TEST_SETUP_TEST_ENVIRONMENT).setAction((_, env, runSuper) => - runSuper().then(() => - Promise.all( - Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) => - setCode(address, '0x' + bytecode.replace(/0x/, '')).then(() => - env.ethers.getContractAt(abi, address).then(instance => (env[name] = instance)), - ), - ), - ), - ), + runSuper().then(() => setup(INSTANCES, env.ethers).then(result => Object.assign(env, result))), ); diff --git a/scripts/fetch-common-contracts.js b/scripts/fetch-common-contracts.js new file mode 100755 index 000000000..af904243b --- /dev/null +++ b/scripts/fetch-common-contracts.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +// This script snapshots the bytecode and ABI for the `hardhat/common-contracts.js` script. +// - Bytecode is fetched directly from the blockchain by querying the provided client endpoint. If no endpoint is +// provided, ethers default provider is used instead. +// - ABI is fetched from etherscan's API using the provided etherscan API key. If no API key is provided, ABI will not +// be fetched and saved. +// +// The produced artifacts are stored in the `output` folder ('test/bin' by default). For each contract, two files are +// produced: +// - `.bytecode` containing the contract bytecode (in binary encoding) +// - `.abi` containing the ABI (in utf-8 encoding) + +const fs = require('fs'); +const path = require('path'); +const { ethers } = require('ethers'); +const { request } = require('undici'); +const { hideBin } = require('yargs/helpers'); +const { argv } = require('yargs/yargs')(hideBin(process.argv)) + .env('') + .options({ + output: { type: 'string', default: 'test/bin/' }, + client: { type: 'string' }, + etherscan: { type: 'string' }, + }); + +// List of contract names and addresses to fetch +const config = { + EntryPoint070: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + SenderCreator070: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + EntryPoint080: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + SenderCreator080: '0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33', +}; + +Promise.all( + Object.entries(config).flatMap(([name, addr]) => + Promise.all([ + argv.etherscan && + request(`https://api.etherscan.io/api?module=contract&action=getabi&address=${addr}&apikey=${argv.etherscan}`) + .then(({ body }) => body.json()) + .then(({ result: abi }) => fs.writeFile(path.join(argv.output, `${name}.abi`), abi, 'utf-8', () => {})), + ethers + .getDefaultProvider(argv.client) + .getCode(addr) + .then(bytecode => + fs.writeFile(path.join(argv.output, `${name}.bytecode`), ethers.getBytes(bytecode), 'binary', () => {}), + ), + ]), + ), +); diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index d3523477a..ab4f345d2 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -22,7 +22,11 @@ describe('ERC4337Utils', function () { describe('entrypoint', function () { it('v0.7.0', async function () { - await expect(this.utils.$ENTRYPOINT_V07()).to.eventually.equal(entrypoint); + await expect(this.utils.$ENTRYPOINT_V07()).to.eventually.equal(entrypoint.v07); + }); + + it('v0.8.0', async function () { + await expect(this.utils.$ENTRYPOINT_V08()).to.eventually.equal(entrypoint.v08); }); }); @@ -172,22 +176,14 @@ describe('ERC4337Utils', function () { }); describe('hash', function () { - it('returns the operation hash with specified entrypoint and chainId', async function () { - const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); - const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); - const otherChainId = 0xdeadbeef; + for (const [version, instance] of Object.entries(entrypoint)) { + it(`returns the operation hash for entrypoint ${version}`, async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const expected = await userOp.hash(instance); - // check that helper matches entrypoint logic - await expect(entrypoint.getUserOpHash(userOp.packed)).to.eventually.equal(userOp.hash(entrypoint, chainId)); - - // check library against helper - await expect(this.utils.$hash(userOp.packed, entrypoint, chainId)).to.eventually.equal( - userOp.hash(entrypoint, chainId), - ); - await expect(this.utils.$hash(userOp.packed, entrypoint, otherChainId)).to.eventually.equal( - userOp.hash(entrypoint, otherChainId), - ); - }); + await expect(this.utils.$hash(userOp.packed, instance)).to.eventually.equal(expected); + }); + } }); describe('userOp values', function () { diff --git a/test/account/utils/draft-ERC7579Utils.t.sol b/test/account/utils/draft-ERC7579Utils.t.sol index dfeb21765..bc69e4c34 100644 --- a/test/account/utils/draft-ERC7579Utils.t.sol +++ b/test/account/utils/draft-ERC7579Utils.t.sol @@ -375,7 +375,7 @@ contract ERC7579UtilsTest is Test { } function hashUserOperation(PackedUserOperation calldata useroperation) public view returns (bytes32) { - return useroperation.hash(address(ERC4337Utils.ENTRYPOINT_V07), block.chainid); + return useroperation.hash(address(ERC4337Utils.ENTRYPOINT_V07)); } function _collectAndPrintLogs(bool includeTotalValue) internal { diff --git a/test/bin/EntryPoint080.abi b/test/bin/EntryPoint080.abi new file mode 100644 index 000000000..22fd32c46 --- /dev/null +++ b/test/bin/EntryPoint080.abi @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"name":"DelegateAndRevert","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"name":"FailedOp","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"},{"internalType":"bytes","name":"inner","type":"bytes"}],"name":"FailedOpWithRevert","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"PostOpReverted","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderAddressResult","type":"error"},{"inputs":[{"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureValidationFailed","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"factory","type":"address"},{"indexed":false,"internalType":"address","name":"paymaster","type":"address"}],"name":"AccountDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"BeforeExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalDeposit","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"PostOpRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureAggregatorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalStaked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeDelaySec","type":"uint256"}],"name":"StakeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawTime","type":"uint256"}],"name":"StakeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"paymaster","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint256","name":"actualGasCost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"actualGasUsed","type":"uint256"}],"name":"UserOperationEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"UserOperationPrefundTooLow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"UserOperationRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"}],"name":"addStake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getDepositInfo","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"internalType":"struct IStakeManager.DepositInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDomainSeparatorV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint192","name":"key","type":"uint192"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPackedUserOpTypeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"getSenderAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"getUserOpHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"userOps","type":"tuple[]"},{"internalType":"contract IAggregator","name":"aggregator","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IEntryPoint.UserOpsPerAggregator[]","name":"opsPerAggregator","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleAggregatedOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"ops","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"key","type":"uint192"}],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"},{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterVerificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterPostOpGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"address","name":"paymaster","type":"address"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"}],"internalType":"struct EntryPoint.MemoryUserOp","name":"mUserOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"prefund","type":"uint256"},{"internalType":"uint256","name":"contextOffset","type":"uint256"},{"internalType":"uint256","name":"preOpGas","type":"uint256"}],"internalType":"struct EntryPoint.UserOpInfo","name":"opInfo","type":"tuple"},{"internalType":"bytes","name":"context","type":"bytes"}],"name":"innerHandleOp","outputs":[{"internalType":"uint256","name":"actualGasCost","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint192","name":"","type":"uint192"}],"name":"nonceSequenceNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"senderCreator","outputs":[{"internalType":"contract ISenderCreator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"}],"name":"withdrawStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"withdrawAmount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/bin/EntryPoint080.bytecode b/test/bin/EntryPoint080.bytecode new file mode 100644 index 000000000..f3f3bd1c1 Binary files /dev/null and b/test/bin/EntryPoint080.bytecode differ diff --git a/test/bin/SenderCreator080.abi b/test/bin/SenderCreator080.abi new file mode 100644 index 000000000..e169340ac --- /dev/null +++ b/test/bin/SenderCreator080.abi @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"},{"internalType":"bytes","name":"inner","type":"bytes"}],"name":"FailedOpWithRevert","type":"error"},{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"createSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"entryPoint","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"initCallData","type":"bytes"}],"name":"initEip7702Sender","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/bin/SenderCreator080.bytecode b/test/bin/SenderCreator080.bytecode new file mode 100644 index 000000000..0a8c61fb5 Binary files /dev/null and b/test/bin/SenderCreator080.bytecode differ diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js index 50a3b4a04..2d5cbe1a0 100644 --- a/test/helpers/erc4337.js +++ b/test/helpers/erc4337.js @@ -78,26 +78,8 @@ class UserOperation { }; } - hash(entrypoint, chainId) { - const p = this.packed; - const h = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode( - ['address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256'], - [ - p.sender, - p.nonce, - ethers.keccak256(p.initCode), - ethers.keccak256(p.callData), - p.accountGasLimits, - p.preVerificationGas, - p.gasFees, - ethers.keccak256(p.paymasterAndData), - ], - ), - ); - return ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [h, getAddress(entrypoint), chainId]), - ); + hash(entrypoint) { + return entrypoint.getUserOpHash(this.packed); } }