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: more tests #1469

Merged
merged 13 commits into from
Oct 1, 2024
5 changes: 5 additions & 0 deletions .changeset/fair-ghosts-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/test-utils": minor
---

Added a new BlockReader contract. BlockReader is a contract that can be used to test reading blocks from the evm. Used internally to test block overrides
5 changes: 5 additions & 0 deletions .changeset/grumpy-islands-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug where loadState would not validate params correctly and then fail with a confusing error if state was wrong
6 changes: 6 additions & 0 deletions .changeset/mean-ghosts-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tevm/actions": patch
"@tevm/vm": patch
---

Fixed bug with block override set missing a state root
5 changes: 5 additions & 0 deletions .changeset/tiny-dolls-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug in tevm_call json-rpc procedure where deployedBytecode, createTrace and createAccessList were not forwarded to the underlying handler. This bug only affected users using JSON-RPC directly
5 changes: 5 additions & 0 deletions .changeset/wild-bats-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug where client status would stay mining if an error gets thrown while emitting events after mining
1 change: 0 additions & 1 deletion packages/actions/src/Call/callHandler.js
Original file line number Diff line number Diff line change
@@ -107,7 +107,6 @@ export const callHandler =
const block = /** @type {import('@tevm/block').Block}*/ (evmInput.block)

await handlePendingTransactionsWarning(client, params, code, deployedBytecode)

/**
* ************
* 1 CLONE THE VM WITH BLOCK TAG
2 changes: 2 additions & 0 deletions packages/actions/src/Call/callHandlerOpts.js
Original file line number Diff line number Diff line change
@@ -67,6 +67,8 @@ export const callHandlerOpts = async (client, params) => {
opts.block = {
...opts.block,
header: {
// this isn't in the type but it needs to be here or else block overrides will fail
...{ stateRoot: block.header.stateRoot },
coinbase:
params.blockOverrideSet.coinbase !== undefined
? createAddress(params.blockOverrideSet.coinbase)
1 change: 0 additions & 1 deletion packages/actions/src/Call/callHandlerResult.js
Original file line number Diff line number Diff line change
@@ -96,6 +96,5 @@ export const callHandlerResult = (evmResult, txHash, trace, accessList) => {
if (evmResult.createdAddress) {
out.createdAddress = getAddress(evmResult.createdAddress.toString())
}

return out
}
7 changes: 4 additions & 3 deletions packages/actions/src/Call/callProcedure.js
Original file line number Diff line number Diff line change
@@ -39,9 +39,12 @@ export const callProcedure = (client) => async (request) => {
}
: {}),
...(request.params[0].code ? { code: request.params[0].code } : {}),
...(request.params[0].data ? { data: request.params[0].data } : {}),
...(request.params[0].deployedBytecode ? { deployedBytecode: request.params[0].deployedBytecode } : {}),
...(request.params[0].createTrace ? { createTrace: request.params[0].createTrace } : {}),
...(request.params[0].createAccessList ? { createAccessList: request.params[0].createAccessList } : {}),
...(request.params[0].blobVersionedHashes ? { blobVersionedHashes: request.params[0].blobVersionedHashes } : {}),
...(request.params[0].caller ? { caller: request.params[0].caller } : {}),
...(request.params[0].data ? { data: request.params[0].data } : {}),
...(request.params[0].depth ? { depth: request.params[0].depth } : {}),
...(request.params[0].gasPrice ? { gasPrice: hexToBigInt(request.params[0].gasPrice) } : {}),
...(request.params[0].gas ? { gas: hexToBigInt(request.params[0].gas) } : {}),
@@ -59,8 +62,6 @@ export const callProcedure = (client) => async (request) => {
...(request.params[0].maxPriorityFeePerGas
? { maxPriorityFeePerGas: hexToBigInt(request.params[0].maxPriorityFeePerGas) }
: {}),
// TODO add support for manually setting nonce
// ...(request.params[0].nonce ? { nonce: hexToBigInt(request.params[0].nonce) } : {}),
})
if (errors.length > 0) {
const error = /** @type {import('./TevmCallError.js').TevmCallError}*/ (errors[0])
103 changes: 101 additions & 2 deletions packages/actions/src/Call/callProcedure.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createAddress } from '@tevm/address'
import { ERC20 } from '@tevm/contract'
import { type TevmNode, createTevmNode } from '@tevm/node'
import { encodeFunctionData, numberToHex, parseEther } from '@tevm/utils'
import { BlockReader, SimpleContract } from '@tevm/test-utils'
import { type Address, type Hex, decodeFunctionResult, encodeFunctionData, numberToHex, parseEther } from '@tevm/utils'
import { beforeEach, describe, expect, it } from 'vitest'
import { deployHandler } from '../Deploy/deployHandler.js'
import { mineHandler } from '../Mine/mineHandler.js'
import { setAccountHandler } from '../SetAccount/setAccountHandler.js'
import type { CallJsonRpcRequest } from './CallJsonRpcRequest.js'
import { callProcedure } from './callProcedure.js'
@@ -77,7 +81,102 @@ describe('callProcedure', () => {
expect(response.result).toMatchSnapshot()
})

