diff --git a/packages/account/package.json b/packages/account/package.json index fabff61d73..5242defdfe 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 142aef879c..d262bd1864 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/package.json b/packages/block/package.json index 1617983b7f..e69987df72 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 a336e07cbd..5e2f66df8d 100644 --- a/packages/block/src/block.ts +++ b/packages/block/src/block.ts @@ -1,7 +1,7 @@ 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 { Transaction } from '@ethereumjs/tx' import { BlockHeader } from './header' import { Blockchain, BlockData, BlockOptions } from './types' @@ -64,7 +64,10 @@ export class Block { // parse transactions for (let i = 0; i < rawTransactions.length; i++) { - const tx = new Transaction(rawTransactions[i], { common: this._common }) + const txData = rawTransactions[i] + const tx = Array.isArray(txData) + ? Transaction.fromValuesArray(txData as Buffer[], this._common) + : Transaction.fromRlpSerializedTx(txData as Buffer, this._common) this.transactions.push(tx) } } @@ -100,7 +103,7 @@ export class Block { serialize(rlpEncode = true) { const raw = [ this.header.raw, - this.transactions.map((tx) => tx.raw), + this.transactions.map((tx) => tx.serialize()), this.uncleHeaders.map((uh) => uh.raw), ] @@ -141,17 +144,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 } /** @@ -223,7 +222,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 { diff --git a/packages/block/src/from-rpc.ts b/packages/block/src/from-rpc.ts index e84893a9e3..e65ca235d8 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' @@ -28,32 +28,36 @@ export default function blockFromRpc(blockParams: any, uncles?: any[], options?: if (blockParams.transactions) { 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, (block)._common) + 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) + block.transactions.push(fakeTx) } } + 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/test/block.spec.ts b/packages/block/test/block.spec.ts index f109369065..741c1eb3aa 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 85ef60d777..78c5f4a47a 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 042e165b69..b0fcf891b5 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 c764c3c594..f656816ef0 100644 --- a/packages/block/test/header.spec.ts +++ b/packages/block/test/header.spec.ts @@ -1,4 +1,4 @@ -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' diff --git a/packages/blockchain/package.json b/packages/blockchain/package.json index 0c4f142221..ac9144ef81 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 b6dac47fc5..5d9fd4d0d9 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 57d40b07e2..9c93212daa 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/ethash/package.json b/packages/ethash/package.json index 08d5a6df14..6e5e789a69 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 b07afc6cfa..f84c3a5a35 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( + common: Common | undefined, + nonce: BN, + gasPrice: BN, + gasLimit: BN, + to: Address | undefined, + value: BN, + data: Buffer, + v?: BN, + r?: BN, + s?: BN, + ) +``` + +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 7e4f7f1002..02bd094b01 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] @@ -8,18 +8,14 @@ # 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', @@ -30,21 +26,43 @@ 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 common = 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' + +_getFakeTransaction(txParams: TxParams): Transaction { + const from = Address.fromString(txParams.from) + delete txParams.from -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. + const tx = Transaction.fromTxData(txParams, this._common) -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 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 @@ -58,8 +76,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 diff --git a/packages/tx/examples/custom-chain-tx.ts b/packages/tx/examples/custom-chain-tx.ts index 589dfa1648..933435206e 100644 --- a/packages/tx/examples/custom-chain-tx.ts +++ b/packages/tx/examples/custom-chain-tx.ts @@ -1,6 +1,6 @@ import { Transaction } from '../src' import Common from '@ethereumjs/common' -import { bufferToHex, privateToAddress } from 'ethereumjs-util' +import { privateToAddress } from 'ethereumjs-util' // In this example we create a transaction for a custom network. // @@ -18,14 +18,14 @@ const customCommon = Common.forCustomChain( // We pass our custom Common object whenever we create a transaction -const tx = new Transaction( +const tx = Transaction.fromTxData( { nonce: 0, gasPrice: 100, gasLimit: 1000000000, value: 100000, }, - { common: customCommon }, + customCommon, ) // Once we created the transaction using the custom Common object, we can use it as a normal tx. @@ -36,15 +36,15 @@ const privateKey = Buffer.from( 'hex', ) -tx.sign(privateKey) +const signedTx = tx.sign(privateKey) if ( - tx.validate() && - bufferToHex(tx.getSenderAddress()) === bufferToHex(privateToAddress(privateKey)) + signedTx.validate() && + signedTx.getSenderAddress().toBuffer().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 c2d236ebf4..97d37371f4 100644 --- a/packages/tx/examples/ropsten-tx.ts +++ b/packages/tx/examples/ropsten-tx.ts @@ -1,14 +1,19 @@ 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 tx = Transaction.fromRlpSerializedTx( + txData, + new Common({ chain: 'ropsten', hardfork: 'petersburg' }), ) 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 ed41fb295b..d3abb944f0 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. +let tx = Transaction.fromTxData({ nonce: 0, gasPrice: 100, gasLimit: 1000000000, @@ -15,16 +17,16 @@ 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) +tx = 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. +// 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() console.log('Total Amount of wei needed:' + feeCost.toString()) @@ -34,10 +36,11 @@ console.log('---Serialized TX----') console.log(tx.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 +53,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 48633f07fa..61dc91c340 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 0000000000..778fe7485b --- /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 d42024fcc3..13d3ff5d5f 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 651a1ccfad..0000000000 --- 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 5f3cab2a02..f9795e6046 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 e72c7759aa..71a1fa60b2 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 { TxData, JsonTx, bnToRlp, bnToHex } from './types' // secp256k1n/2 const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16) @@ -21,288 +21,259 @@ 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, common?: Common) { + const { nonce, gasLimit, gasPrice, to, value, data, v, r, s } = txData + + return new Transaction( + common, + 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')), + ) + } + + public static fromRlpSerializedTx(serialized: Buffer, common?: Common) { + const values = rlp.decode(serialized) + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized tx input. Must be array') + } + + return this.fromValuesArray(values, common) + } + + public static fromValuesArray(values: Buffer[], common?: Common) { + 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( + common, + 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, + ) + } /** - * 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 = {}, + common: Common | undefined, + nonce: BN, + gasPrice: BN, + gasLimit: BN, + to: Address | undefined, + value: BN, + data: Buffer, + v?: BN, + r?: BN, + s?: BN, ) { - // 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 (common) { + this.common = 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 + * Sign a transaction with a given private key * @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 } - Object.assign(this, sig) + return new Transaction( + this.common, + this.nonce, + this.gasPrice, + this.gasLimit, + this.to, + this.value, + this.data, + new BN(v), + new BN(r), + new BN(s), + ) } /** * 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 +281,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, + } } - private _validateV(v?: Buffer): void { - if (v === undefined || v.length === 0) { + public isSigned(): boolean { + const { v, r, s } = this + return !!v && !!r && !!s + } + + private _unsignedTxImplementsEIP155() { + return this.common.gteHardfork('spuriousDragon') + } + + 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 +398,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 fcbcba1c7c..83b497dff9 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,91 +1,103 @@ -import Common from '@ethereumjs/common' +import { BN, unpadBuffer, Address } from 'ethereumjs-util' /** - * Any object that can be transformed into a `Buffer` - */ -export interface TransformableToBuffer { - toBuffer(): Buffer -} - -/** - * 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 d402a9fe76..8be3a6bddf 100644 --- a/packages/tx/test/api.ts +++ b/packages/tx/test/api.ts @@ -1,71 +1,85 @@ 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 tx = Transaction.fromValuesArray( + txFixtures[3].raw.map(toBuffer), + new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), + ) 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 +92,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 +129,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 +140,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 +153,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 +164,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 +215,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 +237,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 +269,74 @@ 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 tx = Transaction.fromValuesArray(txRaw.map(toBuffer), new Common({ chain: 3 })) + 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 }) }) + let tx = Transaction.fromTxData({}, new Common({ chain: 42, hardfork: 'petersburg' })) 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, + new Common({ chain: 42, hardfork: 'petersburg' }), + ) 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 }) }) + let tx = Transaction.fromTxData({}, new Common({ chain: 42, hardfork: 'petersburg' })) 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(() => + Transaction.fromTxData({ v: new BN(1) }, new Common({ chain: 42, hardfork: 'petersburg' })), + ) st.end() }) t.test('EIP155 hashing when singing', function (st) { 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), + new Common({ chain: 1, hardfork: 'petersburg' }), + ) 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 +347,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 +356,55 @@ tape('[Transaction]: Basic functions', function (t) { value: '0x0', } - const fixtureTxSignedWithEIP155 = new Transaction(txData) - fixtureTxSignedWithEIP155.sign(privateKey) + const privateKey = Buffer.from( + '4646464646464646464646464646464646464646464646464646464646464646', + 'hex', + ) + + const fixtureTxSignedWithEIP155 = Transaction.fromTxData(txData).sign(privateKey) - const fixtureTxSignedWithoutEIP155 = new Transaction(txData, { - common: new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), - }) - fixtureTxSignedWithoutEIP155.sign(privateKey) + const fixtureTxSignedWithoutEIP155 = Transaction.fromTxData( + txData, + new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), + ).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(), + new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), + ).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(), + new Common({ chain: 'mainnet', hardfork: 'tangerineWhistle' }), + ).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 +413,13 @@ 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' }) }) + let tx = Transaction.fromTxData({}, new Common({ chain: 'mainnet', hardfork: 'istanbul' })) 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), + new Common({ chain: 'mainnet', hardfork: 'istanbul' }), + ) 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 5c83ac7f4d..0000000000 --- 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 05bdfa37c3..0a9090566d 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 b0447b9d6a..659ea54219 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 6567e21803..2c433374ee 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/examples/decode-opcodes/index.ts b/packages/vm/examples/decode-opcodes/index.ts index daa90fa610..dce85b7c72 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 26c1caef78..b6ea7110e1 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 55fa4473ee..35d712153d 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 7860921379..4baed51490 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 ab3793ca86..0076bd6a29 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 a7ca01e9f3..68227dd668 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 c41dfcdd4f..27809011e0 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 d840e09869..2dddfc01a9 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 35d9713fe8..40765b5ef7 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 d28eb9574d..b28b00da00 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 94bf2282d4..85de1819ad 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,22 @@ 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')) + block.transactions[0] = new Transaction( + (block)._common, + nonce, + gasPrice, + gasLimit, + to, + value, + data, + v, + r, + s, + ) 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 37b0569c43..6046a63825 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 7ca33c76e2..392a40f168 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 7d07ab6037..8d441ad24d 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 }