deth
Version:
Ethereum node focused on Developer Experience
139 lines (138 loc) • 5.54 kB
JavaScript
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,
};
}
}