diff --git a/package.json b/package.json index 3f5562aa..04e69a0a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "eslint-plugin-n": "^15.2.4", "eslint-plugin-promise": "^6.0.0", "lerna": "^5.4.0", - "typescript": "^4.7.4", + "typescript": "^5.6.2", "webpack": "^5.74.0", "webpack-cli": "^4.10.0" }, diff --git a/packages/bundler/src/BundlerServer.ts b/packages/bundler/src/BundlerServer.ts index 6361000a..ecfc993c 100644 --- a/packages/bundler/src/BundlerServer.ts +++ b/packages/bundler/src/BundlerServer.ts @@ -3,9 +3,7 @@ import cors from 'cors' import express, { Express, Response, Request, RequestHandler } from 'express' import { JsonRpcProvider } from '@ethersproject/providers' import { Signer, utils } from 'ethers' -import { hexlify, parseEther } from 'ethers/lib/utils' -import { ChainConfig, Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { EOACode7702Transaction } from '@ethereumjs/tx' +import { parseEther } from 'ethers/lib/utils' import { Server } from 'http' import { @@ -17,7 +15,7 @@ import { decodeRevertReason, deepHexlify, erc4337RuntimeVersion, - packUserOp, sleep + packUserOp } from '@account-abstraction/utils' import { BundlerConfig } from './BundlerConfig' @@ -246,34 +244,6 @@ export class BundlerServer { break } case 'eth_sendTransaction': - if (params[0].authorizationList != null) { - console.log('eth_sendTransaction received EIP-7702 transaction', JSON.stringify(params[0])) - // NOTE: @ethereumjs/tx v5.4.0 has a 'tuple nonce' as an array - patch or wait for fix - // @ts-ignore - const chain: ChainConfig = { - bootstrapNodes: [], - defaultHardfork: Hardfork.Prague, - // consensus: undefined, - // genesis: undefined, - hardforks: Mainnet.hardforks, - name: '', - chainId: 1337 - } - const common = new Common({ chain, eips: [2718, 2929, 2930, 7702] }) - const objectTx = new EOACode7702Transaction(params[0], { common }) - const privateKey = Buffer.from( - 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', - 'hex' - ) - const signedTx = objectTx.sign(privateKey) - const encodedTx = signedTx.serialize() - const senderAddress = signedTx.getSenderAddress().toString() - await this.wallet.sendTransaction({ to: senderAddress, value: parseEther('0.1') }) - await sleep(3000) - const rawTransaction = hexlify(encodedTx) - result = await this.provider.send('eth_sendRawTransaction', [rawTransaction]) - break - } if (!this.config.rip7560) { throw new RpcError(`Method ${method} is not supported`, -32601) } diff --git a/packages/bundler/src/MethodHandlerERC4337.ts b/packages/bundler/src/MethodHandlerERC4337.ts index e1424de1..a98945fa 100644 --- a/packages/bundler/src/MethodHandlerERC4337.ts +++ b/packages/bundler/src/MethodHandlerERC4337.ts @@ -146,17 +146,26 @@ export class MethodHandlerERC4337 { const returnInfo = decodeSimulateHandleOpResult(ret) - const { validAfter, validUntil } = mergeValidationDataValues(returnInfo.accountValidationData, returnInfo.paymasterValidationData) + const { + validAfter, + validUntil + } = mergeValidationDataValues(returnInfo.accountValidationData, returnInfo.paymasterValidationData) const { preOpGas } = returnInfo // todo: use simulateHandleOp for this too... - const callGasLimit = await this.provider.estimateGas({ - from: this.entryPoint.address, - to: userOp.sender, - data: userOp.callData - }).then(b => b.toNumber()).catch(err => { + const callGasLimit = await this.provider.send( + 'eth_estimateGas', [ + { + from: this.entryPoint.address, + to: userOp.sender, + data: userOp.callData, + // @ts-ignore + authorizationList: userOp.authorizationList + } + ] + ).then(b => b.toNumber()).catch(err => { const message = err.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted' throw new RpcError(message, ValidationErrors.UserOperationReverted) }) diff --git a/packages/bundler/src/modules/BundleManager.ts b/packages/bundler/src/modules/BundleManager.ts index 50e0c91b..159f773b 100644 --- a/packages/bundler/src/modules/BundleManager.ts +++ b/packages/bundler/src/modules/BundleManager.ts @@ -1,9 +1,9 @@ import Debug from 'debug' -import { BigNumber, BigNumberish, Signer } from 'ethers' +import { BigNumber, BigNumberish, PopulatedTransaction, Signer } from 'ethers' import { ErrorDescription } from '@ethersproject/abi/lib/interface' import { JsonRpcProvider } from '@ethersproject/providers' import { Mutex } from 'async-mutex' -import { isAddress } from 'ethers/lib/utils' +import { hexlify, isAddress } from 'ethers/lib/utils' import { EmptyValidateUserOpResult, @@ -32,6 +32,10 @@ import { IBundleManager } from './IBundleManager' import { MempoolEntry } from './MempoolEntry' import { MempoolManager } from './MempoolManager' import { ReputationManager, ReputationStatus } from './ReputationManager' +import { ChainConfig, Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { EOACode7702Transaction } from '@ethereumjs/tx' +import { AuthorizationList, EOACode7702TxData } from '@ethereumjs/tx/src/types' +import { PrefixedHexString } from '@ethereumjs/util' const debug = Debug('aa.exec.cron') @@ -123,6 +127,11 @@ export class BundleManager implements IBundleManager { signedTx, { knownAccounts: storageMap } ]) debug('eth_sendRawTransactionConditional ret=', ret) + } else if (tx.type === TX_TYPE_EIP_7702) { + const ethereumJsTx = await this._prepareEip7702Transaction(tx, eip7702Tuples) + const res = await this.provider.send('eth_sendRawTransaction', [ethereumJsTx]) + const rcpt = await this.provider.getTransactionReceipt(res) + ret = rcpt.transactionHash } else { const resp = await this.signer.sendTransaction(tx) const rcpt = await resp.wait() @@ -175,6 +184,75 @@ export class BundleManager implements IBundleManager { } } + // TODO: this is a temporary patch until ethers.js adds EIP-7702 support + async _prepareEip7702Transaction (tx: PopulatedTransaction, eip7702Tuples: EIP7702Authorization[]): Promise { + console.log('creating EIP-7702 transaction') + // TODO: read fields from the configuration + // @ts-ignore + const chain: ChainConfig = { + bootstrapNodes: [], + defaultHardfork: Hardfork.Prague, + // consensus: undefined, + // genesis: undefined, + hardforks: Mainnet.hardforks, + name: '', + chainId: 1337 + } + const common = new Common({ chain, eips: [2718, 2929, 2930, 7702] }) + const authorizationList: AuthorizationList = eip7702Tuples.map(it => { + const res = { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion,@typescript-eslint/no-base-to-string + chainId: `0x${parseInt(it.chainId.toString()).toString(16)}` as PrefixedHexString, + address: it.address as PrefixedHexString, + nonce: it.nonce as PrefixedHexString, + yParity: it.yParity as PrefixedHexString, + r: it.r as PrefixedHexString, + s: it.s as PrefixedHexString + } + if (res.yParity === '0x0') { + // o, for fuck's sake! + res.yParity = '0x' + } + if (res.nonce === '0x0') { + throw new Error('ethereumjs/tx does not handle zero nonce!') + } + return res + }) + const txData: EOACode7702TxData = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + nonce: `0x${tx.nonce!.toString(16)}`, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + to: tx.to!.toString() as PrefixedHexString, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + value: '0x0', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data: tx.data!.toString() as PrefixedHexString, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + chainId: `0x${tx.chainId!.toString(16)}`, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + maxPriorityFeePerGas: tx.maxPriorityFeePerGas!.toHexString() as PrefixedHexString, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + maxFeePerGas: tx.maxPriorityFeePerGas!.toHexString() as PrefixedHexString, + accessList: [], + authorizationList + } + // TODO: not clear why but 'eth_estimateGas' gives an 'execution reverted' error + // txData.gasLimit = await this.provider.send('eth_estimateGas', [txData]) + txData.gasLimit = `0x${(10000000).toString(16)}` + const objectTx = new EOACode7702Transaction(txData, { common }) + const privateKey = Buffer.from( + // @ts-ignore + this.signer.privateKey.slice(2), + // 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', + 'hex' + ) + + const signedTx = objectTx.sign(privateKey) + const encodedTx = signedTx.serialize() + return hexlify(encodedTx) + // const senderAddress = signedTx.getSenderAddress().toString() + } + async _findEntityToBlame (reasonStr: string, userOp: UserOperation): Promise { if (reasonStr.startsWith('AA3')) { // [EREP-030] A staked account is accountable for failure in any entity @@ -257,11 +335,13 @@ export class BundleManager implements IBundleManager { continue } // [GREP-020] - renamed from [SREP-030] + // @ts-ignore if (paymaster != null && (paymasterStatus === ReputationStatus.THROTTLED ?? (stakedEntityCount[paymaster] ?? 0) > THROTTLED_ENTITY_BUNDLE_COUNT)) { debug('skipping throttled paymaster', entry.userOp.sender, (entry.userOp as any).nonce) continue } // [GREP-020] - renamed from [SREP-030] + // @ts-ignore if (factory != null && (deployerStatus === ReputationStatus.THROTTLED ?? (stakedEntityCount[factory] ?? 0) > THROTTLED_ENTITY_BUNDLE_COUNT)) { debug('skipping throttled factory', entry.userOp.sender, (entry.userOp as any).nonce) continue diff --git a/packages/bundler/src/modules/MempoolManager.ts b/packages/bundler/src/modules/MempoolManager.ts index dce02f67..d6c4755a 100644 --- a/packages/bundler/src/modules/MempoolManager.ts +++ b/packages/bundler/src/modules/MempoolManager.ts @@ -277,7 +277,7 @@ export class MempoolManager { res.push( ...userOps.map(it => it.userOp.factory) ) - + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return res.filter(it => it != null).map(it => (it as string).toLowerCase()) } diff --git a/packages/bundler/test/UserOpMethodHandler.test.ts b/packages/bundler/test/UserOpMethodHandler.test.ts index 94273cf1..871b4cd3 100644 --- a/packages/bundler/test/UserOpMethodHandler.test.ts +++ b/packages/bundler/test/UserOpMethodHandler.test.ts @@ -200,6 +200,7 @@ describe('UserOpMethodHandler', function () { // sendUserOperation is async, even in auto-mining. need to wait for it. const event = await waitFor(async () => await entryPoint.queryFilter(entryPoint.filters.UserOperationEvent(userOpHash)).then(ret => ret?.[0])) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const transactionReceipt = await event!.getTransactionReceipt() assert.isNotNull(transactionReceipt) const logs = transactionReceipt.logs.filter(log => log.address === entryPoint.address) diff --git a/packages/bundler/tsconfig.json b/packages/bundler/tsconfig.json index 9cc61136..7b6c44b4 100644 --- a/packages/bundler/tsconfig.json +++ b/packages/bundler/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/bundler/tsconfig.packages.json b/packages/bundler/tsconfig.packages.json index 3ff24b05..4087e7b5 100644 --- a/packages/bundler/tsconfig.packages.json +++ b/packages/bundler/tsconfig.packages.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json index 704536ff..892f856b 100644 --- a/packages/sdk/tsconfig.json +++ b/packages/sdk/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/sdk/tsconfig.packages.json b/packages/sdk/tsconfig.packages.json index 3ff24b05..4087e7b5 100644 --- a/packages/sdk/tsconfig.packages.json +++ b/packages/sdk/tsconfig.packages.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/utils/src/interfaces/EIP7702Authorization.ts b/packages/utils/src/interfaces/EIP7702Authorization.ts index 9e02967a..bc9f24d4 100644 --- a/packages/utils/src/interfaces/EIP7702Authorization.ts +++ b/packages/utils/src/interfaces/EIP7702Authorization.ts @@ -1,8 +1,6 @@ import { BigNumberish } from 'ethers' import RLP from 'rlp' -import { toHex } from 'hardhat/internal/util/bigint' -import { hashMessage, hexlify } from 'ethers/lib/utils' -import { bytesToHex, ecrecover, hexToBytes, pubToAddress } from '@ethereumjs/util' +import { bytesToHex, ecrecover, hexToBigInt, hexToBytes, pubToAddress } from '@ethereumjs/util' import { AddressZero } from '../ERC4337Utils' import { keccak256 } from '@ethersproject/keccak256' @@ -27,11 +25,11 @@ export function getEip7702AuthorizationSigner (authorization: EIP7702Authorizati ) ] const messageHash = keccak256(rlpEncode) as `0x${string}` - console.log(hexlify(rlpEncode)) - console.log(messageHash) + // console.log('getEip7702AuthorizationSigner RLP:\n', hexlify(rlpEncode), rlpEncode.length) + // console.log('getEip7702AuthorizationSigner hash:\n', messageHash) const senderPubKey = ecrecover( hexToBytes(messageHash), - BigInt(authorization.yParity.toString()), + hexToBigInt(authorization.yParity.toString() as `0x${string}`), hexToBytes(authorization.r.toString() as `0x${string}`), hexToBytes(authorization.s.toString() as `0x${string}`) ) diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index d01463cd..e6c7872b 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -5,7 +5,7 @@ ], "compilerOptions": { "outDir": "dist", - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/validation-manager/src/ValidationManager.ts b/packages/validation-manager/src/ValidationManager.ts index b0f3069a..49a17529 100644 --- a/packages/validation-manager/src/ValidationManager.ts +++ b/packages/validation-manager/src/ValidationManager.ts @@ -116,7 +116,7 @@ export class ValidationManager implements IValidationManager { async _geth_traceCall_SimulateValidation ( operation: OperationBase, - stateOverride: {[address: string]: {code: string}} + stateOverride: { [address: string]: { code: string } } ): Promise<[ValidationResult, BundlerTracerResult]> { const userOp = operation as UserOperation const provider = this.entryPoint.provider as JsonRpcProvider @@ -251,8 +251,10 @@ export class ValidationManager implements IValidationManager { } } - async getAuthorizationsStateOverride(authorizations: EIP7702Authorization[]): Promise<{[address: string]: {code: string}}> { - const stateOverride: {[address: string]: {code: string}} = {} + async getAuthorizationsStateOverride ( + authorizations: EIP7702Authorization[] + ): Promise<{ [address: string]: { code: string } }> { + const stateOverride: { [address: string]: { code: string } } = {} // TODO: why don't we have 'provider' as a member in here? const provider = this.entryPoint.provider as JsonRpcProvider for (const authorization of authorizations) { @@ -262,8 +264,8 @@ export class ValidationManager implements IValidationManager { const noCurrentDelegation = currentDelegateeCode.length <= 2 // TODO: do not send such authorizations to 'handleOps' as it is a waste of gas const changeDelegation = newDelegateeCode !== currentDelegateeCode - if (noCurrentDelegation || changeDelegation){ - console.log('Adding state override:', {address: sender, code: newDelegateeCode.slice(0, 20)}) + if (noCurrentDelegation || changeDelegation) { + console.log('Adding state override:', { address: sender, code: newDelegateeCode.slice(0, 20) }) stateOverride[sender] = { code: newDelegateeCode } diff --git a/packages/validation-manager/tsconfig.json b/packages/validation-manager/tsconfig.json index 704536ff..892f856b 100644 --- a/packages/validation-manager/tsconfig.json +++ b/packages/validation-manager/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/validation-manager/tsconfig.packages.json b/packages/validation-manager/tsconfig.packages.json index 3ff24b05..4087e7b5 100644 --- a/packages/validation-manager/tsconfig.packages.json +++ b/packages/validation-manager/tsconfig.packages.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/tsconfig.json b/tsconfig.json index 398db920..8e374536 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "jsx": "react", - "target": "es2017", + "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/yarn.lock b/yarn.lock index 9e16827e..bb7dd121 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10597,12 +10597,12 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@>=4.5.0: +typescript@>=4.5.0, typescript@^5.6.2: version "5.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== -"typescript@^3 || ^4", typescript@^4.7.4: +"typescript@^3 || ^4": version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==