it.todo('should handle a call with block override', async () => {})
it('should handle a call with block override', async () => {
const blockReaderAddress = createAddress(1234)
await setAccountHandler(client)({
address: blockReaderAddress.toString(),
deployedBytecode: BlockReader.deployedBytecode,
})

const request: CallJsonRpcRequest = {
jsonrpc: '2.0',
method: 'tevm_call',
id: 1,
params: [
{
to: blockReaderAddress.toString(),
data: encodeFunctionData(BlockReader.read.getBlockInfo()),
},
{}, // No state override
{
number: numberToHex(1000n),
time: numberToHex(1234567890n),
coinbase: '0x1000000000000000000000000000000000000000',
baseFee: numberToHex(1n),
},
],
}

const response = await callProcedure(client)(request)
expect(response.error).toBeUndefined()
expect(response.result).toBeDefined()
expect(response.method).toBe('tevm_call')
expect(response.id).toBe(request.id as any)

const decodedResult = decodeFunctionResult({
abi: BlockReader.read.getBlockInfo.abi,
data: response.result?.rawData as Hex,
functionName: 'getBlockInfo',
})
expect(decodedResult).toEqual([1000n, 1234567890n, '0x1000000000000000000000000000000000000000', 1n])
})

it('should handle a call with tracing enabled', async () => {
const { createdAddress } = await deployHandler(client)(SimpleContract.deploy(0n))
await mineHandler(client)()

const request: CallJsonRpcRequest = {
jsonrpc: '2.0',
method: 'tevm_call',
id: 1,
params: [
{
to: createdAddress as Address,
data: encodeFunctionData(SimpleContract.write.set(100n)),
createTransaction: true,
createTrace: true,
createAccessList: true,
},
],
}

const response = await callProcedure(client)(request)
expect(response.error).toBeUndefined()
expect(response.result).toBeDefined()
expect(response.method).toBe('tevm_call')
expect(response.result?.logs).toMatchInlineSnapshot(`
[
{
"address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"data": "0x0000000000000000000000000000000000000000000000000000000000000064",
"topics": [
"0x012c78e2b84325878b1bd9d250d772cfe5bda7722d795f45036fa5e1e6e303fc",
],
},
]
`)
expect(response.id).toBe(request.id as any)
expect(response.result?.trace).toBeDefined()
expect(response.result?.trace?.structLogs).toBeInstanceOf(Array)
expect(response.result?.trace?.structLogs?.length).toBeGreaterThan(0)
expect(response.result?.trace?.structLogs[0]).toMatchInlineSnapshot(`
{
"depth": 0,
"gas": "0x1c970ac",
"gasCost": "0x6",
"op": "PUSH1",
"pc": 0,
"stack": [],
}
`)
expect(response.result?.accessList).toMatchInlineSnapshot(`
{
"0x5fbdb2315678afecb367f032d93f642f64180aa3": [
"0x0000000000000000000000000000000000000000000000000000000000000000",
],
}
`)
})

