mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-30 16:18:07 +01:00
b7033196d2
The `waitUntilCalled` utility now has a timeout. It will now throw an error if the stub is not called enough times, rather than blocking forever. The return type had to be changed to a function, so that we could throw when the timeout is triggered. I tried returning an error that rejected first, but if you don't handle the error synchronously Node.js will consider it to be an unhandled Promise rejected (even if it _is_ handled later on). I worked around this by resolving in the timeout case as well, so that there is never a "deferred" Promise exception in the timeout case. The returned function re-throws the error if it's given. That way there is never any unhandled Promise rejection.
1408 lines
44 KiB
JavaScript
1408 lines
44 KiB
JavaScript
import assert from 'assert'
|
|
import sinon from 'sinon'
|
|
import proxyquire from 'proxyquire'
|
|
import nock from 'nock'
|
|
import { cloneDeep } from 'lodash'
|
|
|
|
import waitUntilCalled from '../../../lib/wait-until-called'
|
|
import {
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
MAINNET_CHAIN_ID,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
ROPSTEN_CHAIN_ID,
|
|
ROPSTEN_NETWORK_ID,
|
|
} from '../../../../app/scripts/controllers/network/enums'
|
|
import {
|
|
TRANSACTION_CATEGORIES,
|
|
TRANSACTION_STATUSES,
|
|
} from '../../../../shared/constants/transaction'
|
|
|
|
const IncomingTransactionsController = proxyquire(
|
|
'../../../../app/scripts/controllers/incoming-transactions',
|
|
{
|
|
'../lib/random-id': { default: () => 54321 },
|
|
},
|
|
).default
|
|
|
|
const FAKE_CHAIN_ID = '0x1338'
|
|
const MOCK_SELECTED_ADDRESS = '0x0101'
|
|
const SET_STATE_TIMEOUT = 10
|
|
|
|
function getEmptyInitState() {
|
|
return {
|
|
incomingTransactions: {},
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
[GOERLI]: null,
|
|
[KOVAN]: null,
|
|
[MAINNET]: null,
|
|
[RINKEBY]: null,
|
|
[ROPSTEN]: null,
|
|
},
|
|
}
|
|
}
|
|
|
|
function getNonEmptyInitState() {
|
|
return {
|
|
incomingTransactions: {
|
|
'0x123456': { id: 777 },
|
|
},
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
[GOERLI]: 1,
|
|
[KOVAN]: 2,
|
|
[MAINNET]: 3,
|
|
[RINKEBY]: 5,
|
|
[ROPSTEN]: 4,
|
|
},
|
|
}
|
|
}
|
|
|
|
function getMockNetworkController(chainId = FAKE_CHAIN_ID) {
|
|
return {
|
|
getCurrentChainId: () => chainId,
|
|
on: sinon.spy(),
|
|
}
|
|
}
|
|
|
|
function getMockPreferencesController({
|
|
showIncomingTransactions = true,
|
|
} = {}) {
|
|
return {
|
|
getSelectedAddress: sinon.stub().returns(MOCK_SELECTED_ADDRESS),
|
|
store: {
|
|
getState: sinon.stub().returns({
|
|
featureFlags: {
|
|
showIncomingTransactions,
|
|
},
|
|
}),
|
|
subscribe: sinon.spy(),
|
|
},
|
|
}
|
|
}
|
|
|
|
function getMockBlockTracker() {
|
|
return {
|
|
addListener: sinon.stub().callsArgWithAsync(1, '0xa'),
|
|
removeListener: sinon.spy(),
|
|
testProperty: 'fakeBlockTracker',
|
|
getCurrentBlock: () => '0xa',
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A transaction object in the format returned by the Etherscan API.
|
|
*
|
|
* Note that this is not an exhaustive type definiton; only the properties we use are defined
|
|
*
|
|
* @typedef {Object} EtherscanTransaction
|
|
* @property {string} blockNumber - The number of the block this transaction was found in, in decimal
|
|
* @property {string} from - The hex-prefixed address of the sender
|
|
* @property {string} gas - The gas limit, in decimal WEI
|
|
* @property {string} gasPrice - The gas price, in decimal WEI
|
|
* @property {string} hash - The hex-prefixed transaction hash
|
|
* @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed)
|
|
* @property {string} nonce - The transaction nonce, in decimal
|
|
* @property {string} timeStamp - The timestamp for the transaction, in seconds
|
|
* @property {string} to - The hex-prefixed address of the recipient
|
|
* @property {string} value - The amount of ETH sent in this transaction, in decimal WEI
|
|
*/
|
|
|
|
/**
|
|
* Returns a transaction object matching the expected format returned
|
|
* by the Etherscan API
|
|
*
|
|
* @param {string} [toAddress] - The hex-prefixed address of the recipient
|
|
* @param {number} [blockNumber] - The block number for the transaction
|
|
* @returns {EtherscanTransaction}
|
|
*/
|
|
const getFakeEtherscanTransaction = (
|
|
toAddress = MOCK_SELECTED_ADDRESS,
|
|
blockNumber = 10,
|
|
) => {
|
|
return {
|
|
blockNumber: blockNumber.toString(),
|
|
from: '0xfake',
|
|
gas: '0',
|
|
gasPrice: '0',
|
|
hash: '0xfake',
|
|
isError: '0',
|
|
nonce: '100',
|
|
timeStamp: '16000000000000',
|
|
to: toAddress,
|
|
value: '0',
|
|
}
|
|
}
|
|
|
|
describe('IncomingTransactionsController', function () {
|
|
afterEach(function () {
|
|
sinon.restore()
|
|
nock.cleanAll()
|
|
})
|
|
|
|
describe('constructor', function () {
|
|
it('should set up correct store, listeners and properties in the constructor', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: {},
|
|
},
|
|
)
|
|
sinon.spy(incomingTransactionsController, '_update')
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController.store.getState(),
|
|
getEmptyInitState(),
|
|
)
|
|
|
|
assert(incomingTransactionsController.networkController.on.calledOnce)
|
|
assert.equal(
|
|
incomingTransactionsController.networkController.on.getCall(0).args[0],
|
|
'networkDidChange',
|
|
)
|
|
const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall(
|
|
0,
|
|
).args[1]
|
|
assert.equal(incomingTransactionsController._update.callCount, 0)
|
|
networkControllerListenerCallback('testNetworkType')
|
|
assert.equal(incomingTransactionsController._update.callCount, 1)
|
|
assert.deepEqual(
|
|
incomingTransactionsController._update.getCall(0).args[0],
|
|
{
|
|
address: '0x0101',
|
|
},
|
|
)
|
|
|
|
incomingTransactionsController._update.resetHistory()
|
|
})
|
|
|
|
it('should set the store to a provided initial state', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController.store.getState(),
|
|
getNonEmptyInitState(),
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('update events', function () {
|
|
it('should set up a listener for the latest block', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: {},
|
|
},
|
|
)
|
|
|
|
incomingTransactionsController.start()
|
|
|
|
assert(incomingTransactionsController.blockTracker.addListener.calledOnce)
|
|
assert.equal(
|
|
incomingTransactionsController.blockTracker.addListener.getCall(0)
|
|
.args[0],
|
|
'latest',
|
|
)
|
|
})
|
|
|
|
it('should update upon latest block when started and on supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
const startBlock = getNonEmptyInitState()
|
|
.incomingTxLastFetchedBlocksByNetwork[ROPSTEN]
|
|
nock('https://api-ropsten.etherscan.io')
|
|
.get(
|
|
`/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
|
|
)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
incomingTransactionsController.start()
|
|
await updateStateCalled()
|
|
|
|
const actualState = incomingTransactionsController.store.getState()
|
|
const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id
|
|
|
|
const actualStateWithoutGenerated = cloneDeep(actualState)
|
|
delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id
|
|
|
|
assert.ok(
|
|
typeof generatedTxId === 'number' && generatedTxId > 0,
|
|
'Generated transaction ID should be a positive number',
|
|
)
|
|
assert.deepStrictEqual(
|
|
actualStateWithoutGenerated,
|
|
{
|
|
incomingTransactions: {
|
|
...getNonEmptyInitState().incomingTransactions,
|
|
'0xfake': {
|
|
blockNumber: '10',
|
|
hash: '0xfake',
|
|
metamaskNetworkId: '3',
|
|
status: TRANSACTION_STATUSES.CONFIRMED,
|
|
time: 16000000000000000,
|
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
|
txParams: {
|
|
from: '0xfake',
|
|
gas: '0x0',
|
|
gasPrice: '0x0',
|
|
nonce: '0x64',
|
|
to: '0x0101',
|
|
value: '0x0',
|
|
},
|
|
},
|
|
},
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork,
|
|
[ROPSTEN]: 11,
|
|
},
|
|
},
|
|
'State should have been updated after first block was received',
|
|
)
|
|
})
|
|
|
|
it('should not update upon latest block when started and not on supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
incomingTransactionsController.start()
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
|
|
it('should not update upon latest block when started and incoming transactions disabled', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController({
|
|
showIncomingTransactions: false,
|
|
}),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
incomingTransactionsController.start()
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
|
|
it('should not update upon latest block when not started', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
|
|
it('should not update upon latest block when stopped', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
incomingTransactionsController.stop()
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
|
|
it('should update when the selected address changes and on supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`
|
|
const startBlock = getNonEmptyInitState()
|
|
.incomingTxLastFetchedBlocksByNetwork[ROPSTEN]
|
|
nock('https://api-ropsten.etherscan.io')
|
|
.get(
|
|
`/api?module=account&action=txlist&address=${NEW_MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
|
|
)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)],
|
|
}),
|
|
)
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(
|
|
1,
|
|
).args[0]
|
|
// The incoming transactions controller will always skip the first event
|
|
// We need to call subscription twice to test the event handling
|
|
// TODO: stop skipping the first event
|
|
await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS })
|
|
await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS })
|
|
await updateStateCalled()
|
|
|
|
const actualState = incomingTransactionsController.store.getState()
|
|
const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id
|
|
|
|
const actualStateWithoutGenerated = cloneDeep(actualState)
|
|
delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id
|
|
|
|
assert.ok(
|
|
typeof generatedTxId === 'number' && generatedTxId > 0,
|
|
'Generated transaction ID should be a positive number',
|
|
)
|
|
assert.deepStrictEqual(
|
|
actualStateWithoutGenerated,
|
|
{
|
|
incomingTransactions: {
|
|
...getNonEmptyInitState().incomingTransactions,
|
|
'0xfake': {
|
|
blockNumber: '10',
|
|
hash: '0xfake',
|
|
metamaskNetworkId: '3',
|
|
status: TRANSACTION_STATUSES.CONFIRMED,
|
|
time: 16000000000000000,
|
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
|
txParams: {
|
|
from: '0xfake',
|
|
gas: '0x0',
|
|
gasPrice: '0x0',
|
|
nonce: '0x64',
|
|
to: '0x01019',
|
|
value: '0x0',
|
|
},
|
|
},
|
|
},
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork,
|
|
[ROPSTEN]: 11,
|
|
},
|
|
},
|
|
'State should have been updated after first block was received',
|
|
)
|
|
})
|
|
|
|
it('should not update when the selected address changes and not on supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: { ...getMockBlockTracker() },
|
|
networkController: getMockNetworkController(),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
const NEW_MOCK_SELECTED_ADDRESS = `${MOCK_SELECTED_ADDRESS}9`
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(
|
|
1,
|
|
).args[0]
|
|
// The incoming transactions controller will always skip the first event
|
|
// We need to call subscription twice to test the event handling
|
|
// TODO: stop skipping the first event
|
|
await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS })
|
|
await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS })
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
|
|
it('should update when switching to a supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
const startBlock = getNonEmptyInitState()
|
|
.incomingTxLastFetchedBlocksByNetwork[ROPSTEN]
|
|
nock('https://api-ropsten.etherscan.io')
|
|
.get(
|
|
`/api?module=account&action=txlist&address=${MOCK_SELECTED_ADDRESS}&tag=latest&page=1&startBlock=${startBlock}`,
|
|
)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
const subscription = incomingTransactionsController.networkController.on.getCall(
|
|
0,
|
|
).args[1]
|
|
incomingTransactionsController.networkController = getMockNetworkController(
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
await subscription(ROPSTEN)
|
|
await updateStateCalled()
|
|
|
|
const actualState = incomingTransactionsController.store.getState()
|
|
const generatedTxId = actualState?.incomingTransactions?.['0xfake']?.id
|
|
|
|
const actualStateWithoutGenerated = cloneDeep(actualState)
|
|
delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id
|
|
|
|
assert.ok(
|
|
typeof generatedTxId === 'number' && generatedTxId > 0,
|
|
'Generated transaction ID should be a positive number',
|
|
)
|
|
assert.deepStrictEqual(
|
|
actualStateWithoutGenerated,
|
|
{
|
|
incomingTransactions: {
|
|
...getNonEmptyInitState().incomingTransactions,
|
|
'0xfake': {
|
|
blockNumber: '10',
|
|
hash: '0xfake',
|
|
metamaskNetworkId: '3',
|
|
status: TRANSACTION_STATUSES.CONFIRMED,
|
|
time: 16000000000000000,
|
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
|
txParams: {
|
|
from: '0xfake',
|
|
gas: '0x0',
|
|
gasPrice: '0x0',
|
|
nonce: '0x64',
|
|
to: '0x0101',
|
|
value: '0x0',
|
|
},
|
|
},
|
|
},
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork,
|
|
[ROPSTEN]: 11,
|
|
},
|
|
},
|
|
'State should have been updated after first block was received',
|
|
)
|
|
})
|
|
|
|
it('should not update when switching to an unsupported network', async function () {
|
|
const networkController = getMockNetworkController(ROPSTEN_CHAIN_ID)
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController,
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
// reply with a valid request for any supported network, so that this test has every opportunity to fail
|
|
for (const network of [
|
|
GOERLI,
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
'undefined',
|
|
]) {
|
|
nock(
|
|
`https://api${
|
|
network === MAINNET ? '' : `-${network.toLowerCase()}`
|
|
}.etherscan.io`,
|
|
)
|
|
.get(/api.+/u)
|
|
.reply(
|
|
200,
|
|
JSON.stringify({
|
|
status: '1',
|
|
result: [getFakeEtherscanTransaction()],
|
|
}),
|
|
)
|
|
}
|
|
const updateStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'updateState',
|
|
)
|
|
const updateStateCalled = waitUntilCalled(
|
|
updateStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
const putStateStub = sinon.stub(
|
|
incomingTransactionsController.store,
|
|
'putState',
|
|
)
|
|
const putStateCalled = waitUntilCalled(
|
|
putStateStub,
|
|
incomingTransactionsController.store,
|
|
)
|
|
|
|
const subscription = incomingTransactionsController.networkController.on.getCall(
|
|
0,
|
|
).args[1]
|
|
|
|
networkController.getCurrentChainId = () => FAKE_CHAIN_ID
|
|
await subscription()
|
|
|
|
try {
|
|
await Promise.race([
|
|
updateStateCalled(),
|
|
putStateCalled(),
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error('TIMEOUT')), SET_STATE_TIMEOUT)
|
|
}),
|
|
])
|
|
assert.fail('Update state should not have been called')
|
|
} catch (error) {
|
|
assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('_getDataForUpdate', function () {
|
|
it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getEmptyInitState(),
|
|
},
|
|
)
|
|
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
|
|
|
await incomingTransactionsController._getDataForUpdate({
|
|
address: 'fakeAddress',
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
newBlockNumberDec: 999,
|
|
})
|
|
|
|
assert(incomingTransactionsController._fetchAll.calledOnce)
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController._fetchAll.getCall(0).args,
|
|
['fakeAddress', 999, ROPSTEN_CHAIN_ID],
|
|
)
|
|
})
|
|
|
|
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
|
|
|
await incomingTransactionsController._getDataForUpdate({
|
|
address: 'fakeAddress',
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
newBlockNumberDec: 999,
|
|
})
|
|
|
|
assert(incomingTransactionsController._fetchAll.calledOnce)
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController._fetchAll.getCall(0).args,
|
|
['fakeAddress', 4, ROPSTEN_CHAIN_ID],
|
|
)
|
|
})
|
|
|
|
it('should return the expected data', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
incomingTransactionsController._fetchAll = sinon.stub().returns({
|
|
latestIncomingTxBlockNumber: 444,
|
|
txs: [{ id: 555 }],
|
|
})
|
|
|
|
const result = await incomingTransactionsController._getDataForUpdate({
|
|
address: 'fakeAddress',
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
})
|
|
|
|
assert.deepEqual(result, {
|
|
latestIncomingTxBlockNumber: 444,
|
|
newTxs: [{ id: 555 }],
|
|
currentIncomingTxs: {
|
|
'0x123456': { id: 777 },
|
|
},
|
|
currentBlocksByNetwork: {
|
|
[GOERLI]: 1,
|
|
[KOVAN]: 2,
|
|
[MAINNET]: 3,
|
|
[RINKEBY]: 5,
|
|
[ROPSTEN]: 4,
|
|
},
|
|
fetchedBlockNumber: 4,
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_updateStateWithNewTxData', function () {
|
|
const MOCK_INPUT_WITHOUT_LASTEST = {
|
|
newTxs: [{ id: 555, hash: '0xfff' }],
|
|
currentIncomingTxs: {
|
|
'0x123456': { id: 777, hash: '0x123456' },
|
|
},
|
|
currentBlocksByNetwork: {
|
|
[GOERLI]: 1,
|
|
[KOVAN]: 2,
|
|
[MAINNET]: 3,
|
|
[RINKEBY]: 5,
|
|
[ROPSTEN]: 4,
|
|
},
|
|
fetchedBlockNumber: 1111,
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
}
|
|
|
|
const MOCK_INPUT_WITH_LASTEST = {
|
|
...MOCK_INPUT_WITHOUT_LASTEST,
|
|
latestIncomingTxBlockNumber: 444,
|
|
}
|
|
|
|
it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
sinon.spy(incomingTransactionsController.store, 'updateState')
|
|
|
|
await incomingTransactionsController._updateStateWithNewTxData(
|
|
MOCK_INPUT_WITH_LASTEST,
|
|
)
|
|
|
|
assert(incomingTransactionsController.store.updateState.calledOnce)
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController.store.updateState.getCall(0).args[0],
|
|
{
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
|
[ROPSTEN]: 445,
|
|
},
|
|
incomingTransactions: {
|
|
'0x123456': { id: 777, hash: '0x123456' },
|
|
'0xfff': { id: 555, hash: '0xfff' },
|
|
},
|
|
},
|
|
)
|
|
})
|
|
|
|
it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
sinon.spy(incomingTransactionsController.store, 'updateState')
|
|
|
|
await incomingTransactionsController._updateStateWithNewTxData(
|
|
MOCK_INPUT_WITHOUT_LASTEST,
|
|
)
|
|
|
|
assert(incomingTransactionsController.store.updateState.calledOnce)
|
|
|
|
assert.deepEqual(
|
|
incomingTransactionsController.store.updateState.getCall(0).args[0],
|
|
{
|
|
incomingTxLastFetchedBlocksByNetwork: {
|
|
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
|
[ROPSTEN]: 1112,
|
|
},
|
|
incomingTransactions: {
|
|
'0x123456': { id: 777, hash: '0x123456' },
|
|
'0xfff': { id: 555, hash: '0xfff' },
|
|
},
|
|
},
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('_fetchTxs', function () {
|
|
const mockFetch = sinon.stub().returns(
|
|
Promise.resolve({
|
|
json: () => Promise.resolve({ someKey: 'someValue' }),
|
|
}),
|
|
)
|
|
let tempFetch
|
|
beforeEach(function () {
|
|
tempFetch = window.fetch
|
|
window.fetch = mockFetch
|
|
})
|
|
|
|
afterEach(function () {
|
|
window.fetch = tempFetch
|
|
mockFetch.resetHistory()
|
|
})
|
|
|
|
it('should call fetch with the expected url when passed an address, block number and supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
await incomingTransactionsController._fetchTxs(
|
|
'0xfakeaddress',
|
|
'789',
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
|
|
assert(mockFetch.calledOnce)
|
|
assert.equal(
|
|
mockFetch.getCall(0).args[0],
|
|
`https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`,
|
|
)
|
|
})
|
|
|
|
it('should call fetch with the expected url when passed an address, block number and MAINNET', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(MAINNET_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
await incomingTransactionsController._fetchTxs(
|
|
'0xfakeaddress',
|
|
'789',
|
|
MAINNET_CHAIN_ID,
|
|
)
|
|
|
|
assert(mockFetch.calledOnce)
|
|
assert.equal(
|
|
mockFetch.getCall(0).args[0],
|
|
`https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`,
|
|
)
|
|
})
|
|
|
|
it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
await incomingTransactionsController._fetchTxs(
|
|
'0xfakeaddress',
|
|
null,
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
|
|
assert(mockFetch.calledOnce)
|
|
assert.equal(
|
|
mockFetch.getCall(0).args[0],
|
|
`https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`,
|
|
)
|
|
})
|
|
|
|
it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
const result = await incomingTransactionsController._fetchTxs(
|
|
'0xfakeaddress',
|
|
'789',
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
|
|
assert(mockFetch.calledOnce)
|
|
assert.deepEqual(result, {
|
|
someKey: 'someValue',
|
|
address: '0xfakeaddress',
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_processTxFetchResponse', function () {
|
|
it('should return a null block number and empty tx array if status is 0', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
const result = incomingTransactionsController._processTxFetchResponse({
|
|
status: '0',
|
|
result: [{ id: 1 }],
|
|
address: '0xfakeaddress',
|
|
})
|
|
|
|
assert.deepEqual(result, {
|
|
latestIncomingTxBlockNumber: null,
|
|
txs: [],
|
|
})
|
|
})
|
|
|
|
it('should return a null block number and empty tx array if the passed result array is empty', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
const result = incomingTransactionsController._processTxFetchResponse({
|
|
status: '1',
|
|
result: [],
|
|
address: '0xfakeaddress',
|
|
})
|
|
|
|
assert.deepEqual(result, {
|
|
latestIncomingTxBlockNumber: null,
|
|
txs: [],
|
|
})
|
|
})
|
|
|
|
it('should return the expected block number and tx list when passed data from a successful fetch', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
incomingTransactionsController._normalizeTxFromEtherscan = (tx) => ({
|
|
...tx,
|
|
currentNetworkID: ROPSTEN_NETWORK_ID,
|
|
normalized: true,
|
|
})
|
|
|
|
const result = incomingTransactionsController._processTxFetchResponse({
|
|
status: '1',
|
|
address: '0xfakeaddress',
|
|
chainId: ROPSTEN_CHAIN_ID,
|
|
result: [
|
|
{
|
|
hash: '0xabc123',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5000,
|
|
time: 10,
|
|
},
|
|
{
|
|
hash: '0xabc123',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5000,
|
|
time: 10,
|
|
},
|
|
{
|
|
hash: '0xabc1234',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5000,
|
|
time: 9,
|
|
},
|
|
{
|
|
hash: '0xabc12345',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5001,
|
|
time: 11,
|
|
},
|
|
{
|
|
hash: '0xabc123456',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5001,
|
|
time: 12,
|
|
},
|
|
{
|
|
hash: '0xabc1234567',
|
|
txParams: {
|
|
to: '0xanotherFakeaddress',
|
|
},
|
|
blockNumber: 5002,
|
|
time: 13,
|
|
},
|
|
],
|
|
})
|
|
|
|
assert.deepEqual(result, {
|
|
latestIncomingTxBlockNumber: 5001,
|
|
txs: [
|
|
{
|
|
hash: '0xabc1234',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5000,
|
|
time: 9,
|
|
normalized: true,
|
|
currentNetworkID: ROPSTEN_NETWORK_ID,
|
|
},
|
|
{
|
|
hash: '0xabc123',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5000,
|
|
time: 10,
|
|
normalized: true,
|
|
currentNetworkID: ROPSTEN_NETWORK_ID,
|
|
},
|
|
{
|
|
hash: '0xabc12345',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5001,
|
|
time: 11,
|
|
normalized: true,
|
|
currentNetworkID: ROPSTEN_NETWORK_ID,
|
|
},
|
|
{
|
|
hash: '0xabc123456',
|
|
txParams: {
|
|
to: '0xfakeaddress',
|
|
},
|
|
blockNumber: 5001,
|
|
time: 12,
|
|
normalized: true,
|
|
currentNetworkID: ROPSTEN_NETWORK_ID,
|
|
},
|
|
],
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('_normalizeTxFromEtherscan', function () {
|
|
it('should return the expected data when the tx is in error', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
const result = incomingTransactionsController._normalizeTxFromEtherscan(
|
|
{
|
|
timeStamp: '4444',
|
|
isError: '1',
|
|
blockNumber: 333,
|
|
from: '0xa',
|
|
gas: '11',
|
|
gasPrice: '12',
|
|
nonce: '13',
|
|
to: '0xe',
|
|
value: '15',
|
|
hash: '0xg',
|
|
},
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
|
|
assert.deepEqual(result, {
|
|
blockNumber: 333,
|
|
id: 54321,
|
|
metamaskNetworkId: ROPSTEN_NETWORK_ID,
|
|
status: TRANSACTION_STATUSES.FAILED,
|
|
time: 4444000,
|
|
txParams: {
|
|
from: '0xa',
|
|
gas: '0xb',
|
|
gasPrice: '0xc',
|
|
nonce: '0xd',
|
|
to: '0xe',
|
|
value: '0xf',
|
|
},
|
|
hash: '0xg',
|
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
|
})
|
|
})
|
|
|
|
it('should return the expected data when the tx is not in error', function () {
|
|
const incomingTransactionsController = new IncomingTransactionsController(
|
|
{
|
|
blockTracker: getMockBlockTracker(),
|
|
networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
|
|
preferencesController: getMockPreferencesController(),
|
|
initState: getNonEmptyInitState(),
|
|
},
|
|
)
|
|
|
|
const result = incomingTransactionsController._normalizeTxFromEtherscan(
|
|
{
|
|
timeStamp: '4444',
|
|
isError: '0',
|
|
blockNumber: 333,
|
|
from: '0xa',
|
|
gas: '11',
|
|
gasPrice: '12',
|
|
nonce: '13',
|
|
to: '0xe',
|
|
value: '15',
|
|
hash: '0xg',
|
|
},
|
|
ROPSTEN_CHAIN_ID,
|
|
)
|
|
|
|
assert.deepEqual(result, {
|
|
blockNumber: 333,
|
|
id: 54321,
|
|
metamaskNetworkId: ROPSTEN_NETWORK_ID,
|
|
status: TRANSACTION_STATUSES.CONFIRMED,
|
|
time: 4444000,
|
|
txParams: {
|
|
from: '0xa',
|
|
gas: '0xb',
|
|
gasPrice: '0xc',
|
|
nonce: '0xd',
|
|
to: '0xe',
|
|
value: '0xf',
|
|
},
|
|
hash: '0xg',
|
|
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
|
|
})
|
|
})
|
|
})
|
|
})
|