deth
Version:
Ethereum node focused on Developer Experience
142 lines (141 loc) • 5.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const primitives_1 = require("./primitives");
const model_1 = require("./model");
const TestVM_1 = require("./vm/TestVM");
const TestChainOptions_1 = require("./TestChainOptions");
const errors_1 = require("./errors");
const stepsLoggers_1 = require("./debugger/stepsLoggers");
const SnapshotObject_1 = require("./vm/storage/SnapshotObject");
const lodash_1 = require("lodash");
const ethereumjs_tx_1 = require("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.
*/
class TestChain {
constructor(logger, options) {
this.logger = logger;
this.options = new SnapshotObject_1.SnapshotObject(TestChainOptions_1.getTestChainOptionsWithDefaults(options), lodash_1.cloneDeep);
this.tvm = new TestVM_1.TestVM(this.options.value);
}
async init() {
await this.tvm.init();
this.tvm.installStepHook(stepsLoggers_1.eventLogger(this.logger));
this.tvm.installStepHook(stepsLoggers_1.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 primitives_1.bnToQuantity(this.options.value.defaultGasPrice);
}
async getBalance(address, blockTag) {
if (blockTag !== 'latest') {
throw errors_1.unsupportedBlockTag('getBalance', blockTag, ['latest']);
}
return this.tvm.getBalance(address);
}
async getTransactionCount(address, blockTag) {
if (blockTag !== 'latest' && blockTag !== 'pending') {
throw errors_1.unsupportedBlockTag('getTransactionCount', blockTag, ['latest', 'pending']);
}
// TODO: handle pending better
return this.tvm.getNonce(address);
}
async getCode(address, blockTag) {
if (blockTag !== 'latest') {
throw errors_1.unsupportedBlockTag('getCode', blockTag, ['latest']);
}
return this.tvm.getCode(address);
}
async getStorageAt(address, position, blockTag) {
// @TODO: always assumes blockTag === latest
return primitives_1.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 errors_1.unsupportedBlockTag('call', blockTag, ['latest']);
}
const tx = model_1.toFakeTransaction({
...transactionRequest,
gas: primitives_1.bnToQuantity(this.options.value.blockGasLimit),
});
const result = await this.tvm.runIsolatedTransaction(tx, this.options.value.clockSkew);
// TODO: handle errors
return primitives_1.bufferToHexData(result.execResult.returnValue);
}
// @NOTE: this is very simplified implementation
async estimateGas(transactionRequest) {
if (!transactionRequest.gas) {
transactionRequest.gas = primitives_1.bnToQuantity(this.options.value.blockGasLimit);
}
const tx = model_1.toFakeTransaction(transactionRequest);
const result = await this.tvm.runIsolatedTransaction(tx, this.options.value.clockSkew);
// TODO: handle errors
return primitives_1.bnToQuantity(result.gasUsed);
}
async getBlock(blockTagOrHash, includeTransactions) {
if (blockTagOrHash === 'pending') {
throw errors_1.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 errors_1.transactionNotFound(transactionHash);
}
return transaction;
}
getTransactionReceipt(transactionHash) {
return this.tvm.getTransactionReceipt(transactionHash);
}
async getLogs(filter) {
throw errors_1.unsupportedOperation('getLogs');
}
parseTx(signedTransaction) {
var _a, _b;
const tx = new ethereumjs_tx_1.Transaction(signedTransaction, { common: this.tvm.vm._common });
return {
to: ((_a = tx.to) === null || _a === void 0 ? void 0 : _a.length) > 0 ? primitives_1.bufferToAddress(tx.to) : undefined,
from: primitives_1.bufferToAddress(tx.from),
data: ((_b = tx.data) === null || _b === void 0 ? void 0 : _b.length) > 0 ? primitives_1.bufferToHexData(tx.data) : undefined,
};
}
}
exports.TestChain = TestChain;