it('should handle errors from callHandler', async () => {
const request: CallJsonRpcRequest = {
3 changes: 0 additions & 3 deletions packages/actions/src/Call/handleEvmError.js
Original file line number Diff line number Diff line change
@@ -125,9 +125,6 @@ export const handleRunTxError = (e) => {
if (e.message.includes("sender doesn't have enough funds to send tx.")) {
return new InsufficientBalanceError(e.message, { cause: /** @type {any}*/ (e) })
}
if (e.message.includes("sender doesn't have enough funds to send tx. The upfront cost is")) {
return new InsufficientBalanceError(e.message, { cause: /** @type {any}*/ (e) })
}
return new InternalEvmError(e.message, { cause: /** @type {any}*/ (e) })
}
if (!(e instanceof EvmError)) {
15 changes: 15 additions & 0 deletions packages/actions/src/Call/handleEvmError.spec.ts
Original file line number Diff line number Diff line change
@@ -122,6 +122,21 @@ describe('handleRunTxError', () => {
})
})

it('should handle insufficient balance error with upfront cost', () => {
const errorMessage = "sender doesn't have enough funds to send tx. The upfront cost is 1000 wei"
const error = new Error(errorMessage)
const result = handleRunTxError(error)
expect(result).toBeInstanceOf(InsufficientBalanceError)
expect(result.cause).toBe(error)
expect(result.message).toMatchInlineSnapshot(`
"sender doesn't have enough funds to send tx. The upfront cost is 1000 wei

Docs: https://tevm.sh/reference/tevm/errors/classes/insufficientbalanceerror/
Details: sender doesn't have enough funds to send tx. The upfront cost is 1000 wei
Version: 1.1.0.next-73"
`)
})

it('should handle unknown EvmError subclasses', () => {
class UnknownEvmError extends EvmError {
constructor(message: string) {
36 changes: 36 additions & 0 deletions packages/actions/src/Contract/contractHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -363,4 +363,40 @@ describe('contractHandler', () => {
expect(result.l1BlobFee).toBeGreaterThan(0n)
expect(result.l1GasUsed).toBeGreaterThan(0n)
})

it('should handle contract revert errors', async () => {
const client = createTevmNode()
// deploy contract
expect(
(
await setAccountHandler(client)({
address: ERC20_ADDRESS,
deployedBytecode: ERC20_BYTECODE,
})
).errors,
).toBeUndefined()

const from = `0x${'11'.repeat(20)}` as const
const to = `0x${'22'.repeat(20)}` as const
const amount = 1000n

const result = await contractHandler(client)({
abi: ERC20_ABI,
functionName: 'transferFrom',
args: [from, to, amount],
to: ERC20_ADDRESS,
throwOnFail: false,
})

expect(result.errors).toBeDefined()
expect(result.errors?.length).toBe(1)
expect(result.errors?.[0]?.name).toBe('RevertError')
expect(result.errors?.[0]).toMatchInlineSnapshot(`
[RevertError: revert

Docs: https://tevm.sh/reference/tevm/errors/classes/reverterror/
Details: {"error":"revert","errorType":"EvmError"}
Version: 1.1.0.next-73]
`)
})
})
24 changes: 24 additions & 0 deletions packages/actions/src/Deploy/deployHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InvalidRequestError } from '@tevm/errors'
import { createTevmNode } from '@tevm/node'
import { EthjsAddress, type Hex, bytesToHex, parseAbi } from '@tevm/utils'
import { describe, expect, it } from 'vitest'
@@ -61,4 +62,27 @@ describe('deployHandler', () => {
).data,
).toBe(initialValue)
})

it('should throw an InvalidRequestError when constructor args are incorrect', async () => {
const client = createTevmNode({ loggingLevel: 'warn' })
const deploy = deployHandler(client)

// Attempt to deploy with incorrect argument type (string instead of uint256)
await expect(
deploy({
abi: simpleConstructorAbi,
bytecode: simpleConstructorBytecode,
args: ['not a number'],
}),
).rejects.toThrow(InvalidRequestError)

// Attempt to deploy with incorrect number of arguments
await expect(
deploy({
abi: simpleConstructorAbi,
bytecode: simpleConstructorBytecode,
args: [420n, 'extra arg'],
}),
).rejects.toThrow(InvalidRequestError)
})
})
9 changes: 8 additions & 1 deletion packages/actions/src/DumpState/dumpStateHandler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InternalError } from '@tevm/errors'
import { BaseError, InternalError } from '@tevm/errors'
import { bytesToHex } from '@tevm/utils'
import { getPendingClient } from '../internal/getPendingClient.js'
import { maybeThrowOnFail } from '../internal/maybeThrowOnFail.js'
@@ -56,6 +56,13 @@ export const dumpStateHandler =
'Unsupported state manager. Must use a TEVM state manager from `@tevm/state` package. This may indicate a bug in TEVM internal code.',
)
} catch (e) {
if (/** @type {BaseError}*/ (e)._tag) {
return maybeThrowOnFail(throwOnFail ?? true, {
state: {},
// TODO we need to strongly type errors better here
errors: [/**@type {any} */ (e)],
})
}
return maybeThrowOnFail(throwOnFail ?? true, {
state: {},
errors: [e instanceof InternalError ? e : new InternalError('Unexpected error', { cause: e })],
15 changes: 15 additions & 0 deletions packages/actions/src/DumpState/dumpStateHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -39,3 +39,18 @@ test('should dump important account info and storage', async () => {

expect(accountStorage).toEqual(storageValue)
})

test('should handle block not found', async () => {
const client = createTevmNode()
const { errors } = await dumpStateHandler(client)({ blockTag: 1n, throwOnFail: false })
expect(errors).toBeDefined()
expect(errors).toHaveLength(1)
expect(errors).toMatchInlineSnapshot(`
[
[UnknownBlock: Block number 1 does not exist

Docs: https://tevm.sh/reference/tevm/errors/classes/unknownblockerror/
Version: 1.1.0.next-73],
]
`)
})
Loading