From e5658d7a32f8d4e18f38cbf165353691cd7380c7 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Thu, 17 Aug 2023 01:48:58 +0200 Subject: [PATCH 1/2] Allow extcode undeployed sender factory (#327) Co-authored-by: Dror Tirosh --- eip/EIPS/eip-4337.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eip/EIPS/eip-4337.md b/eip/EIPS/eip-4337.md index f8d43cba..cccc9e16 100644 --- a/eip/EIPS/eip-4337.md +++ b/eip/EIPS/eip-4337.md @@ -382,6 +382,7 @@ While simulating `userOp` validation, the client should make sure that: 4. cannot call EntryPoint's methods, except `depositFor` (to avoid recursion) 5. `EXTCODEHASH` of every address accessed (by any opcode) does not change between first and second simulations of the op. 6. `EXTCODEHASH`, `EXTCODELENGTH`, `EXTCODECOPY` may not access address with no code. +The exception is accessing the `sender` address from the `factory` contract as it is guaranteed to deploy the `sender`. 7. If `op.initcode.length != 0` , allow only one `CREATE2` opcode call (in the first (deployment) block), otherwise forbid `CREATE2`. Transient Storage slots defined in [EIP-1153](./eip-1153.md) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes From da37cc23d6b45586bd9edee1bfdc28391230aa3b Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Mon, 21 Aug 2023 13:46:49 +0200 Subject: [PATCH 2/2] AA-176: Add ERC-165 "supportsInterface" to the EntryPoint (#331) * AA-176: Add ERC-165 "supportsInterface" to the EntryPoint --- contracts/core/EntryPoint.sol | 15 +++++++++++- reports/gas-checker.txt | 24 +++++++++---------- src/Utils.ts | 16 +++++++++++++ test/entrypoint.test.ts | 43 +++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 src/Utils.ts diff --git a/contracts/core/EntryPoint.sol b/contracts/core/EntryPoint.sol index a5f6f274..af9fcbf4 100644 --- a/contracts/core/EntryPoint.sol +++ b/contracts/core/EntryPoint.sol @@ -13,13 +13,16 @@ import "./StakeManager.sol"; import "./SenderCreator.sol"; import "./Helpers.sol"; import "./NonceManager.sol"; + +// we also require '@gnosis.pm/safe-contracts' and both libraries have 'IERC165.sol', leading to conflicts +import "@openzeppelin/contracts/utils/introspection/ERC165.sol" as OpenZeppelin; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /* * Account-Abstraction (EIP-4337) singleton EntryPoint implementation. * Only one instance required on each chain. */ -contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard { +contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard, OpenZeppelin.ERC165 { using UserOperationLib for UserOperation; @@ -39,6 +42,16 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard */ uint256 public constant SIG_VALIDATION_FAILED = 1; + /// @inheritdoc OpenZeppelin.IERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + // note: solidity "type(IEntryPoint).interfaceId" is without inherited methods but we want to check everything + return interfaceId == (type(IEntryPoint).interfaceId ^ type(IStakeManager).interfaceId ^ type(INonceManager).interfaceId) || + interfaceId == type(IEntryPoint).interfaceId || + interfaceId == type(IStakeManager).interfaceId || + interfaceId == type(INonceManager).interfaceId || + super.supportsInterface(interfaceId); + } + /** * Compensate the caller's beneficiary address with the collected fees of all UserOperations. * @param beneficiary - The address to receive the fees. diff --git a/reports/gas-checker.txt b/reports/gas-checker.txt index a1f07868..9fd84fa8 100644 --- a/reports/gas-checker.txt +++ b/reports/gas-checker.txt @@ -12,28 +12,28 @@ ║ │ │ │ (delta for │ (compared to ║ ║ │ │ │ one UserOp) │ account.exec()) ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 1 │ 81899 │ │ ║ +║ simple │ 1 │ 81943 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 2 │ │ 44186 │ 15172 ║ +║ simple - diff from previous │ 2 │ │ 44208 │ 15194 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple │ 10 │ 479702 │ │ ║ +║ simple │ 10 │ 479920 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple - diff from previous │ 11 │ │ 44222 │ 15208 ║ +║ simple - diff from previous │ 11 │ │ 44280 │ 15266 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 1 │ 88182 │ │ ║ +║ simple paymaster │ 1 │ 88202 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 2 │ │ 43175 │ 14161 ║ +║ simple paymaster with diff │ 2 │ │ 43197 │ 14183 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster │ 10 │ 476938 │ │ ║ +║ simple paymaster │ 10 │ 477156 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ simple paymaster with diff │ 11 │ │ 43234 │ 14220 ║ +║ simple paymaster with diff │ 11 │ │ 43184 │ 14170 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 1 │ 182932 │ │ ║ +║ big tx 5k │ 1 │ 183000 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 2 │ │ 144721 │ 19461 ║ +║ big tx - diff from previous │ 2 │ │ 144719 │ 19459 ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx 5k │ 10 │ 1485370 │ │ ║ +║ big tx 5k │ 10 │ 1485612 │ │ ║ ╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢ -║ big tx - diff from previous │ 11 │ │ 144710 │ 19450 ║ +║ big tx - diff from previous │ 11 │ │ 144720 │ 19460 ║ ╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝ diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 00000000..8b520578 --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,16 @@ +import { Interface, JsonFragment } from '@ethersproject/abi' + +export function getERC165InterfaceID (abi: JsonFragment[]): string { + let interfaceId = + abi + .filter(it => it.type === 'function' && it.name != null) + .map(it => { + const iface = new Interface([it]) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return iface.getSighash(it.name!) + }) + .map((x) => parseInt(x, 16)) + .reduce((x, y) => x ^ y) + interfaceId = interfaceId > 0 ? interfaceId : 0xFFFFFFFF + interfaceId + 1 + return '0x' + interfaceId.toString(16).padStart(8, '0') +} diff --git a/test/entrypoint.test.ts b/test/entrypoint.test.ts index 082badb6..281288ce 100644 --- a/test/entrypoint.test.ts +++ b/test/entrypoint.test.ts @@ -21,6 +21,11 @@ import { TestSignatureAggregator__factory, MaliciousAccount__factory, TestWarmColdAccount__factory, + IEntryPoint__factory, + SimpleAccountFactory__factory, + IStakeManager__factory, + INonceManager__factory, + EntryPoint__factory, TestPaymasterRevertCustomError__factory } from '../typechain' import { @@ -53,6 +58,7 @@ import { arrayify, defaultAbiCoder, hexConcat, hexZeroPad, parseEther } from 'et import { debugTransaction } from './debugTx' import { BytesLike } from '@ethersproject/bytes' import { toChecksumAddress } from 'ethereumjs-util' +import { getERC165InterfaceID } from '../src/Utils' describe('EntryPoint', function () { let entryPoint: EntryPoint @@ -1362,4 +1368,41 @@ describe('EntryPoint', function () { }) }) }) + + describe('ERC-165', function () { + it('should return true for IEntryPoint interface ID', async function () { + const iepInterface = IEntryPoint__factory.createInterface() + const iepInterfaceID = getERC165InterfaceID([...iepInterface.fragments]) + expect(await entryPoint.supportsInterface(iepInterfaceID)).to.equal(true) + }) + + it('should return true for pure EntryPoint, IStakeManager and INonceManager interface IDs', async function () { + const epInterface = EntryPoint__factory.createInterface() + const smInterface = IStakeManager__factory.createInterface() + const nmInterface = INonceManager__factory.createInterface() + // note: manually generating "pure", solidity-like "type(IEntryPoint).interfaceId" without inherited methods + const epPureInterfaceFunctions = [ + ...epInterface.fragments.filter(it => [ + 'handleOps', + 'handleAggregatedOps', + 'getUserOpHash', + 'getSenderAddress', + 'simulateValidation', + 'simulateHandleOp' + ].includes(it.name)) + ] + const epPureInterfaceID = getERC165InterfaceID(epPureInterfaceFunctions) + const smInterfaceID = getERC165InterfaceID([...smInterface.fragments]) + const nmInterfaceID = getERC165InterfaceID([...nmInterface.fragments]) + expect(await entryPoint.supportsInterface(smInterfaceID)).to.equal(true) + expect(await entryPoint.supportsInterface(nmInterfaceID)).to.equal(true) + expect(await entryPoint.supportsInterface(epPureInterfaceID)).to.equal(true) + }) + + it('should return false for a wrong interface', async function () { + const saInterface = SimpleAccountFactory__factory.createInterface() + const entryPointInterfaceID = getERC165InterfaceID([...saInterface.fragments]) + expect(await entryPoint.supportsInterface(entryPointInterfaceID)).to.equal(false) + }) + }) })