UNPKG

@ethereumjs/common

Version:
789 lines 30.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Common = void 0; const util_1 = require("@ethereumjs/util"); const eventemitter3_1 = require("eventemitter3"); const crc_ts_1 = require("./crc.js"); const eips_ts_1 = require("./eips.js"); const enums_ts_1 = require("./enums.js"); const hardforks_ts_1 = require("./hardforks.js"); /** * Common class to access chain and hardfork parameters and to provide * a unified and shared view on the network and hardfork state. * * Use the {@link createCustomCommon} constructor for creating simple * custom chain {@link Common} objects (more complete custom chain setups * can be created via the main constructor). * * Use the {@link createCommonFromGethGenesis} constructor for creating * a Common object from a Geth genesis file. */ class Common { constructor(opts) { this._eips = []; this._paramsCache = {}; this._activatedEIPsCache = []; this.events = new eventemitter3_1.EventEmitter(); this._chainParams = JSON.parse(JSON.stringify(opts.chain)); // copy this.DEFAULT_HARDFORK = this._chainParams.defaultHardfork ?? enums_ts_1.Hardfork.Prague; // Assign hardfork changes in the sequence of the applied hardforks this.HARDFORK_CHANGES = this.hardforks().map((hf) => [ hf.name, // Allow to even override an existing hardfork specification (this._chainParams.customHardforks && this._chainParams.customHardforks[hf.name]) ?? hardforks_ts_1.hardforksDict[hf.name], ]); this._hardfork = this.DEFAULT_HARDFORK; this._params = opts.params ? JSON.parse(JSON.stringify(opts.params)) : {}; // copy if (opts.hardfork !== undefined) { this.setHardfork(opts.hardfork); } if (opts.eips) { this.setEIPs(opts.eips); } this.customCrypto = opts.customCrypto ?? {}; if (Object.keys(this._paramsCache).length === 0) { this._buildParamsCache(); this._buildActivatedEIPsCache(); } } /** * Update the internal Common EIP params set. Existing values * will get preserved unless there is a new value for a parameter * provided with params. * * Example Format: * * ```ts * { * 1559: { * initialBaseFee: 1000000000, * } * } * ``` * * @param params */ updateParams(params) { for (const [eip, paramsConfig] of Object.entries(params)) { if (!(eip in this._params)) { this._params[eip] = JSON.parse(JSON.stringify(paramsConfig)); // copy } else { this._params[eip] = JSON.parse(JSON.stringify({ ...this._params[eip], ...params[eip] })); // copy } } this._buildParamsCache(); } /** * Fully resets the internal Common EIP params set with the values provided. * * Example Format: * * ```ts * { * 1559: { * initialBaseFee: 1000000000, * } * } * ``` * * @param params */ resetParams(params) { this._params = JSON.parse(JSON.stringify(params)); // copy this._buildParamsCache(); } /** * Sets the hardfork to get params for * @param hardfork String identifier (e.g. 'byzantium') or {@link Hardfork} enum */ setHardfork(hardfork) { let existing = false; for (const hfChanges of this.HARDFORK_CHANGES) { if (hfChanges[0] === hardfork) { if (this._hardfork !== hardfork) { this._hardfork = hardfork; this._buildParamsCache(); this._buildActivatedEIPsCache(); this.events.emit('hardforkChanged', hardfork); } existing = true; } } if (!existing) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Hardfork with name ${hardfork} not supported`); } } /** * Returns the hardfork either based on block number (older HFs) or * timestamp (Shanghai upwards). * * @param opts Block number or timestamp * @returns The name of the HF */ getHardforkBy(opts) { const blockNumber = (0, util_1.toType)(opts.blockNumber, util_1.TypeOutput.BigInt); const timestamp = (0, util_1.toType)(opts.timestamp, util_1.TypeOutput.BigInt); // Filter out hardforks with no block number, no timestamp (i.e. unapplied hardforks) const hfs = this.hardforks().filter((hf) => hf.block !== null || hf.timestamp !== undefined); // Find the first hardfork that has a block number greater than `blockNumber` // If timestamp is not provided, it also skips timestamps hardforks to continue // discovering/checking number hardforks. let hfIndex = hfs.findIndex((hf) => (blockNumber !== undefined && hf.block !== null && BigInt(hf.block) > blockNumber) || (timestamp !== undefined && hf.timestamp !== undefined && BigInt(hf.timestamp) > timestamp)); if (hfIndex === -1) { // all hardforks apply, set hfIndex to the last one as that's the candidate hfIndex = hfs.length; } else if (hfIndex === 0) { // cannot have a case where a block number is before all applied hardforks // since the chain has to start with a hardfork throw Error('Must have at least one hardfork at block 0'); } // If timestamp is not provided, we need to rollback to the last hf with block if (timestamp === undefined) { const stepBack = hfs .slice(0, hfIndex) .reverse() .findIndex((hf) => hf.block !== null); hfIndex = hfIndex - stepBack; } // Move hfIndex one back to arrive at candidate hardfork hfIndex = hfIndex - 1; const hfStartIndex = hfIndex; // Move the hfIndex to the end of the hardforks that might be scheduled on the same block/timestamp // This won't anyway be the case with Merge hfs for (; hfIndex < hfs.length - 1; hfIndex++) { // break out if hfIndex + 1 is not scheduled at hfIndex if (hfs[hfIndex].block !== hfs[hfIndex + 1].block || hfs[hfIndex].timestamp !== hfs[hfIndex + 1].timestamp) { break; } } if (timestamp !== undefined) { const minTimeStamp = hfs .slice(0, hfStartIndex) .reduce((acc, hf) => Math.max(Number(hf.timestamp ?? '0'), acc), 0); if (minTimeStamp > timestamp) { throw Error(`Maximum HF determined by timestamp is lower than the block number HF`); } const maxTimeStamp = hfs .slice(hfIndex + 1) .reduce((acc, hf) => Math.min(Number(hf.timestamp ?? timestamp), acc), Number(timestamp)); if (maxTimeStamp < timestamp) { throw Error(`Maximum HF determined by block number is lower than timestamp HF`); } } const hardfork = hfs[hfIndex]; return hardfork.name; } /** * Sets a new hardfork either based on block number (older HFs) or * timestamp (Shanghai upwards). * * @param opts Block number or timestamp * @returns The name of the HF set */ setHardforkBy(opts) { const hardfork = this.getHardforkBy(opts); this.setHardfork(hardfork); return hardfork; } /** * Internal helper function, returns the params for the given hardfork for the chain set * @param hardfork Hardfork name * @returns Dictionary with hardfork params or null if hardfork not on chain */ _getHardfork(hardfork) { const hfs = this.hardforks(); for (const hf of hfs) { if (hf['name'] === hardfork) return hf; } return null; } /** * Sets the active EIPs * @param eips */ setEIPs(eips = []) { for (const eip of eips) { if (!(eip in eips_ts_1.eipsDict)) { throw (0, util_1.EthereumJSErrorWithoutCode)(`${eip} not supported`); } const minHF = this.gteHardfork(eips_ts_1.eipsDict[eip]['minimumHardfork']); if (!minHF) { throw (0, util_1.EthereumJSErrorWithoutCode)(`${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`); } } this._eips = eips; this._buildParamsCache(); this._buildActivatedEIPsCache(); for (const eip of eips) { if (eips_ts_1.eipsDict[eip].requiredEIPs !== undefined) { for (const elem of eips_ts_1.eipsDict[eip].requiredEIPs) { if (!(eips.includes(elem) || this.isActivatedEIP(elem))) { throw (0, util_1.EthereumJSErrorWithoutCode)(`${eip} requires EIP ${elem}, but is not included in the EIP list`); } } } } } /** * Internal helper for _buildParamsCache() */ _mergeWithParamsCache(params) { for (const [key, value] of Object.entries(params)) { this._paramsCache[key] = value; } } /** * Build up a cache for all parameter values for the current HF and all activated EIPs */ _buildParamsCache() { this._paramsCache = {}; // Iterate through all hardforks up to hardfork set const hardfork = this.hardfork(); for (const hfChanges of this.HARDFORK_CHANGES) { // EIP-referencing HF config (e.g. for berlin) if ('eips' in hfChanges[1]) { const hfEIPs = hfChanges[1].eips ?? []; for (const eip of hfEIPs) { if (this._params[eip] !== undefined && this._params[eip] !== null) { this._mergeWithParamsCache(this._params[eip]); } } } // Hardfork-scoped params (e.g. for bpo1, bpo2) // override the baseline EIP values when present const hfScopedParams = this._params[hfChanges[0]]; if (hfScopedParams !== undefined && hfScopedParams !== null) { this._mergeWithParamsCache(hfScopedParams); } // Parameter-inlining HF config (e.g. for istanbul or custom blobSchedule) if (hfChanges[1].params !== undefined && hfChanges[1].params !== null) { this._mergeWithParamsCache(hfChanges[1].params); } if (hfChanges[0] === hardfork) break; } // Iterate through all additionally activated EIPs for (const eip of this._eips) { if (this._params[eip] !== undefined && this._params[eip] !== null) { this._mergeWithParamsCache(this._params[eip]); } } } /** * Builds the cache of EIPs activated either via hardforks or constructor `eips`. */ _buildActivatedEIPsCache() { this._activatedEIPsCache = []; for (const [name, hf] of this.HARDFORK_CHANGES) { if (this.gteHardfork(name) && 'eips' in hf) { this._activatedEIPsCache = this._activatedEIPsCache.concat(hf.eips ?? []); } } this._activatedEIPsCache = this._activatedEIPsCache.concat(this._eips); } /** * Returns a parameter for the current chain setup * * If the parameter is present in an EIP, the EIP always takes precedence. * Otherwise the parameter is taken from the latest applied HF with * a change on the respective parameter. * * @param name Parameter name (e.g. 'minGasLimit') * @returns The value requested (throws if not found) */ param(name) { // TODO: consider the case that different active EIPs // can change the same parameter if (!(name in this._paramsCache)) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Missing parameter value for ${name}`); } const value = this._paramsCache[name]; return BigInt(value ?? 0); } /** * Returns the parameter corresponding to a hardfork * @param name Parameter name (e.g. 'minGasLimit') * @param hardfork Hardfork name * @returns The value requested (throws if not found) */ paramByHardfork(name, hardfork) { let value; for (const hfChanges of this.HARDFORK_CHANGES) { // EIP-referencing HF config (e.g. for berlin) if ('eips' in hfChanges[1]) { const hfEIPs = hfChanges[1]['eips']; for (const eip of hfEIPs) { const eipParams = this._params[eip]; const eipValue = eipParams?.[name]; if (eipValue !== undefined) { value = eipValue; } } // Parameter-inlining HF config (e.g. for istanbul) } else { const hfValue = hfChanges[1].params?.[name]; if (hfValue !== undefined) { value = hfValue; } } if (hfChanges[0] === hardfork) break; } if (value === undefined) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Missing parameter value for ${name}`); } return BigInt(value ?? 0); } /** * Returns a parameter corresponding to an EIP * @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic) * @param eip Number of the EIP * @returns The value requested (throws if not found) */ paramByEIP(name, eip) { if (!(eip in eips_ts_1.eipsDict)) { throw (0, util_1.EthereumJSErrorWithoutCode)(`${eip} not supported`); } const eipParams = this._params[eip]; if (eipParams?.[name] === undefined) { throw (0, util_1.EthereumJSErrorWithoutCode)(`Missing parameter value for ${name}`); } const value = eipParams[name]; return BigInt(value ?? 0); } /** * Returns a parameter for the hardfork active on block number or * optional provided total difficulty (Merge HF) * @param name Parameter name * @param blockNumber Block number * @returns The value requested or `BigInt(0)` if not found */ paramByBlock(name, blockNumber, timestamp) { const hardfork = this.getHardforkBy({ blockNumber, timestamp }); return this.paramByHardfork(name, hardfork); } /** * Returns the blob gas schedule for the current hardfork * @returns The blob gas schedule */ getBlobGasSchedule() { if (this.gteHardfork(enums_ts_1.Hardfork.Bpo1)) { return { targetBlobGasPerBlock: this.param('target') * this.param('blobGasPerBlob'), maxBlobGasPerBlock: this.param('max') * this.param('blobGasPerBlob'), blobGasPriceUpdateFraction: this.param('blobGasPriceUpdateFraction'), }; } return { targetBlobGasPerBlock: this.param('targetBlobGasPerBlock'), maxBlobGasPerBlock: this.param('maxBlobGasPerBlock'), blobGasPriceUpdateFraction: this.param('blobGasPriceUpdateFraction'), }; } /** * Checks if an EIP is activated by either being included in the EIPs * manually passed in with the {@link CommonOpts.eips} or in a * hardfork currently being active * * Note: this method only works for EIPs being supported * by the {@link CommonOpts.eips} constructor option * @param eip */ isActivatedEIP(eip) { if (this._activatedEIPsCache.includes(eip)) { return true; } return false; } /** * Checks if set or provided hardfork is active on block number * @param hardfork Hardfork name or null (for HF set) * @param blockNumber * @returns True if HF is active on block number */ hardforkIsActiveOnBlock(hardfork, blockNumber) { blockNumber = (0, util_1.toType)(blockNumber, util_1.TypeOutput.BigInt); hardfork = hardfork ?? this._hardfork; const hfBlock = this.hardforkBlock(hardfork); if (typeof hfBlock === 'bigint' && hfBlock !== util_1.BIGINT_0 && blockNumber >= hfBlock) { return true; } return false; } /** * Alias to hardforkIsActiveOnBlock when hardfork is set * @param blockNumber * @returns True if HF is active on block number */ activeOnBlock(blockNumber) { return this.hardforkIsActiveOnBlock(null, blockNumber); } /** * Sequence based check if given or set HF1 is greater than or equal HF2 * @param hardfork1 Hardfork name or null (if set) * @param hardfork2 Hardfork name * @returns True if HF1 gte HF2 */ hardforkGteHardfork(hardfork1, hardfork2) { hardfork1 = hardfork1 ?? this._hardfork; const hardforks = this.hardforks(); let posHf1 = -1, posHf2 = -1; let index = 0; for (const hf of hardforks) { if (hf['name'] === hardfork1) posHf1 = index; if (hf['name'] === hardfork2) posHf2 = index; index += 1; } return posHf1 >= posHf2 && posHf2 !== -1; } /** * Alias to hardforkGteHardfork when hardfork is set * @param hardfork Hardfork name * @returns True if hardfork set is greater than hardfork provided */ gteHardfork(hardfork) { return this.hardforkGteHardfork(null, hardfork); } /** * Returns the hardfork change block for hardfork provided or set * @param hardfork Hardfork name, optional if HF set * @returns Block number or null if unscheduled */ hardforkBlock(hardfork) { hardfork = hardfork ?? this._hardfork; const block = this._getHardfork(hardfork)?.['block']; if (block === undefined || block === null) { return null; } return BigInt(block); } /** * Returns the timestamp at which a given hardfork is scheduled (if any). * @param hardfork Hardfork name, optional if HF set * @returns Timestamp or null if the hardfork is not timestamp-based */ hardforkTimestamp(hardfork) { hardfork = hardfork ?? this._hardfork; const timestamp = this._getHardfork(hardfork)?.['timestamp']; if (timestamp === undefined || timestamp === null) { return null; } return BigInt(timestamp); } /** * Returns the hardfork change block for eip * @param eip EIP number * @returns Block number or null if unscheduled */ eipBlock(eip) { for (const hfChanges of this.HARDFORK_CHANGES) { const hf = hfChanges[1]; if ('eips' in hf) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (hf['eips'].includes(eip)) { return this.hardforkBlock(hfChanges[0]); } } } return null; } /** * Returns the scheduled timestamp of the EIP (if scheduled and scheduled by timestamp) * @param eip EIP number * @returns Scheduled timestamp. If this EIP is unscheduled, or the EIP is scheduled by block number, then it returns `null`. */ eipTimestamp(eip) { for (const hfChanges of this.HARDFORK_CHANGES) { const hf = hfChanges[1]; if ('eips' in hf) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (hf['eips'].includes(eip)) { return this.hardforkTimestamp(hfChanges[0]); } } } return null; } /** * Returns the block number or timestamp at which the next hardfork will occur. * For pre-merge hardforks, returns the block number. * For post-merge hardforks, returns the timestamp. * Returns null if there is no next hardfork. * @param hardfork Hardfork name, optional if HF set * @returns Block number or timestamp, or null if not available */ nextHardforkBlockOrTimestamp(hardfork) { const targetHardfork = hardfork ?? this._hardfork; const hfs = this.hardforks(); // Find the index of the target hardfork let targetHfIndex = hfs.findIndex((hf) => hf.name === targetHardfork); // Special handling for The Merge (Paris) hardfork if (targetHardfork === enums_ts_1.Hardfork.Paris) { // The Merge is determined by total difficulty, not block number // So we look at the previous hardfork's parameters instead targetHfIndex -= 1; } // If we couldn't find a valid hardfork index, return null if (targetHfIndex < 0) { return null; } // Get the current hardfork's block/timestamp const currentHf = hfs[targetHfIndex]; const currentBlockOrTimestamp = currentHf.timestamp ?? currentHf.block; if (currentBlockOrTimestamp === null || currentBlockOrTimestamp === undefined) { return null; } // Find the next hardfork that has a different block/timestamp const nextHf = hfs.slice(targetHfIndex + 1).find((hf) => { const nextBlockOrTimestamp = hf.timestamp ?? hf.block; return (nextBlockOrTimestamp !== null && nextBlockOrTimestamp !== undefined && nextBlockOrTimestamp !== currentBlockOrTimestamp); }); // If no next hf found with valid block or timestamp return null if (nextHf === undefined) { return null; } // Get the block/timestamp for the next hardfork const nextBlockOrTimestamp = nextHf.timestamp ?? nextHf.block; if (nextBlockOrTimestamp === null || nextBlockOrTimestamp === undefined) { return null; } return BigInt(nextBlockOrTimestamp); } /** * Internal helper function to calculate a fork hash * @param hardfork Hardfork name * @param genesisHash Genesis block hash of the chain * @returns Fork hash as hex string */ _calcForkHash(hardfork, genesisHash) { let hfBytes = new Uint8Array(0); let prevBlockOrTime = 0; for (const hf of this.hardforks()) { const { block, timestamp, name } = hf; // Timestamp to be used for timestamp based hfs even if we may bundle // block number with them retrospectively let blockOrTime = timestamp ?? block; blockOrTime = blockOrTime !== null ? Number(blockOrTime) : null; // Skip for chainstart (0), not applied HFs (null) and // when already applied on same blockOrTime HFs // and on the merge since forkhash doesn't change on merge hf if (typeof blockOrTime === 'number' && blockOrTime !== 0 && blockOrTime !== prevBlockOrTime && name !== enums_ts_1.Hardfork.Paris) { const hfBlockBytes = (0, util_1.hexToBytes)(`0x${blockOrTime.toString(16).padStart(16, '0')}`); hfBytes = (0, util_1.concatBytes)(hfBytes, hfBlockBytes); prevBlockOrTime = blockOrTime; } if (hf.name === hardfork) break; } const inputBytes = (0, util_1.concatBytes)(genesisHash, hfBytes); // CRC32 delivers result as signed (negative) 32-bit integer, // convert to hex string const forkhash = (0, util_1.bytesToHex)((0, util_1.intToBytes)((0, crc_ts_1.crc32)(inputBytes) >>> 0)); return forkhash; } /** * Returns an eth/64 compliant fork hash (EIP-2124) * @param hardfork Hardfork name, optional if HF set * @param genesisHash Genesis block hash of the network, optional if already defined and not needed to be calculated * @returns Fork hash as a hex string */ forkHash(hardfork, genesisHash) { hardfork = hardfork ?? this._hardfork; const data = this._getHardfork(hardfork); if (data === null || (data?.block === null && data?.timestamp === undefined)) { const msg = 'No fork hash calculation possible for future hardfork'; throw (0, util_1.EthereumJSErrorWithoutCode)(msg); } if (data?.forkHash !== null && data?.forkHash !== undefined) { return data.forkHash; } if (!genesisHash) throw (0, util_1.EthereumJSErrorWithoutCode)('genesisHash required for forkHash calculation'); return this._calcForkHash(hardfork, genesisHash); } /** * * @param forkHash Fork hash as a hex string * @returns Array with hardfork data (name, block, forkHash) */ hardforkForForkHash(forkHash) { const resArray = this.hardforks().filter((hf) => { return hf.forkHash === forkHash; }); return resArray.length >= 1 ? resArray[resArray.length - 1] : null; } /** * Sets any missing forkHashes on this {@link Common} instance. * @param genesisHash The genesis block hash */ setForkHashes(genesisHash) { for (const hf of this.hardforks()) { const blockOrTime = hf.timestamp ?? hf.block; if ((hf.forkHash === null || hf.forkHash === undefined) && blockOrTime !== null && blockOrTime !== undefined) { hf.forkHash = this.forkHash(hf.name, genesisHash); } } } /** * Returns the Genesis parameters of the current chain * @returns Genesis dictionary */ genesis() { return this._chainParams.genesis; } /** * Returns the hardfork definitions for the current chain. * @returns Array of hardfork transition configs */ hardforks() { const hfs = this._chainParams.hardforks; if (this._chainParams.customHardforks !== undefined) { // Add transition configs for custom hardforks that aren't already in the hardforks array const existingNames = new Set(hfs.map((hf) => hf.name)); const customHfEntries = Object.keys(this._chainParams.customHardforks) .filter((name) => !existingNames.has(name)) .map((name) => ({ name, block: null, // Custom hardforks without explicit transition config default to null (inactive by block) })); return [...hfs, ...customHfEntries]; } return hfs; } /** * Returns bootstrap nodes for the current chain. * @returns Array of bootstrap node configs */ bootstrapNodes() { return this._chainParams.bootstrapNodes; } /** * Returns DNS networks for the current chain * @returns {String[]} Array of DNS ENR urls */ dnsNetworks() { return this._chainParams.dnsNetworks; } /** * Returns the hardfork set * @returns Hardfork name */ hardfork() { return this._hardfork; } /** * Returns the Id of current chain * @returns chain Id */ chainId() { return BigInt(this._chainParams.chainId); } /** * Returns the name of current chain * @returns chain name (lower case) */ chainName() { return this._chainParams.name; } /** * Returns the additionally activated EIPs * (by using the `eips` constructor option) * @returns List of EIPs */ eips() { return this._eips; } /** * Returns the consensus type of the network * Possible values: "pow"|"poa"|"pos" * * Note: This value can update along a Hardfork. */ consensusType() { const hardfork = this.hardfork(); let value; for (const hfChanges of this.HARDFORK_CHANGES) { if ('consensus' in hfChanges[1]) { value = hfChanges[1]['consensus']['type']; } if (hfChanges[0] === hardfork) break; } return value ?? this._chainParams['consensus']['type']; } /** * Returns the concrete consensus implementation * algorithm or protocol for the network * e.g. "ethash" for "pow" consensus type, * "clique" for "poa" consensus type or * "casper" for "pos" consensus type. * * Note: This value can update along a Hardfork. */ consensusAlgorithm() { const hardfork = this.hardfork(); let value; for (const hfChanges of this.HARDFORK_CHANGES) { if ('consensus' in hfChanges[1]) { value = hfChanges[1]['consensus']['algorithm']; } if (hfChanges[0] === hardfork) break; } return value ?? this._chainParams['consensus']['algorithm']; } /** * Returns a dictionary with consensus configuration * parameters based on the consensus algorithm * * Expected returns (parameters must be present in * the respective chain JSON files): * * ethash: empty object * clique: period, epoch * casper: empty object * * Note: This value can update along a Hardfork. */ consensusConfig() { const hardfork = this.hardfork(); let value; for (const hfChanges of this.HARDFORK_CHANGES) { if ('consensus' in hfChanges[1]) { // The config parameter is named after the respective consensus algorithm const config = hfChanges[1]; const algorithm = config['consensus']['algorithm']; value = config['consensus'][algorithm]; } if (hfChanges[0] === hardfork) break; } return (value ?? this._chainParams['consensus'][this.consensusAlgorithm()] ?? {}); } /** * Returns a deep copy of this {@link Common} instance. */ copy() { const copy = Object.assign(Object.create(Object.getPrototypeOf(this)), this); copy.events = new eventemitter3_1.EventEmitter(); return copy; } } exports.Common = Common; //# sourceMappingURL=common.js.map