Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧪 Test: Add more test coverage to tevm actions #1476

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/famous-bottles-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tevm/actions": patch
"@tevm/node": patch
---

Fixed bug with anvil_impersonateAccount woudln't properly throw an error for invalid addresses
72 changes: 72 additions & 0 deletions packages/actions/src/anvil/anvilImpersonateAccount.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
45 changes: 45 additions & 0 deletions packages/actions/src/anvil/anvilLoadStateProcedure.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
})
})
})
56 changes: 56 additions & 0 deletions packages/actions/src/eth/ethNewBlockFilterProcedure.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
66 changes: 66 additions & 0 deletions packages/actions/src/eth/ethSendTransactionProcedure.spec.ts
Original file line number Diff line number Diff line change
@@ -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<typeof createTevmNode>
let procedure: ReturnType<typeof ethSendTransactionJsonRpcProcedure>

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'))
})
})
4 changes: 2 additions & 2 deletions packages/node/src/createTevmNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
Loading