diff --git a/app/ts/background/background.ts b/app/ts/background/background.ts index f5429a2e..583daae6 100644 --- a/app/ts/background/background.ts +++ b/app/ts/background/background.ts @@ -14,7 +14,7 @@ import { getActiveAddressEntry, getAddressBookEntriesForVisualiser, identifyAddr import { getActiveAddress, sendPopupMessageToOpenWindows } from './backgroundUtils.js' import { DistributiveOmit, assertNever, assertUnreachable, modifyObject } from '../utils/typescript.js' import { EthereumClientService } from '../simulation/services/EthereumClientService.js' -import { appendTransaction, calculateRealizedEffectiveGasPrice, copySimulationState, fixNonceErrorsIfNeeded, getAddressToMakeRich, getBaseFeeAdjustedTransactions, getNonceFixedSimulatedTransactions, getTokenBalancesAfter, getWebsiteCreatedEthereumUnsignedTransactions, mockSignTransaction, setSimulationTransactionsAndSignedMessages, simulateEstimateGas } from '../simulation/services/SimulationModeEthereumClientService.js' +import { appendTransaction, calculateRealizedEffectiveGasPrice, copySimulationState, fixNonceErrorsIfNeeded, getAddressToMakeRich, getBaseFeeAdjustedTransactions, getNonceFixedSimulatedTransactions, getTokenBalancesAfter, getWebsiteCreatedEthereumUnsignedTransactions, mockSignTransaction, setSimulationTransactionsAndSignedMessages, simulationGasLeft } from '../simulation/services/SimulationModeEthereumClientService.js' import { Semaphore } from '../utils/semaphore.js' import { JsonRpcResponseError, handleUnexpectedError, isFailedToFetchError, isNewBlockAbort, printError } from '../utils/errors.js' import { formSimulatedAndVisualizedTransaction } from '../components/formVisualizerResults.js' @@ -102,7 +102,7 @@ export const simulateGovernanceContractExecution = async (pendingTransaction: Pe if (tokenBalancesAfter[0] === undefined) return returnError('Could not compute token balances') - const governanceContractSimulationState: SimulationState = { + const governanceContractSimulationState: SimulationState = { addressToMakeRich: undefined, simulatedTransactions: [{ preSimulationTransaction: { @@ -145,8 +145,7 @@ export const simulateGnosisSafeMetaTransaction = async (gnosisSafeMessage: Visua nonce: 0n, chainId: ethereumClientService.getChainId(), } - const gasLimit = gnosisSafeMessage.message.message.baseGas !== 0n ? { gas: gnosisSafeMessage.message.message.baseGas } : await simulateEstimateGas(ethereumClientService, undefined, simulationState, transactionWithoutGas) - if ('error' in gasLimit) return returnError(gasLimit.error.message) + const gasLimit = gnosisSafeMessage.message.message.baseGas !== 0n ? { gas: gnosisSafeMessage.message.message.baseGas } : { gas: simulationGasLeft(simulationState, await ethereumClientService.getBlock(undefined)) } const transaction = { ...transactionWithoutGas, gas: gasLimit.gas } const metaTransaction: WebsiteCreatedEthereumUnsignedTransaction = { website: gnosisSafeMessage.website, diff --git a/app/ts/simulation/compoundGovernanceFaking.ts b/app/ts/simulation/compoundGovernanceFaking.ts index 4656d375..55a42748 100644 --- a/app/ts/simulation/compoundGovernanceFaking.ts +++ b/app/ts/simulation/compoundGovernanceFaking.ts @@ -51,7 +51,8 @@ export const simulateCompoundGovernanceExecution = async (ethereumClientService: ] const parentBlock = await ethereumClientService.getBlock(undefined) if (parentBlock === null) throw new Error('The latest block is null') - const governanceContractCalls = (await ethereumClientService.simulateTransactionsAndSignatures(calls, [], parentBlock.number, undefined)).calls + const governanceContractCalls = (await ethereumClientService.simulateTransactionsAndSignatures([calls], [], parentBlock.number, undefined))[0]?.calls + if (governanceContractCalls === undefined) throw new Error('simulateTransactionsAndSignatures returned zero length aray') for (const call of governanceContractCalls) { if (call.status !== 'success') throw new Error('Failed to retrieve governance contracts information') } diff --git a/app/ts/simulation/services/EthereumClientService.ts b/app/ts/simulation/services/EthereumClientService.ts index f55db1b1..7d28ca19 100644 --- a/app/ts/simulation/services/EthereumClientService.ts +++ b/app/ts/simulation/services/EthereumClientService.ts @@ -207,12 +207,12 @@ export class EthereumClientService { return EthSimulateV1Result.parse(unvalidatedResult) } - public readonly simulateTransactionsAndSignatures = async (transactions: readonly OptionalEthereumUnsignedTransaction[], signatures: readonly SignatureWithFakeSignerAddress[], blockNumber: bigint, requestAbortController: AbortController | undefined, extraAccountOverrides: StateOverrides = {}) => { - const transactionsWithRemoveZeroPricedOnes = transactions.map((transaction) => { + public readonly simulateTransactionsAndSignatures = async (transactionsInBlocks: readonly OptionalEthereumUnsignedTransaction[][], signatures: readonly SignatureWithFakeSignerAddress[], blockNumber: bigint, requestAbortController: AbortController | undefined, extraAccountOverrides: StateOverrides = {}) => { + const transactionsWithRemoveZeroPricedOnes = transactionsInBlocks.map((block) => block.map((transaction) => { if (transaction.type !== '1559') return transaction const { maxFeePerGas, ...transactionWithoutMaxFee } = transaction return { ...transactionWithoutMaxFee, ...maxFeePerGas === 0n ? {} : { maxFeePerGas } } - }) + })) const ecRecoverMovedToAddress = 0x123456n const ecRecoverAddress = 1n const parentBlock = await this.getBlock(requestAbortController, blockNumber) @@ -233,16 +233,22 @@ export class EthereumClientService { return acc }, {} as { [key: string]: bigint } ) - const query = [{ - calls: transactionsWithRemoveZeroPricedOnes, - blockOverride: { - number: parentBlock.number + 1n, - prevRandao: 0x1n, - time: new Date(parentBlock.timestamp.getTime() + 12 * 1000), - gasLimit: parentBlock.gasLimit, - feeRecipient: parentBlock.miner, - baseFeePerGas: parentBlock.baseFeePerGas === undefined ? 15000000n : parentBlock.baseFeePerGas - }, + const getBlockOverrides = (index: number) => ({ + number: parentBlock.number + 1n + BigInt(index), + prevRandao: 0x1n, + time: new Date(parentBlock.timestamp.getTime() + (index + 1) * 12 * 1000), + gasLimit: parentBlock.gasLimit, + feeRecipient: parentBlock.miner, + baseFeePerGas: parentBlock.baseFeePerGas === undefined ? 15000000n : parentBlock.baseFeePerGas + }) + + const [firstBlocksTransactions, ...restofTheBlocks] = transactionsWithRemoveZeroPricedOnes + if (firstBlocksTransactions === undefined) throw new Error('No blocks specified for simulateTransactionsAndSignatures') + + // add stateOverrides only to the first block (ecrecover overrides, make me rich and such) + const firstBlocksCalls = { + calls: firstBlocksTransactions, + blockOverride: getBlockOverrides(0), stateOverrides: { ...signatures.length > 0 ? { [addressString(ecRecoverAddress)]: { @@ -253,12 +259,11 @@ export class EthereumClientService { } : {}, ...extraAccountOverrides, } - }] - const ethSimulateResults = await this.ethSimulateV1(query, parentBlock.number, requestAbortController) - if (ethSimulateResults.length !== 1) throw new Error('Ran Eth Simulate for one block but did not get one block') - const singleMulticalResult = ethSimulateResults[0] - if (singleMulticalResult === undefined) throw new Error('Eth Simualte result was undefined') - return singleMulticalResult + } + const blockStateCalls = [firstBlocksCalls, ...restofTheBlocks.map((calls, index) => ({ calls, blockOverride: getBlockOverrides(index + 1) }))] + const ethSimulateResults = await this.ethSimulateV1(blockStateCalls, parentBlock.number, requestAbortController) + if (ethSimulateResults.length !== transactionsWithRemoveZeroPricedOnes.length) throw new Error(`Ran Eth Simulate for ${ transactionsWithRemoveZeroPricedOnes.length } blocks but got ${ ethSimulateResults.length } blocks`) + return ethSimulateResults } public readonly web3ClientVersion = async (requestAbortController: AbortController | undefined) => { diff --git a/app/ts/simulation/services/SimulationModeEthereumClientService.ts b/app/ts/simulation/services/SimulationModeEthereumClientService.ts index 0a2ad1e5..1e0966e8 100644 --- a/app/ts/simulation/services/SimulationModeEthereumClientService.ts +++ b/app/ts/simulation/services/SimulationModeEthereumClientService.ts @@ -138,7 +138,7 @@ const transactionQueueTotalGasLimit = (simulationState: SimulationState) => { return simulationState.simulatedTransactions.reduce((a, b) => a + b.preSimulationTransaction.signedTransaction.gas, 0n) } -const simulationGasLeft = (simulationState: SimulationState | undefined, blockHeader: EthereumBlockHeader) => { +export const simulationGasLeft = (simulationState: SimulationState | undefined, blockHeader: EthereumBlockHeader) => { if (blockHeader === null) throw new Error('The latest block is null') if (simulationState === undefined) return blockHeader.gasLimit * 1023n / 1024n return max(blockHeader.gasLimit * 1023n / 1024n - transactionQueueTotalGasLimit(simulationState), 0n) @@ -180,7 +180,8 @@ export const simulateEstimateGas = async (ethereumClientService: EthereumClientS accessList: [] } try { - const multiCall = await simulatedMulticall(ethereumClientService, requestAbortController, simulationState, [tmp], block.number) + const multiCall = (await simulatedMulticall(ethereumClientService, requestAbortController, simulationState, [tmp], block.number))[0] + if (multiCall === undefined) return { error: { code: ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, message: 'ETH Simulate Failed to estimate gas', data: '' } } const lastResult = multiCall.calls[multiCall.calls.length - 1] if (lastResult === undefined) return { error: { code: ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, message: 'ETH Simulate Failed to estimate gas', data: '' } } if (lastResult.status === 'failure') return { error: { ...lastResult.error, data: dataStringWith0xStart(lastResult.returnData) } } @@ -238,10 +239,11 @@ export const appendTransaction = async (ethereumClientService: EthereumClientSer const signedTxs = getSignedTransactions() const addressToMakeRich = await getMakeMeRichAddress() const makeMeRich = getMakeMeRichStateOverride(addressToMakeRich) - const ethSimulateV1CallResult = await ethereumClientService.simulateTransactionsAndSignatures(signedTxs, signedMessages, parentBlock.number, requestAbortController, makeMeRich) + const ethSimulateV1CallResult = (await ethereumClientService.simulateTransactionsAndSignatures([signedTxs], signedMessages, parentBlock.number, requestAbortController, makeMeRich))[0] + if (ethSimulateV1CallResult === undefined) throw new Error('multicall length does not match in appendTransaction') const transactionWebsiteData = { website: transaction.website, created: transaction.created, originalRequestParameters: transaction.originalRequestParameters, transactionIdentifier: transaction.transactionIdentifier } const transactionData = simulationState === undefined ? [transactionWebsiteData] : simulationState.simulatedTransactions.map((x) => ({ website: x.preSimulationTransaction.website, created: x.preSimulationTransaction.created, originalRequestParameters: x.preSimulationTransaction.originalRequestParameters, transactionIdentifier: x.preSimulationTransaction.transactionIdentifier })).concat(transactionWebsiteData) - if (ethSimulateV1CallResult.calls.length !== signedTxs.length) throw 'multicall length does not match in appendTransaction' + if (ethSimulateV1CallResult.calls.length !== signedTxs.length) throw Error('multicall length does not match in appendTransaction') const tokenBalancesAfter = await getTokenBalancesAfter( ethereumClientService, requestAbortController, @@ -251,7 +253,7 @@ export const appendTransaction = async (ethereumClientService: EthereumClientSer signedMessages, makeMeRich, ) - if (ethSimulateV1CallResult.calls.length !== tokenBalancesAfter.length) throw 'tokenBalancesAfter length does not match' + if (ethSimulateV1CallResult.calls.length !== tokenBalancesAfter.length) throw Error('tokenBalancesAfter length does not match') return { addressToMakeRich, @@ -259,7 +261,7 @@ export const appendTransaction = async (ethereumClientService: EthereumClientSer const signedTx = signedTxs[index] const tokenBalancesAfterForIndex = tokenBalancesAfter[index] const transactionDataForIndex = transactionData[index] - if (signedTx === undefined || tokenBalancesAfterForIndex === undefined || transactionDataForIndex === undefined) throw 'invalid transaction index' + if (signedTx === undefined || tokenBalancesAfterForIndex === undefined || transactionDataForIndex === undefined) throw Error('invalid transaction index') return { type: 'transaction', ethSimulateV1CallResult: singleResult, @@ -298,8 +300,8 @@ export const setSimulationTransactionsAndSignedMessages = async (ethereumClientS const parentBaseFeePerGas = parentBlock.baseFeePerGas if (parentBaseFeePerGas === undefined) throw new Error(CANNOT_SIMULATE_OFF_LEGACY_BLOCK) const makeMeRich = getMakeMeRichStateOverride(addressToMakeRich) - const multicallResult = await ethereumClientService.simulateTransactionsAndSignatures(transactionStack.transactions.map((x) => x.signedTransaction), transactionStack.signedMessages, parentBlock.number, requestAbortController, makeMeRich) - if (multicallResult.calls.length !== transactionStack.transactions.length) throw new Error('Multicall length does not match in setSimulationTransactions') + const multicallResult = (await ethereumClientService.simulateTransactionsAndSignatures([transactionStack.transactions.map((x) => x.signedTransaction)], transactionStack.signedMessages, parentBlock.number, requestAbortController, makeMeRich))[0] + if (multicallResult === undefined || multicallResult.calls.length !== transactionStack.transactions.length) throw new Error('Multicall length does not match in setSimulationTransactions') const tokenBalancesAfter: Promise[] = [] for (let resultIndex = 0; resultIndex < multicallResult.calls.length; resultIndex++) { @@ -525,14 +527,16 @@ export const getSimulatedCode = async (ethereumClientService: EthereumClientServ nonce: await ethereumClientService.getTransactionCount(MOCK_ADDRESS, 'latest', requestAbortController), maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, - gas: simulationGasLeft(simulationState, block), + gas: block.gasLimit, to: GET_CODE_CONTRACT, value: 0n, input: input, accessList: [] } as const - const multiCall = await simulatedMulticall(ethereumClientService, requestAbortController, simulationState, [getCodeTransaction], block.number, { [addressString(GET_CODE_CONTRACT)]: { code: getCodeByteCode() } }) - const lastResult = multiCall.calls[multiCall.calls.length - 1] + const multiCall = await simulatedMulticall(ethereumClientService, requestAbortController, simulationState, [getCodeTransaction], block.number, { [addressString(GET_CODE_CONTRACT)]: { code: getCodeByteCode() } }, true) + const lastBlock = multiCall[multiCall.length - 1] + if (lastBlock === undefined) throw new Error('last block did not exist in multicall') + const lastResult = lastBlock.calls[lastBlock.calls.length - 1] if (lastResult === undefined) throw new Error('last result did not exist in multicall') if (lastResult.status === 'failure') return { statusCode: 'failure' } as const const parsed = atInterface.decodeFunctionResult('at', lastResult.returnData) @@ -694,9 +698,10 @@ export const getSimulatedTransactionByHash = async (ethereumClientService: Ether } export const simulatedCall = async (ethereumClientService: EthereumClientService, requestAbortController: AbortController | undefined, simulationState: SimulationState | undefined, params: Pick & Partial>, blockTag: EthereumBlockTag = 'latest') => { - const currentBlock = await ethereumClientService.getBlockNumber(requestAbortController) - const blockNumToUse = blockTag === 'latest' || blockTag === 'pending' ? currentBlock : min(blockTag, currentBlock) - const simulationStateToUse = blockNumToUse >= currentBlock ? simulationState : undefined + const currentBlock = await ethereumClientService.getBlock(requestAbortController) + if (currentBlock === null) throw new Error('cannot perform call on top of missing block') + const blockNumToUse = blockTag === 'latest' || blockTag === 'pending' ? currentBlock.number : min(blockTag, currentBlock.number) + const simulationStateToUse = blockNumToUse >= currentBlock.number ? simulationState : undefined const from = params.from ?? DEFAULT_CALL_ADDRESS const transaction = { ...params, @@ -708,9 +713,11 @@ export const simulatedCall = async (ethereumClientService: EthereumClientService } as const //todo, we can optimize this by leaving nonce out - const multicallResult = await simulatedMulticall(ethereumClientService, requestAbortController, simulationStateToUse, [{ ...transaction, gas: params.gasLimit === undefined ? simulationGasLeft(simulationState, await ethereumClientService.getBlock(requestAbortController)) : params.gasLimit }], blockNumToUse) - const callResult = multicallResult.calls[multicallResult.calls.length - 1] - if (callResult === undefined) throw new Error('failed to eth simulate') + const multicallResult = await simulatedMulticall(ethereumClientService, requestAbortController, simulationStateToUse, [{ ...transaction, gas: params.gasLimit === undefined ? currentBlock.gasLimit : params.gasLimit }], blockNumToUse, {}, true) + const lastBlockResult = multicallResult[multicallResult.length - 1] + if (lastBlockResult === undefined) throw new Error('failed to get last block in eth simulate') + const callResult = lastBlockResult.calls[lastBlockResult.calls.length - 1] + if (callResult === undefined) throw new Error('failed to get last call in eth simulate') if (callResult?.status === 'failure') return { error: callResult.error } return { result: callResult.returnData } } @@ -719,10 +726,11 @@ const getSignedMessagesWithFakeSigner = (simulationState: SimulationState | unde return simulationState === undefined ? [] : simulationState.signedMessages.map((x) => ({ fakeSignedFor: x.fakeSignedFor, originalRequestParameters: x.originalRequestParameters })) } -const simulatedMulticall = async (ethereumClientService: EthereumClientService, requestAbortController: AbortController | undefined, simulationState: SimulationState | undefined, transactions: EthereumUnsignedTransaction[], blockNumber: bigint, extraAccountOverrides: StateOverrides = {}) => { +const simulatedMulticall = async (ethereumClientService: EthereumClientService, requestAbortController: AbortController | undefined, simulationState: SimulationState | undefined, transactions: EthereumUnsignedTransaction[], blockNumber: bigint, extraAccountOverrides: StateOverrides = {}, simulateOnBlockAboveExistingSimulationStack: boolean = false) => { const mergedTxs: EthereumUnsignedTransaction[] = getTransactionQueue(simulationState) const makeMeRich = getMakeMeRichStateOverride(simulationState?.addressToMakeRich) - return await ethereumClientService.simulateTransactionsAndSignatures(mergedTxs.concat(transactions), getSignedMessagesWithFakeSigner(simulationState), blockNumber, requestAbortController, { ...extraAccountOverrides, ...makeMeRich }) + const transactionsInBlocks = simulateOnBlockAboveExistingSimulationStack ? [mergedTxs, transactions] : [mergedTxs.concat(transactions)] + return await ethereumClientService.simulateTransactionsAndSignatures(transactionsInBlocks, getSignedMessagesWithFakeSigner(simulationState), blockNumber, requestAbortController, { ...extraAccountOverrides, ...makeMeRich }) } // use time as block hash as that makes it so that updated simulations with different states are different, but requires no additional calculation @@ -818,12 +826,14 @@ const getSimulatedTokenBalances = async (ethereumClientService: EthereumClientSe input: tokenAndEthBalancesInputData, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, - gas: 15_000_000n, + gas: (await ethereumClientService.getBlock(requestAbortController))?.gasLimit || 15_000_000n, nonce: 0n, chainId: ethereumClientService.getChainId(), } as const - const multicallResults = await ethereumClientService.simulateTransactionsAndSignatures(transactionQueue.concat(callTransaction), signedMessages, blockNumber, requestAbortController, extraAccountOverrides) - const aggregate3CallResult = multicallResults.calls[multicallResults.calls.length - 1] + const multicallResults = await ethereumClientService.simulateTransactionsAndSignatures([transactionQueue, [callTransaction]], signedMessages, blockNumber, requestAbortController, extraAccountOverrides) + const lastBlockResults = multicallResults[multicallResults.length - 1] + if (lastBlockResults === undefined) throw Error('Failed to get last block in eth simulate in aggregate3') + const aggregate3CallResult = lastBlockResults.calls[lastBlockResults.calls.length - 1] if (aggregate3CallResult === undefined || aggregate3CallResult.status === 'failure') throw Error('Failed aggregate3') const multicallReturnData: { success: boolean, returnData: string }[] = IMulticall3.decodeFunctionResult('aggregate3', dataStringWith0xStart(aggregate3CallResult.returnData))[0] if (multicallReturnData.length !== deduplicatedBalanceQueries.length) throw Error('Got wrong number of balances back') diff --git a/test/RPCResponses.ts b/test/RPCResponses.ts index 3c07e800..489599c3 100644 --- a/test/RPCResponses.ts +++ b/test/RPCResponses.ts @@ -163,40 +163,172 @@ export const eth_simulateV1_dummy_call_result = ` "id": 1, "result": [ { - "number": "0x1294390", - "hash": "0xef17a9574063a4f9cc15478aa2e5103e719cef6c6e3ba376c75f89bd716d9d3b", - "timestamp": "0x65fbde68", + "baseFeePerGas": "0x0", + "blobGasUsed": "0x0", + "calls": [ + { + "returnData": "0x", + "logs": [ + { + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045", + "0x000000000000000000000000da9dfa130df4de4673b89022ee50ff26f6ea73cf" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000a", + "blockNumber": "0x1463610", + "transactionHash": "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08", + "transactionIndex": "0x0", + "blockHash": "0x07e66724bc93ce1fff7011b19389eb0d0f3e49207aefaaaeeeb187cc7855054d", + "logIndex": "0x0", + "removed": false + } + ], + "gasUsed": "0x5208", + "status": "0x1" + } + ], + "difficulty": "0x0", + "excessBlobGas": "0x4000000", + "extraData": "0x", "gasLimit": "0x1c9c380", "gasUsed": "0x5208", - "feeRecipient": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5", - "baseFeePerGas": "0x19", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "hash": "0x07e66724bc93ce1fff7011b19389eb0d0f3e49207aefaaaeeeb187cc7855054d", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1463610", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x7376dea4bd6a8d55fb17eab8aaf750b87ed088e4fc60a885e4fd9d529256636d", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x272", + "stateRoot": "0x646ca2364d88ef4abcb2e5e78c8bc3c352f010c1686f71c2b24f74af7776f29e", + "timestamp": "0x675964eb", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08" + ], + "transactionsRoot": "0xb485d74defe884dfa6395377266e3aa5269e6b8b35da374d2d9a794f6d745c51", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } + ] +} +` + +export const eth_simulateV1_dummy_call_result_2calls = ` +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "baseFeePerGas": "0x0", + "blobGasUsed": "0x0", "calls": [ { "returnData": "0x", - "logs": [], + "logs": [ + { + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045", + "0x000000000000000000000000da9dfa130df4de4673b89022ee50ff26f6ea73cf" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000a", + "blockNumber": "0x1463610", + "transactionHash": "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08", + "transactionIndex": "0x0", + "blockHash": "0x07e66724bc93ce1fff7011b19389eb0d0f3e49207aefaaaeeeb187cc7855054d", + "logIndex": "0x0", + "removed": false + } + ], "gasUsed": "0x5208", "status": "0x1" } - ] + ], + "difficulty": "0x0", + "excessBlobGas": "0x4000000", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "hash": "0x07e66724bc93ce1fff7011b19389eb0d0f3e49207aefaaaeeeb187cc7855054d", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1463610", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x7376dea4bd6a8d55fb17eab8aaf750b87ed088e4fc60a885e4fd9d529256636d", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x272", + "stateRoot": "0x646ca2364d88ef4abcb2e5e78c8bc3c352f010c1686f71c2b24f74af7776f29e", + "timestamp": "0x675964eb", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08" + ], + "transactionsRoot": "0xb485d74defe884dfa6395377266e3aa5269e6b8b35da374d2d9a794f6d745c51", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + }, + { + "baseFeePerGas": "0x0", + "blobGasUsed": "0x0", + "calls": [ + { + "returnData": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000168825f7b8b09d7a14", + "logs": [], + "gasUsed": "0x6dd4", + "status": "0x1" + } + ], + "difficulty": "0x0", + "excessBlobGas": "0x3fa0000", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x6dd4", + "hash": "0xdf58567b9d9bcf5d909ba0f357f7007508008ed684078b8ee56bffb8c550c082", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1463611", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0xb7232861aff005700abbdea80849ed21d908b3c26def9152141f06cfbfc393f5", + "receiptsRoot": "0xcc012e76b32263502a29ef58b38df17199f6c904f5ce62a798356dc60d1bc053", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x3a0", + "stateRoot": "0x7773a5ee197e0f32a688100c3a2bf8b8d83385256635fbe6f78afc72aff8a67b", + "timestamp": "0x675964f7", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + "0x8ba282b81ac4af2c1d21ddfcff100a4d820c63908149b98562f8223242c9f063" + ], + "transactionsRoot": "0x0170380fd850ff899309c67f6feb620e7d2c75abc4d5b68ccf1d5ceadc80eedf", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } ] } ` + export const eth_simulateV1_get_eth_balance_multicall = ` { "jsonrpc": "2.0", "id": 1, "result": [ { - "number": "0x129ecf5", - "hash": "0x42082a3579bb6aaf0747a889ca743ea23ffe5a1d35ab3e66c0a114c57912369e", - "timestamp": "0x6603ea48", - "gasLimit": "0x1c9c380", - "gasUsed": "0xbfdc", - "feeRecipient": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "baseFeePerGas": "0x594f1e1b8", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x0", + "blobGasUsed": "0x0", "calls": [ { "returnData": "0x", @@ -209,24 +341,82 @@ export const eth_simulateV1_get_eth_balance_multicall = ` "0x000000000000000000000000da9dfa130df4de4673b89022ee50ff26f6ea73cf" ], "data": "0x000000000000000000000000000000000000000000000000000000000000000a", - "blockNumber": "0x129ecf5", - "transactionHash": "0xb78d6ba9501e72014cc8675e6ba3ec3970eaa7723e3ecf3cc6077542265f6f39", + "blockNumber": "0x1463660", + "transactionHash": "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08", "transactionIndex": "0x0", - "blockHash": "0x42082a3579bb6aaf0747a889ca743ea23ffe5a1d35ab3e66c0a114c57912369e", + "blockHash": "0x0d372030669ce3017faaa3b0eef5844888db634a2323c4c90b8da6ce99cfd803", "logIndex": "0x0", "removed": false } ], "gasUsed": "0x5208", "status": "0x1" - }, + } + ], + "difficulty": "0x0", + "excessBlobGas": "0x3e60000", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "hash": "0x0d372030669ce3017faaa3b0eef5844888db634a2323c4c90b8da6ce99cfd803", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x48319f97e5da1233c21c48b80097c0fb7a20ff86", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1463660", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0xae93c1572b68070df2b4327e0316933bfc623d983624b52dcdbc0798a3319877", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x272", + "stateRoot": "0x62db8b5784a407df63f81e0e6a195583f87beb30ab31ab6a54633cd470c0ff98", + "timestamp": "0x675968ab", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + "0x5a4bed19ad1e1bd8f581e04ebb5a71dec78f147eef37b320fff449f7d1ad1f08" + ], + "transactionsRoot": "0xb485d74defe884dfa6395377266e3aa5269e6b8b35da374d2d9a794f6d745c51", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + }, + { + "baseFeePerGas": "0x0", + "blobGasUsed": "0x0", + "calls": [ { - "returnData": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000263bd61e8d6e1711f3", + "returnData": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000168825f7b8b09d7a140000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000085ac5265b7af1720477c", "logs": [], - "gasUsed": "0x6dd4", + "gasUsed": "0x84be", "status": "0x1" } - ] + ], + "difficulty": "0x0", + "excessBlobGas": "0x3e00000", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x84be", + "hash": "0xe7826ae106ac084a2c4a944259a9ada57d137d7ba2119be00c169e6bf300a237", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x48319f97e5da1233c21c48b80097c0fb7a20ff86", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1463661", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0xa90e89a4d496a3aad10b8a835f8720687a5cb45746f82d89fef5664ad69e6534", + "receiptsRoot": "0xf4a805a21d7f900c0ce03ff9dc58c3e8ac2fab31cabd12070a7e214abd217a0a", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x480", + "stateRoot": "0xa99d14b95c165df0048a2678cdaa6251a26232b8cfad2e490e32545051c611dd", + "timestamp": "0x675968b7", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + "0x06c18d904b022ff80f81ec2c1e1c06fc72f0ad9e428a077763b03d73132ed65f" + ], + "transactionsRoot": "0xb5c5059cf2071313e76b2bb7e753e21311228ea3fde2f27819553559e485c147", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } ] } diff --git a/test/tests/nethermindComparison.ts b/test/tests/nethermindComparison.ts index 1d4e5315..cec0ad82 100644 --- a/test/tests/nethermindComparison.ts +++ b/test/tests/nethermindComparison.ts @@ -2,12 +2,13 @@ import { EthereumClientService } from '../../app/ts/simulation/services/Ethereum import { appendTransaction, getSimulatedBlock, getSimulatedTransactionByHash } from '../../app/ts/simulation/services/SimulationModeEthereumClientService.js' import { EthereumSignedTransactionWithBlockData, serialize } from '../../app/ts/types/wire-types.js' import { GetBlockReturn, JsonRpcResponse, EthereumJsonRpcRequest } from '../../app/ts/types/JsonRpc-types.js' -import { eth_getBlockByNumber_goerli_8443561_false, eth_getBlockByNumber_goerli_8443561_true, eth_simulateV1_dummy_call_result, eth_simulateV1_get_eth_balance_multicall, eth_transactionByhash0xe10c2a85168046080235fff99e2e14ef1e90c8cf5e9d675f2ca214e49e555e0f } from '../RPCResponses.js' +import { eth_getBlockByNumber_goerli_8443561_false, eth_getBlockByNumber_goerli_8443561_true, eth_simulateV1_dummy_call_result, eth_simulateV1_dummy_call_result_2calls, eth_simulateV1_get_eth_balance_multicall, eth_transactionByhash0xe10c2a85168046080235fff99e2e14ef1e90c8cf5e9d675f2ca214e49e555e0f } from '../RPCResponses.js' import { describe, should } from '../micro-should.js' import * as assert from 'assert' import { assertIsObject } from '../../app/ts/utils/typescript.js' import { stringToUint8Array } from '../../app/ts/utils/bigint.js' import { areEqualUint8Arrays } from '../../app/ts/utils/typed-arrays.js' +import { EthSimulateV1Params } from '../../app/ts/types/ethSimulate-types.js' function parseRequest(data: string) { const jsonRpcResponse = JsonRpcResponse.parse(JSON.parse(data)) @@ -31,10 +32,18 @@ class MockEthereumJSONRpcRequestHandler { return parseRequest(eth_getBlockByNumber_goerli_8443561_false) } case 'eth_simulateV1': { - if (areEqualUint8Arrays(rpcRequest.params[0]?.blockStateCalls[0]?.calls[1]?.input, stringToUint8Array('0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000244d2301cc000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000'))) { + console.log(JSON.stringify(serialize(EthSimulateV1Params, rpcRequest))) + if (areEqualUint8Arrays(rpcRequest.params[0]?.blockStateCalls[1]?.calls[0]?.input, stringToUint8Array('0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000244d2301cc000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000244d2301cc000000000000000000000000da9dfa130df4de4673b89022ee50ff26f6ea73cf00000000000000000000000000000000000000000000000000000000'))) { // get eth balance query return parseRequest(eth_simulateV1_get_eth_balance_multicall) } + if (areEqualUint8Arrays(rpcRequest.params[0]?.blockStateCalls[1]?.calls[0]?.input, stringToUint8Array('0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000244d2301cc000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000'))) { + // get eth balance query + return parseRequest(eth_simulateV1_get_eth_balance_multicall) + } + if (rpcRequest.params[0]?.blockStateCalls.length == 2) { + return parseRequest(eth_simulateV1_dummy_call_result_2calls) + } return parseRequest(eth_simulateV1_dummy_call_result) } case 'eth_getTransactionByHash': {