From 48428a55db2be895901e37b04ceb4ae821b978bb Mon Sep 17 00:00:00 2001 From: William Cory Date: Wed, 9 Oct 2024 17:22:16 -0700 Subject: [PATCH] :test_tube: Test: Add more test coverage to tevm actions --- .changeset/famous-bottles-fail.md | 6 ++ .../src/anvil/anvilImpersonateAccount.spec.ts | 72 +++++++++++++++++++ .../src/anvil/anvilLoadStateProcedure.spec.ts | 45 ++++++++++++ .../eth/ethNewBlockFilterProcedure.spec.ts | 56 +++++++++++++++ .../eth/ethSendTransactionProcedure.spec.ts | 66 +++++++++++++++++ packages/node/src/createTevmNode.js | 4 +- 6 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 .changeset/famous-bottles-fail.md create mode 100644 packages/actions/src/anvil/anvilImpersonateAccount.spec.ts create mode 100644 packages/actions/src/anvil/anvilLoadStateProcedure.spec.ts create mode 100644 packages/actions/src/eth/ethNewBlockFilterProcedure.spec.ts create mode 100644 packages/actions/src/eth/ethSendTransactionProcedure.spec.ts diff --git a/.changeset/famous-bottles-fail.md b/.changeset/famous-bottles-fail.md new file mode 100644 index 000000000..72225a8e6 --- /dev/null +++ b/.changeset/famous-bottles-fail.md @@ -0,0 +1,6 @@ +--- +"@tevm/actions": patch +"@tevm/node": patch +--- + +Fixed bug with anvil_impersonateAccount woudln't properly throw an error for invalid addresses diff --git a/packages/actions/src/anvil/anvilImpersonateAccount.spec.ts b/packages/actions/src/anvil/anvilImpersonateAccount.spec.ts new file mode 100644 index 000000000..64649808d --- /dev/null +++ b/packages/actions/src/anvil/anvilImpersonateAccount.spec.ts @@ -0,0 +1,72 @@ +import { createAddress } from '@tevm/address' +import { createTevmNode } from '@tevm/node' +import { describe, expect, it } from 'vitest' +import { anvilImpersonateAccountJsonRpcProcedure } from './anvilImpersonateAccountProcedure.js' + +describe('anvilImpersonateAccountJsonRpcProcedure', () => { + it('should impersonate an account successfully', async () => { + const client = createTevmNode() + const impersonateAccountProcedure = anvilImpersonateAccountJsonRpcProcedure(client) + + const testAddress = createAddress('0x1234567890123456789012345678901234567890') + + const request = { + method: 'anvil_impersonateAccount', + params: [testAddress.toString()], + jsonrpc: '2.0', + id: 1, + } as const + + const result = await impersonateAccountProcedure(request) + + // Check the returned result + expect(result).toEqual({ + jsonrpc: '2.0', + method: 'anvil_impersonateAccount', + id: 1, + result: null, + }) + + // Verify that the impersonated account was set in the client + const impersonatedAccount = client.getImpersonatedAccount() + expect(impersonatedAccount).toBe(testAddress.toString()) + }) + + it('should handle an invalid address', async () => { + const client = createTevmNode() + const impersonateAccountProcedure = anvilImpersonateAccountJsonRpcProcedure(client) + + const invalidAddress = '0xinvalid' + + const request = { + method: 'anvil_impersonateAccount', + params: [invalidAddress], + jsonrpc: '2.0', + id: 1, + } as const + + const result = await impersonateAccountProcedure(request) + + // Check the returned result for an error + expect(result).toMatchInlineSnapshot(` + { + "error": { + "code": -32602, + "message": "Address "0xinvalid" is invalid. + + - Address must be a hex value of 20 bytes (40 hex characters). + - Address must match its checksum counterpart. + + Version: 2.21.1", + }, + "id": 1, + "jsonrpc": "2.0", + "method": "anvil_impersonateAccount", + } + `) + + // Verify that the impersonated account was not set in the client + const impersonatedAccount = client.getImpersonatedAccount() + expect(impersonatedAccount).toBeUndefined() + }) +}) diff --git a/packages/actions/src/anvil/anvilLoadStateProcedure.spec.ts b/packages/actions/src/anvil/anvilLoadStateProcedure.spec.ts new file mode 100644 index 000000000..16b679c89 --- /dev/null +++ b/packages/actions/src/anvil/anvilLoadStateProcedure.spec.ts @@ -0,0 +1,45 @@ +import { createAddress } from '@tevm/address' +import { createTevmNode } from '@tevm/node' +import { EthjsAccount, bytesToHex } from '@tevm/utils' +import { describe, expect, it } from 'vitest' +import { anvilLoadStateJsonRpcProcedure } from './anvilLoadStateProcedure.js' + +describe('anvilLoadStateJsonRpcProcedure', () => { + it('should successfully load state', async () => { + const client = createTevmNode() + const loadStateProcedure = anvilLoadStateJsonRpcProcedure(client) + + const testAddress = createAddress('0x1234567890123456789012345678901234567890') + const account = EthjsAccount.fromAccountData({ balance: 1000n, nonce: 1n }) + + const request = { + method: 'anvil_loadState', + params: [ + { + state: { + [testAddress.toString()]: bytesToHex(account.serialize()), + }, + }, + ], + jsonrpc: '2.0', + id: 1, + } as const + + const result = await loadStateProcedure(request) + + // Check successful state loading + const vm = await client.getVm() + const loadedAccount = await vm.stateManager.getAccount(testAddress) + + expect(loadedAccount?.balance).toBe(1000n) + expect(loadedAccount?.nonce).toBe(1n) + + // Check the returned result + expect(result).toEqual({ + jsonrpc: '2.0', + method: 'anvil_loadState', + result: null, + id: 1, + }) + }) +}) diff --git a/packages/actions/src/eth/ethNewBlockFilterProcedure.spec.ts b/packages/actions/src/eth/ethNewBlockFilterProcedure.spec.ts new file mode 100644 index 000000000..66e28f258 --- /dev/null +++ b/packages/actions/src/eth/ethNewBlockFilterProcedure.spec.ts @@ -0,0 +1,56 @@ +import { Block } from '@tevm/block' +import { createTevmNode } from '@tevm/node' +import { describe, expect, it } from 'vitest' +import { ethNewBlockFilterProcedure } from './ethNewBlockFilterProcedure.js' + +describe('ethNewBlockFilterProcedure', () => { + it('should create a new block filter and return its id', async () => { + const client = createTevmNode() + const newBlockFilterProcedure = ethNewBlockFilterProcedure(client) + + const request = { + method: 'eth_newBlockFilter', + params: [], + jsonrpc: '2.0', + id: 1, + } as const + + const result = await newBlockFilterProcedure(request) + + // Check that the result contains a filter id + expect(result).toEqual({ + id: 1, + method: 'eth_newBlockFilter', + jsonrpc: '2.0', + result: expect.any(String), + }) + + const filterId = result.result as `0x${string}` + + // Verify that the filter was added to the client + const filters = client.getFilters() + expect(filters.has(filterId)).toBe(true) + + const filter = filters.get(filterId) + expect(filter).toEqual({ + id: filterId, + type: 'Block', + created: expect.any(Number), + logs: [], + tx: [], + blocks: [], + installed: {}, + err: undefined, + registeredListeners: expect.any(Array), + }) + + const vm = await client.getVm() + + // Test that the listener is working + const newBlock = Block.fromBlockData({ header: { number: 1n } }, { common: vm.common }) + filter?.registeredListeners[0]?.(newBlock) + + // Verify that the block was added to the filter + expect(filter?.blocks).toContain(newBlock) + }) +}) diff --git a/packages/actions/src/eth/ethSendTransactionProcedure.spec.ts b/packages/actions/src/eth/ethSendTransactionProcedure.spec.ts new file mode 100644 index 000000000..6b33c8c45 --- /dev/null +++ b/packages/actions/src/eth/ethSendTransactionProcedure.spec.ts @@ -0,0 +1,66 @@ +import { createAddress } from '@tevm/address' +import { createTevmNode } from '@tevm/node' +import { parseEther } from '@tevm/utils' +import { beforeEach, describe, expect, it } from 'vitest' +import { getAccountHandler } from '../GetAccount/getAccountHandler.js' +import { mineHandler } from '../Mine/mineHandler.js' +import { setAccountHandler } from '../SetAccount/setAccountHandler.js' +import { ethSendTransactionJsonRpcProcedure } from './ethSendTransactionProcedure.js' + +describe('ethSendTransactionJsonRpcProcedure', () => { + let client: ReturnType + let procedure: ReturnType + + beforeEach(() => { + client = createTevmNode() + procedure = ethSendTransactionJsonRpcProcedure(client) + }) + + it('should handle a simple transaction request', async () => { + const from = createAddress('0x1234') + const to = createAddress('0x5678') + const value = parseEther('1') + + // Set up the sender's account + await setAccountHandler(client)({ + address: from.toString(), + balance: parseEther('10'), + }) + + const request = { + method: 'eth_sendTransaction', + params: [ + { + from: from.toString(), + to: to.toString(), + value: `0x${value.toString(16)}`, + gas: '0x5208', // 21000 + }, + ], + jsonrpc: '2.0', + id: 1, + } as const + + const result = await procedure(request) + + // Check the response structure + expect(result).toEqual({ + jsonrpc: '2.0', + id: 1, + method: 'eth_sendTransaction', + result: expect.stringMatching(/^0x[a-fA-F0-9]{64}$/), // Transaction hash + }) + + // Mine the block to process the transaction + await mineHandler(client)() + + // Verify the balance change + const toAccount = await getAccountHandler(client)({ address: to.toString() }) + expect(toAccount.balance).toBe(value) + + // Verify the sender's balance change (should be less than 10 - 1 due to gas costs) + const fromAccount = await getAccountHandler(client)({ address: from.toString() }) + expect(fromAccount.balance).toBeLessThan(parseEther('9')) + expect(fromAccount.balance).toBeGreaterThan(parseEther('8.9')) + }) +}) diff --git a/packages/node/src/createTevmNode.js b/packages/node/src/createTevmNode.js index 358b15945..edfd320eb 100644 --- a/packages/node/src/createTevmNode.js +++ b/packages/node/src/createTevmNode.js @@ -5,7 +5,7 @@ import { createLogger } from '@tevm/logger' import { ReceiptsManager, createMapDb } from '@tevm/receipt-manager' import { createStateManager } from '@tevm/state' import { TxPool } from '@tevm/txpool' -import { KECCAK256_RLP, bytesToHex, hexToBigInt, keccak256 } from '@tevm/utils' +import { KECCAK256_RLP, bytesToHex, getAddress, hexToBigInt, keccak256 } from '@tevm/utils' import { createVm } from '@tevm/vm' import { DEFAULT_CHAIN_ID } from './DEFAULT_CHAIN_ID.js' import { GENESIS_STATE } from './GENESIS_STATE.js' @@ -309,7 +309,7 @@ export const createTevmNode = (options = {}) => { * returns {void} */ const setImpersonatedAccount = (address) => { - impersonatedAccount = address + impersonatedAccount = address && getAddress(address) } await readyPromise const oldVm = await vmPromise