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(actions): more test coverage #1455

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
29 changes: 29 additions & 0 deletions packages/actions/src/Mine/processTx.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createTevmNode } from '@tevm/node'
import { TransactionFactory } from '@tevm/tx'
import { PREFUNDED_PRIVATE_KEYS, hexToBytes } from '@tevm/utils'
import { BlockBuilder } from '@tevm/vm'
import { describe, expect, it } from 'vitest'
import { processTx } from './processTx.js'

describe('processTx', () => {
it('should process a transaction successfully', async () => {
const client = createTevmNode()
const vm = await client.getVm()
const parentBlock = await client.getVm().then((vm) => vm.blockchain.getCanonicalHeadBlock())
const blockBuilder = new BlockBuilder(vm, { parentBlock })
const receipts: any[] = []

const tx = TransactionFactory.fromTxData({
to: '0x1234567890123456789012345678901234567890',
value: 1000n,
gasLimit: 21000n,
gasPrice: 10n,
}).sign(hexToBytes(PREFUNDED_PRIVATE_KEYS[0]))

await processTx(client, tx, blockBuilder, receipts)

expect(receipts).toHaveLength(1)
expect(receipts[0]).toBeDefined()
expect(receipts[0].status).toBe(1)
})
})
134 changes: 134 additions & 0 deletions packages/actions/src/SetAccount/validateSetAccountParams.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {
InvalidAddressError,
InvalidBalanceError,
InvalidDeployedBytecodeError,
InvalidNonceError,
InvalidRequestError,
InvalidStorageRootError,
} from '@tevm/errors'
import { describe, expect, it } from 'vitest'
import { validateSetAccountParams } from './validateSetAccountParams.js'

describe('validateSetAccountParams', () => {
it('should return no errors for valid input', () => {
const validParams = {
address: '0x1234567890123456789012345678901234567890' as const,
nonce: 1n,
balance: 100n,
deployedBytecode: '0x1234' as const,
storageRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' as const,
state: { '0x01': '0x02' } as const,
}
const errors = validateSetAccountParams(validParams)
expect(errors).toHaveLength(0)
})

it('should return InvalidAddressError for invalid address', () => {
const invalidParams = {
address: '0x123' as const, // Too short
}
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidAddressError)
})

it('should return InvalidNonceError for invalid nonce', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
nonce: -1n,
}
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidNonceError)
})

it('should return InvalidBalanceError for invalid balance', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
balance: -100n,
}
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidBalanceError)
})

it('should return InvalidDeployedBytecodeError for invalid deployedBytecode', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
deployedBytecode: '0x123' as const, // Odd number of characters
}
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidDeployedBytecodeError)
})

it('should return InvalidStorageRootError for invalid storageRoot', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
storageRoot: '0x123' as const, // Too short
}
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidStorageRootError)
})

it('should return InvalidRequestError for invalid state', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
state: 'what is this',
}
// @ts-expect-error - Intentionally invalid state
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidRequestError)
})

it('should return InvalidRequestError for invalid stateDiff', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
stateDiff: true,
}
// @ts-expect-error - Intentionally invalid stateDiff
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidRequestError)
})

it('should return multiple errors for multiple invalid fields', () => {
const invalidParams = {
address: '0x123' as const, // Invalid
nonce: -1n, // Invalid
balance: -100n, // Invalid
deployedBytecode: '0x123' as const, // Invalid
storageRoot: '0x123' as const, // Invalid
state: { '0x123': 'not a hex value' }, // Invalid
}
// @ts-expect-error - Intentionally invalid params
const errors = validateSetAccountParams(invalidParams)
expect(errors.length).toBeGreaterThan(1)
expect(errors.some((e) => e instanceof InvalidAddressError)).toBe(true)
expect(errors.some((e) => e instanceof InvalidNonceError)).toBe(true)
expect(errors.some((e) => e instanceof InvalidBalanceError)).toBe(true)
expect(errors.some((e) => e instanceof InvalidDeployedBytecodeError)).toBe(true)
expect(errors.some((e) => e instanceof InvalidStorageRootError)).toBe(true)
})

