From 0a77e54c307d9becf0473b73541c4f9d3b2743a3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 25 Mar 2025 20:41:28 +0100 Subject: [PATCH] Use Entrypoint's provided hashing function to support v0.8.0 change of hash (#5586) Co-authored-by: ernestognw Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .../account/utils/draft-ERC4337Utils.sol | 43 +++++----- hardhat/common-contracts.js | 75 +++++++++++------- scripts/fetch-common-contracts.js | 50 ++++++++++++ test/account/utils/draft-ERC4337Utils.test.js | 28 +++---- test/account/utils/draft-ERC7579Utils.t.sol | 2 +- test/bin/EntryPoint080.abi | 1 + test/bin/EntryPoint080.bytecode | Bin 0 -> 21738 bytes test/bin/SenderCreator080.abi | 1 + test/bin/SenderCreator080.bytecode | Bin 0 -> 1217 bytes test/helpers/erc4337.js | 22 +---- 10 files changed, 131 insertions(+), 91 deletions(-) create mode 100755 scripts/fetch-common-contracts.js create mode 100644 test/bin/EntryPoint080.abi create mode 100644 test/bin/EntryPoint080.bytecode create mode 100644 test/bin/SenderCreator080.abi create mode 100644 test/bin/SenderCreator080.bytecode diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index b17a0db81..fda29dc83 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 0000000000000000000000000000000000000000..f3f3bd1c1be220e99009e479c79697f0d66d18c8 GIT binary patch literal 21738 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0a8c61fb52987901cf2cbfbb119129d55d2a3c60 GIT binary patch literal 1217 zcmbVL&1(};5Z^I-*psv+ji7}-?CMRi)+BzQwzL%$TarCwOBdXEWK#{qq&D?X6n5Y4 zChaksRuK=Sh$lt#9}rsf;GupTRPZ3wiwE&w@FbYlx7!Z_t*!JO-VAT%&HQHOH)vtE zf?}+xl?u(OZq+qCic4))THU)p7N&cKR4e`bleZkE@Lf&MKY0H+Ooh{d;|U7iRO~ia zwm*Mj&=Pox(c)H~EW$#8U5;K`x3gydC59a~w6yxF?or`|Q;k(nW++0`0~&HuT7?QL zTLv|ShSM9D24)ZGKSyQyZtCva*)w`gigbmkcsQlmNSx#}c*8ZGr%4sGvs4oN^j}mB zUtxEW+id{eB%3Kn7*uuloVkwkA94xX`Olfsw3&HbA80_bNi8Wlie*p`QRc!%gWqOP zPiGhDzm$l_j~yq)Oy0Q2Ca#5};0?ivqmluK@M2C5K6Y#Xl8&>hYnQrQv{Z>$&b zdz8?VG^C}ps#k3+F7%tPRnprD!4jTl*zQ6EA`UpLsUgHZ8pKfS8Rja?S#6TdkW9P! zJ&1FPq%?Ez;_aK82-)zUE7*ggg0f!MTB&$MO=)OwV5anPVy?k&lj<)DiZ-VxL75BA z=TmV~yk5AKGf0}|a%5zzaD(*c$HyiTg=~Ht`?yzq`Js^=9VreSY#~O+#N_=Cl6;g6 kew^>DJ$tkGwE4oqLi>}c#jo|pS1udHLN47aNNqybUt=U#&;S4c literal 0 HcmV?d00001 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); } }