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)