it('should return InvalidRequestError for invalid throwOnFail', () => {
const invalidParams = {
address: '0x1234567890123456789012345678901234567890' as const,
throwOnFail: 'not a boolean',
}
// @ts-expect-error - Intentionally invalid throwOnFail
const errors = validateSetAccountParams(invalidParams)
expect(errors).toHaveLength(1)
expect(errors[0]).toBeInstanceOf(InvalidRequestError)
})

it('should handle undefined optional fields', () => {
const params = {
address: '0x1234567890123456789012345678901234567890' as const,
}
const errors = validateSetAccountParams(params)
expect(errors).toHaveLength(0)
})
})
12 changes: 6 additions & 6 deletions packages/actions/src/eth/EthJsonRpcRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export type EthGetTransactionByBlockNumberAndIndexJsonRpcRequest = JsonRpcReques
/**
* JSON-RPC request for `eth_getTransactionReceipt` procedure
*/
export type EthGetTransactionReceiptJsonRpcRequest = JsonRpcRequest<'eth_getTransactionReceipt', [txHash: Hex]>
export type EthGetTransactionReceiptJsonRpcRequest = JsonRpcRequest<'eth_getTransactionReceipt', readonly [txHash: Hex]>
// eth_getUncleByBlockHashAndIndex
/**
* JSON-RPC request for `eth_getUncleByBlockHashAndIndex` procedure
Expand Down Expand Up @@ -240,19 +240,19 @@ export type EthSendRawTransactionJsonRpcRequest = JsonRpcRequest<'eth_sendRawTra
/**
* JSON-RPC request for `eth_sendTransaction` procedure
*/
export type EthSendTransactionJsonRpcRequest = JsonRpcRequest<'eth_sendTransaction', [tx: JsonRpcTransaction]>
export type EthSendTransactionJsonRpcRequest = JsonRpcRequest<'eth_sendTransaction', readonly [tx: JsonRpcTransaction]>
// eth_sign
/**
* JSON-RPC request for `eth_sign` procedure
*/
export type EthSignJsonRpcRequest = JsonRpcRequest<'eth_sign', [address: Address, message: Hex]>
export type EthSignJsonRpcRequest = JsonRpcRequest<'eth_sign', readonly [address: Address, message: Hex]>
// eth_signTransaction
/**
* JSON-RPC request for `eth_signTransaction` procedure
*/
export type EthSignTransactionJsonRpcRequest = JsonRpcRequest<
'eth_signTransaction',
[
readonly [
{
from: Address
to?: Address
Expand All @@ -274,7 +274,7 @@ export type EthSyncingJsonRpcRequest = JsonRpcRequest<'eth_syncing', readonly []
/**
* JSON-RPC request for `eth_newFilter` procedure
*/
export type EthNewFilterJsonRpcRequest = JsonRpcRequest<'eth_newFilter', [SerializeToJson<FilterParams>]>
export type EthNewFilterJsonRpcRequest = JsonRpcRequest<'eth_newFilter', readonly [SerializeToJson<FilterParams>]>
// eth_newBlockFilter
/**
* JSON-RPC request for `eth_newBlockFilter` procedure
Expand All @@ -292,7 +292,7 @@ export type EthNewPendingTransactionFilterJsonRpcRequest = JsonRpcRequest<
/**
* JSON-RPC request for `eth_uninstallFilter` procedure
*/
export type EthUninstallFilterJsonRpcRequest = JsonRpcRequest<'eth_uninstallFilter', [filterId: Hex]>
export type EthUninstallFilterJsonRpcRequest = JsonRpcRequest<'eth_uninstallFilter', readonly [filterId: Hex]>

export type EthJsonRpcRequest =
| EthAccountsJsonRpcRequest
Expand Down
26 changes: 26 additions & 0 deletions packages/actions/src/eth/ethAccountsProcedure.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest'
import { ethAccountsProcedure } from './ethAccountsProcedure.js'
import { testAccounts } from './utils/testAccounts.js'

describe('ethAccountsProcedure', () => {
it('should return the correct JSON-RPC response', async () => {
const req = { id: 1, method: 'eth_accounts', jsonrpc: '2.0' } as const
const response = await ethAccountsProcedure(testAccounts)(req)
expect(response).toEqual({
id: 1,
jsonrpc: '2.0',
method: 'eth_accounts',
result: testAccounts.map((account) => account.address),
})
})

it('should handle requests without an id', async () => {
const req = { method: 'eth_accounts', jsonrpc: '2.0' } as const
const response = await ethAccountsProcedure(testAccounts)(req)
expect(response).toEqual({
jsonrpc: '2.0',
method: 'eth_accounts',
result: testAccounts.map((account) => account.address),
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createTevmNode } from '@tevm/node'
import { bytesToHex } from '@tevm/utils'
import { describe, expect, it } from 'vitest'
import { callHandler } from '../Call/callHandler.js'
import { mineHandler } from '../Mine/mineHandler.js'
import { ethGetTransactionByBlockHashAndIndexJsonRpcProcedure } from './ethGetTransactionByBlockHashAndIndexProcedure.js'

describe('ethGetTransactionByBlockHashAndIndexJsonRpcProcedure', () => {
it('should return the transaction if found', async () => {
const client = createTevmNode()
const to = `0x${'69'.repeat(20)}` as const

// Send a transaction
const callResult = await callHandler(client)({
createTransaction: true,
to,
value: 420n,
skipBalance: true,
})

// Mine a block to include the transaction
await mineHandler(client)({})

// Get the block hash
const block = await client.getVm().then((vm) => vm.blockchain.getCanonicalHeadBlock())
const blockHash = bytesToHex(block.hash())

const request = {
id: 1,
method: 'eth_getTransactionByBlockHashAndIndex',
params: [blockHash, '0x0'],
jsonrpc: '2.0',
} as const

const procedure = ethGetTransactionByBlockHashAndIndexJsonRpcProcedure(client)
const response = await procedure(request)

expect(response.result?.hash).toEqual(callResult.txHash)
expect(response).toMatchObject({
id: 1,
jsonrpc: '2.0',
method: 'eth_getTransactionByBlockHashAndIndex',
result: {
accessList: [],
blobVersionedHashes: undefined,
blockHash: expect.stringMatching(/^0x[a-fA-F0-9]{64}$/),
blockNumber: '0x1',
data: '0x',
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
gas: '0x5a3c',
gasPrice: '0x7',
hash: '0x5e5b342fae6b13548e62c3038078915397ebd2406a8c67afd276e8dc84ebba80',
maxFeePerBlobGas: undefined,
maxFeePerGas: '0x7',
maxPriorityFeePerGas: '0x0',
nonce: '0x0',
to: '0x6969696969696969696969696969696969696969',
transactionIndex: '0x0',
type: '0x2',
value: '0x1a4',
},
})
})

it('should return an error if the transaction is not found', async () => {
const client = createTevmNode()

// Mine a block without any transactions
await mineHandler(client)({})

// Get the block hash
const block = await client.getVm().then((vm) => vm.blockchain.getCanonicalHeadBlock())
const blockHash = bytesToHex(block.hash())

const request = {
id: 1,
method: 'eth_getTransactionByBlockHashAndIndex',
params: [blockHash, '0x0'],
jsonrpc: '2.0',
} as const

const procedure = ethGetTransactionByBlockHashAndIndexJsonRpcProcedure(client)
const response = await procedure(request)

expect(response).toEqual({
id: 1,
jsonrpc: '2.0',
method: 'eth_getTransactionByBlockHashAndIndex',
error: {
code: -32602,
message: 'Transaction not found',
},
})
})
})
Loading
Loading