Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Local Testing

Use Tevm Node for local testing of smart contracts and transactions. For background, see the Smart Contract Testing Guide.

Basic Test Setup

import { createTevmNode } from 'tevm'
import { createImpersonatedTx } from 'tevm/tx'
import { expect, test } from 'vitest' // or jest, mocha, etc.
 
test('Basic ETH transfer', async () => {
  const node = createTevmNode({ miningConfig: { type: 'auto' } })
  await node.ready()
 
  const vm = await node.getVm()
 
  const tx = createImpersonatedTx({
    from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
    value: 1000000000000000000n, // 1 ETH
  })
 
  const result = await vm.runTx({ tx })
  expect(result.execResult.exceptionError).toBeUndefined()
 
  const account = await vm.stateManager.getAccount(tx.to)
  expect(account.balance).toBe(1000000000000000000n)
})

See the EVM Execution Model for more on transaction execution.

Contract Testing

Deployment & Interaction

See the Contract Deployment Guide for background.

import { Contract } from 'tevm/contract'
import { bytecode, abi } from './MyContract.json'
 
test('Deploy and interact with contract', async () => {
  const node = createTevmNode()
  await node.ready()
  const vm = await node.getVm()
 
  const deployTx = createImpersonatedTx({ data: bytecode })
  const result = await vm.runTx({ tx: deployTx })
  expect(result.execResult.exceptionError).toBeUndefined()
 
  const contractAddress = result.createdAddress
  expect(contractAddress).toBeDefined()
 
  const contract = new Contract(contractAddress, abi)
 
  const callResult = await contract.read.getValue()
  expect(callResult).toBe(expectedValue)
 
  const tx = await contract.write.setValue([newValue])
  const txResult = await vm.runTx({ tx })
  expect(txResult.execResult.exceptionError).toBeUndefined()
 
  const updatedValue = await contract.read.getValue()
  expect(updatedValue).toBe(newValue)
})

Event Testing

See the Events and Logs Guide for background.

test('Contract events', async () => {
  const node = createTevmNode()
  await node.ready()
 
  const contract = await deployContract(node)
 
  node.setFilter({
    id: '0x1',
    address: contract.address,
    topics: [contract.interface.getEventTopic('ValueChanged')],
  })
 
  const tx = await contract.write.setValue([123])
  await vm.runTx({ tx })
 
  const receipts = await node.getReceiptsManager()
  const logs = await receipts.getLogs({
    fromBlock: 0n,
    toBlock: 'latest',
    address: contract.address,
  })
 
  expect(logs.length).toBe(1)
  expect(logs[0].topics[0]).toBe(contract.interface.getEventTopic('ValueChanged'))
})

Complex Scenarios

State Management

See the Ethereum State Guide.

test('Complex state changes', async () => {
  const node = createTevmNode()
  await node.ready()
  const vm = await node.getVm()
 
  await vm.stateManager.checkpoint()
 
  try {
    await performStateChanges(vm)
 
    const intermediateState = await getState(vm)
    expect(intermediateState).toMatchSnapshot()
 
    await performMoreChanges(vm)
    await vm.stateManager.commit()
  } catch (error) {
    await vm.stateManager.revert()
    throw error
  }
})

Fork Testing

See the Forking Guide.

test('Mainnet fork testing', async () => {
  const node = createTevmNode({
    fork: {
      transport: http('https://mainnet.infura.io/v3/YOUR-KEY'),
      blockTag: 17_000_000n,
    },
  })
  await node.ready()
 
  node.setImpersonatedAccount('0x28C6c06298d514Db089934071355E5743bf21d60')
 
  const uniswap = new Contract(UNISWAP_ADDRESS, UNISWAP_ABI)
  const tx = await uniswap.write.swapExactTokensForTokens([/* ... */])
 
  const result = await vm.runTx({ tx })
  expect(result.execResult.exceptionError).toBeUndefined()
})

Time-based Testing

See the Block Time Guide.

test('Time-dependent behavior', async () => {
  const node = createTevmNode({ miningConfig: { type: 'interval', interval: 1000 } })
  await node.ready()
  const vm = await node.getVm()
 
  const contract = await deployTimeLock(vm)
 
  // Should fail
  let tx = await contract.write.withdraw()
  let result = await vm.runTx({ tx })
  expect(result.execResult.exceptionError).toBeDefined()
 
  // Advance time
  for (let i = 0; i < 100; i++) {
    await vm.blockchain.putBlock(createBlock({ timestamp: Date.now() + i * 1000 }))
  }
 
  // Should succeed
  tx = await contract.write.withdraw()
  result = await vm.runTx({ tx })
  expect(result.execResult.exceptionError).toBeUndefined()
})

Testing Utilities

Account Management

See the Accounts Guide.

async function setupAccounts(vm) {
  const accounts = [
    '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
  ]
 
  for (const address of accounts) {
    await vm.stateManager.putAccount(address, {
      nonce: 0n,
      balance: 10000000000000000000n, // 10 ETH
    })
  }
 
  return accounts
}

Transaction Helpers

See the Transaction Types Guide.

async function sendEth(vm, from, to, value) {
  const tx = createImpersonatedTx({ from, to, value })
  return vm.runTx({ tx })
}
 
async function deployContract(vm, bytecode, args = []) {
  const tx = createImpersonatedTx({ data: bytecode + encodeConstructor(args) })
  const result = await vm.runTx({ tx })
  return result.createdAddress
}

Replaying Contracts with Shadow Events

import { createTevmNode } from 'tevm'
import { http } from 'viem'
 
const node = createTevmNode({
  fork: { transport: http('https://mainnet.infura.io/v3/YOUR-KEY') },
})
 
const receipt = await node.request({
  method: 'eth_getTransactionReceipt',
  params: ['0x...'], // Original tx hash
})
 
const block = await node.request({
  method: 'eth_getBlockByNumber',
  params: [(receipt.blockNumber - 1n).toString(16), true],
})
 
// Replay all transactions before our target tx
for (let i = 0; i < receipt.transactionIndex; i++) {
  await node.getVm().runTx({ tx: block.transactions[i] })
}
 
// Deploy modified contract with new event
const modifiedBytecode = '0x...'
await node.setAccount({
  address: receipt.contractAddress,
  deployedBytecode: modifiedBytecode,
})
 
// Run the target transaction (no createImpersonatedTx — these come from the block)
const result = await node.getVm().runTx({
  tx: block.transactions[receipt.transactionIndex],
})
 
console.log(result.execResult.logs)

Estimating Gas for Token Approval

import { createTevmNode } from 'tevm/node'
import { createImpersonatedTx } from 'tevm/tx'
import { encodeFunctionData } from 'viem'
 
const node = createTevmNode()
const vm = await node.getVm()
 
const approveTx = createImpersonatedTx({
  to: tokenAddress,
  data: encodeFunctionData({
    abi: erc20ABI,
    functionName: 'approve',
    args: [spenderAddress, amount],
  }),
})
 
const result = await vm.runTx({ tx: approveTx })
console.log('Gas used:', result.execResult.executionGasUsed)
 
const transferFromTx = createImpersonatedTx({
  to: tokenAddress,
  data: encodeFunctionData({
    abi: erc20ABI,
    functionName: 'transferFrom',
    args: [ownerAddress, recipientAddress, amount],
  }),
})
 
const transferResult = await vm.runTx({ tx: transferFromTx })
console.log('TransferFrom gas:', transferResult.execResult.executionGasUsed)

Related Topics