@polygon-hermez/vm
Version:
An Ethereum VM implementation
303 lines • 11.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const merkle_patricia_tree_1 = require("merkle-patricia-tree");
const ethereumjs_util_1 = require("ethereumjs-util");
const blockchain_1 = __importDefault(require("@ethereumjs/blockchain"));
const common_1 = __importStar(require("@ethereumjs/common"));
const index_1 = require("./state/index");
const runCode_1 = __importDefault(require("./runCode"));
const runCall_1 = __importDefault(require("./runCall"));
const runTx_1 = __importDefault(require("./runTx"));
const runBlock_1 = __importDefault(require("./runBlock"));
const buildBlock_1 = __importDefault(require("./buildBlock"));
const opcodes_1 = require("./evm/opcodes");
const precompiles_1 = require("./evm/precompiles");
const runBlockchain_1 = __importDefault(require("./runBlockchain"));
const AsyncEventEmitter = require('async-eventemitter');
const util_1 = require("util");
// very ugly way to detect if we are running in a browser
const isBrowser = new Function('try {return this===window;}catch(e){ return false;}');
let mcl;
let mclInitPromise;
if (!isBrowser()) {
mcl = require('mcl-wasm');
mclInitPromise = mcl.init(mcl.BLS12_381);
}
/**
* Execution engine which can be used to run a blockchain, individual
* blocks, individual transactions, or snippets of EVM bytecode.
*
* This class is an AsyncEventEmitter, please consult the README to learn how to use it.
*/
class VM extends AsyncEventEmitter {
/**
* VM async constructor. Creates engine instance and initializes it.
*
* @param opts VM engine constructor options
*/
static async create(opts = {}) {
const vm = new this(opts);
await vm.init();
return vm;
}
/**
* Instantiates a new {@link VM} Object.
* @param opts
*/
constructor(opts = {}) {
var _a, _b, _c, _d;
super();
this._isInitialized = false;
/**
* VM is run in DEBUG mode (default: false)
* Taken from DEBUG environment variable
*
* Safeguards on debug() calls are added for
* performance reasons to avoid string literal evaluation
* @hidden
*/
this.DEBUG = false;
this._opts = opts;
// Throw on chain or hardfork options removed in latest major release
// to prevent implicit chain setup on a wrong chain
if ('chain' in opts || 'hardfork' in opts) {
throw new Error('Chain/hardfork options are not allowed any more on initialization');
}
if (opts.common) {
// Supported EIPs
const supportedEIPs = [1559, 2315, 2537, 2565, 2718, 2929, 2930, 3198, 3529, 3541, 3607, 3855];
for (const eip of opts.common.eips()) {
if (!supportedEIPs.includes(eip)) {
throw new Error(`EIP-${eip} is not supported by the VM`);
}
}
this._common = opts.common;
}
else {
const DEFAULT_CHAIN = common_1.Chain.Mainnet;
const supportedHardforks = [
'chainstart',
'homestead',
'dao',
'tangerineWhistle',
'spuriousDragon',
'byzantium',
'constantinople',
'petersburg',
'istanbul',
'muirGlacier',
'berlin',
'arrowGlacier',
];
this._common = new common_1.default({
chain: DEFAULT_CHAIN,
supportedHardforks,
});
}
this._common.on('hardforkChanged', () => {
this._opcodes = (0, opcodes_1.getOpcodesForHF)(this._common);
});
// Set list of opcodes based on HF
// TODO: make this EIP-friendly
this._opcodes = (0, opcodes_1.getOpcodesForHF)(this._common);
if (opts.stateManager) {
this.stateManager = opts.stateManager;
}
else {
const trie = (_a = opts.state) !== null && _a !== void 0 ? _a : new merkle_patricia_tree_1.CheckpointTrie();
this.stateManager = new index_1.DefaultStateManager({
trie,
common: this._common,
});
}
this.blockchain = (_b = opts.blockchain) !== null && _b !== void 0 ? _b : new blockchain_1.default({ common: this._common });
this._allowUnlimitedContractSize = (_c = opts.allowUnlimitedContractSize) !== null && _c !== void 0 ? _c : false;
if (opts.hardforkByBlockNumber !== undefined && opts.hardforkByTD !== undefined) {
throw new Error(`The hardforkByBlockNumber and hardforkByTD options can't be used in conjunction`);
}
this._hardforkByBlockNumber = (_d = opts.hardforkByBlockNumber) !== null && _d !== void 0 ? _d : false;
this._hardforkByTD = opts.hardforkByTD;
if (this._common.isActivatedEIP(2537)) {
if (isBrowser()) {
throw new Error('EIP-2537 is currently not supported in browsers');
}
else {
this._mcl = mcl;
}
}
// Safeguard if "process" is not available (browser)
if (process !== undefined && process.env.DEBUG) {
this.DEBUG = true;
}
// We cache this promisified function as it's called from the main execution loop, and
// promisifying each time has a huge performance impact.
this._emit = (0, util_1.promisify)(this.emit.bind(this));
}
async init() {
if (this._isInitialized) {
return;
}
await this.blockchain.initPromise;
if (this._opts.activatePrecompiles && !this._opts.stateManager) {
await this.stateManager.checkpoint();
// put 1 wei in each of the precompiles in order to make the accounts non-empty and thus not have them deduct `callNewAccount` gas.
await Promise.all(Object.keys(precompiles_1.precompiles)
.map((k) => new ethereumjs_util_1.Address(Buffer.from(k, 'hex')))
.map(async (address) => {
const account = ethereumjs_util_1.Account.fromAccountData({ balance: 1 });
await this.stateManager.putAccount(address, account);
}));
await this.stateManager.commit();
}
if (this._common.isActivatedEIP(2537)) {
if (isBrowser()) {
throw new Error('EIP-2537 is currently not supported in browsers');
}
else {
const mcl = this._mcl;
await mclInitPromise; // ensure that mcl is initialized.
mcl.setMapToMode(mcl.IRTF); // set the right map mode; otherwise mapToG2 will return wrong values.
mcl.verifyOrderG1(1); // subgroup checks for G1
mcl.verifyOrderG2(1); // subgroup checks for G2
}
}
this._isInitialized = true;
}
/**
* Processes blocks and adds them to the blockchain.
*
* This method modifies the state.
*
* @param blockchain - A {@link Blockchain} object to process
*/
async runBlockchain(blockchain, maxBlocks) {
await this.init();
return runBlockchain_1.default.bind(this)(blockchain, maxBlocks);
}
/**
* Processes the `block` running all of the transactions it contains and updating the miner's account
*
* This method modifies the state. If `generate` is `true`, the state modifications will be
* reverted if an exception is raised. If it's `false`, it won't revert if the block's header is
* invalid. If an error is thrown from an event handler, the state may or may not be reverted.
*
* @param {RunBlockOpts} opts - Default values for options:
* - `generate`: false
*/
async runBlock(opts) {
await this.init();
return runBlock_1.default.bind(this)(opts);
}
/**
* Process a transaction. Run the vm. Transfers eth. Checks balances.
*
* This method modifies the state. If an error is thrown, the modifications are reverted, except
* when the error is thrown from an event handler. In the latter case the state may or may not be
* reverted.
*
* @param {RunTxOpts} opts
*/
async runTx(opts) {
await this.init();
return runTx_1.default.bind(this)(opts);
}
/**
* runs a call (or create) operation.
*
* This method modifies the state.
*
* @param {RunCallOpts} opts
*/
async runCall(opts) {
await this.init();
return runCall_1.default.bind(this)(opts);
}
/**
* Runs EVM code.
*
* This method modifies the state.
*
* @param {RunCodeOpts} opts
*/
async runCode(opts) {
await this.init();
return runCode_1.default.bind(this)(opts);
}
/**
* Build a block on top of the current state
* by adding one transaction at a time.
*
* Creates a checkpoint on the StateManager and modifies the state
* as transactions are run. The checkpoint is committed on {@link BlockBuilder.build}
* or discarded with {@link BlockBuilder.revert}.
*
* @param {BuildBlockOpts} opts
* @returns An instance of {@link BlockBuilder} with methods:
* - {@link BlockBuilder.addTransaction}
* - {@link BlockBuilder.build}
* - {@link BlockBuilder.revert}
*/
async buildBlock(opts) {
await this.init();
return buildBlock_1.default.bind(this)(opts);
}
/**
* Returns a list with the currently activated opcodes
* available for VM execution
*/
getActiveOpcodes() {
return (0, opcodes_1.getOpcodesForHF)(this._common);
}
/**
* Returns a copy of the {@link VM} instance.
*/
copy() {
return new VM({
stateManager: this.stateManager.copy(),
blockchain: this.blockchain.copy(),
common: this._common.copy(),
});
}
/**
* Return a compact error string representation of the object
*/
errorStr() {
let hf = '';
try {
hf = this._common.hardfork();
}
catch (e) {
hf = 'error';
}
const errorStr = `vm hf=${hf}`;
return errorStr;
}
}
exports.default = VM;
//# sourceMappingURL=index.js.map