UNPKG

deth

Version:

Ethereum node focused on Developer Experience

139 lines (138 loc) 5.54 kB
import { bnToQuantity, bufferToHexData, bufferToAddress } from './primitives'; import { toFakeTransaction, } from './model'; import { TestVM } from './vm/TestVM'; import { getTestChainOptionsWithDefaults } from './TestChainOptions'; import { transactionNotFound, unsupportedBlockTag, unsupportedOperation } from './errors'; import { eventLogger, revertLogger } from './debugger/stepsLoggers'; import { SnapshotObject } from './vm/storage/SnapshotObject'; import { cloneDeep } from 'lodash'; import { Transaction } from 'ethereumjs-tx'; /** * TestChain wraps TestVM and provides an API suitable for use by a provider. * It is separate from the provider so that there can be many provider instances * using the same TestChain instance. */ export class TestChain { constructor(logger, options) { this.logger = logger; this.options = new SnapshotObject(getTestChainOptionsWithDefaults(options), cloneDeep); this.tvm = new TestVM(this.options.value); } async init() { await this.tvm.init(); this.tvm.installStepHook(eventLogger(this.logger)); this.tvm.installStepHook(revertLogger(this.logger)); } makeSnapshot() { this.options.makeSnapshot(); return this.tvm.makeSnapshot(); } revertToSnapshot(id) { this.options.revert(id); return this.tvm.revertToSnapshot(id); } skewClock(delta) { this.options.value.clockSkew += delta; } startAutoMining() { this.options.value.autoMining = true; } stopAutoMining() { this.options.value.autoMining = false; } async mineBlock() { return this.tvm.mineBlock(this.options.value.clockSkew); } async getBlockNumber() { return this.tvm.getBlockNumber(); } getGasPrice() { return bnToQuantity(this.options.value.defaultGasPrice); } async getBalance(address, blockTag) { if (blockTag !== 'latest') { throw unsupportedBlockTag('getBalance', blockTag, ['latest']); } return this.tvm.getBalance(address); } async getTransactionCount(address, blockTag) { if (blockTag !== 'latest' && blockTag !== 'pending') { throw unsupportedBlockTag('getTransactionCount', blockTag, ['latest', 'pending']); } // TODO: handle pending better return this.tvm.getNonce(address); } async getCode(address, blockTag) { if (blockTag !== 'latest') { throw unsupportedBlockTag('getCode', blockTag, ['latest']); } return this.tvm.getCode(address); } async getStorageAt(address, position, blockTag) { // @TODO: always assumes blockTag === latest return bufferToHexData(this.tvm.state.value.stateManger.getContractStorage(address, position)); } async sendTransaction(signedTransaction) { this.logger.logTransaction(this.parseTx(signedTransaction)); const hash = await this.tvm.addPendingTransaction(signedTransaction); if (this.options.value.autoMining) { await this.tvm.mineBlock(this.options.value.clockSkew); } return hash; } async call(transactionRequest, blockTag) { if (blockTag !== 'latest') { throw unsupportedBlockTag('call', blockTag, ['latest']); } const tx = toFakeTransaction({ ...transactionRequest, gas: bnToQuantity(this.options.value.blockGasLimit), }); const result = await this.tvm.runIsolatedTransaction(tx, this.options.value.clockSkew); // TODO: handle errors return bufferToHexData(result.execResult.returnValue); } // @NOTE: this is very simplified implementation async estimateGas(transactionRequest) { if (!transactionRequest.gas) { transactionRequest.gas = bnToQuantity(this.options.value.blockGasLimit); } const tx = toFakeTransaction(transactionRequest); const result = await this.tvm.runIsolatedTransaction(tx, this.options.value.clockSkew); // TODO: handle errors return bnToQuantity(result.gasUsed); } async getBlock(blockTagOrHash, includeTransactions) { if (blockTagOrHash === 'pending') { throw unsupportedBlockTag('call', blockTagOrHash); } const block = blockTagOrHash === 'latest' ? await this.tvm.getLatestBlock() : await this.tvm.getBlock(blockTagOrHash); if (!includeTransactions) { return block; } const transactions = block.transactions.map(tx => this.getTransaction(tx)); return { ...block, transactions }; } getTransaction(transactionHash) { const transaction = this.tvm.getTransaction(transactionHash); if (!transaction) { throw transactionNotFound(transactionHash); } return transaction; } getTransactionReceipt(transactionHash) { return this.tvm.getTransactionReceipt(transactionHash); } async getLogs(filter) { throw unsupportedOperation('getLogs'); } parseTx(signedTransaction) { var _a, _b; const tx = new Transaction(signedTransaction, { common: this.tvm.vm._common }); return { to: ((_a = tx.to) === null || _a === void 0 ? void 0 : _a.length) > 0 ? bufferToAddress(tx.to) : undefined, from: bufferToAddress(tx.from), data: ((_b = tx.data) === null || _b === void 0 ? void 0 : _b.length) > 0 ? bufferToHexData(tx.data) : undefined, }; } }