commit
922ecb9149
@ -0,0 +1,163 @@ |
|||||||
|
const EventEmitter = require('events') |
||||||
|
const EthQuery = require('ethjs-query') |
||||||
|
const sufficientBalance = require('./util').sufficientBalance |
||||||
|
/* |
||||||
|
|
||||||
|
Utility class for tracking the transactions as they |
||||||
|
go from a pending state to a confirmed (mined in a block) state |
||||||
|
|
||||||
|
As well as continues broadcast while in the pending state |
||||||
|
|
||||||
|
~config is not optional~ |
||||||
|
requires a: { |
||||||
|
provider: //,
|
||||||
|
nonceTracker: //see nonce tracker,
|
||||||
|
getBalnce: //(address) a function for getting balances,
|
||||||
|
getPendingTransactions: //() a function for getting an array of transactions,
|
||||||
|
publishTransaction: //(rawTx) a async function for publishing raw transactions,
|
||||||
|
} |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
module.exports = class PendingTransactionTracker extends EventEmitter { |
||||||
|
constructor (config) { |
||||||
|
super() |
||||||
|
this.query = new EthQuery(config.provider) |
||||||
|
this.nonceTracker = config.nonceTracker |
||||||
|
|
||||||
|
this.getBalance = config.getBalance |
||||||
|
this.getPendingTransactions = config.getPendingTransactions |
||||||
|
this.publishTransaction = config.publishTransaction |
||||||
|
} |
||||||
|
|
||||||
|
// checks if a signed tx is in a block and
|
||||||
|
// if included sets the tx status as 'confirmed'
|
||||||
|
checkForTxInBlock (block) { |
||||||
|
const signedTxList = this.getPendingTransactions() |
||||||
|
if (!signedTxList.length) return |
||||||
|
signedTxList.forEach((txMeta) => { |
||||||
|
const txHash = txMeta.hash |
||||||
|
const txId = txMeta.id |
||||||
|
|
||||||
|
if (!txHash) { |
||||||
|
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') |
||||||
|
noTxHashErr.name = 'NoTxHashError' |
||||||
|
this.emit('txFailed', txId, noTxHashErr) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
block.transactions.forEach((tx) => { |
||||||
|
if (tx.hash === txHash) this.emit('txConfirmed', txId) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
queryPendingTxs ({oldBlock, newBlock}) { |
||||||
|
// check pending transactions on start
|
||||||
|
if (!oldBlock) { |
||||||
|
this._checkPendingTxs() |
||||||
|
return |
||||||
|
} |
||||||
|
// if we synced by more than one block, check for missed pending transactions
|
||||||
|
const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16) |
||||||
|
if (diff > 1) this._checkPendingTxs() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
resubmitPendingTxs () { |
||||||
|
const pending = this.getPendingTransactions('status', 'submitted') |
||||||
|
// only try resubmitting if their are transactions to resubmit
|
||||||
|
if (!pending.length) return |
||||||
|
pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => { |
||||||
|
/* |
||||||
|
Dont marked as failed if the error is a "known" transaction warning |
||||||
|
"there is already a transaction with the same sender-nonce |
||||||
|
but higher/same gas price" |
||||||
|
*/ |
||||||
|
const errorMessage = err.message.toLowerCase() |
||||||
|
const isKnownTx = ( |
||||||
|
// geth
|
||||||
|
errorMessage.includes('replacement transaction underpriced') |
||||||
|
|| errorMessage.includes('known transaction') |
||||||
|
// parity
|
||||||
|
|| errorMessage.includes('gas price too low to replace') |
||||||
|
|| errorMessage.includes('transaction with the same hash was already imported') |
||||||
|
// other
|
||||||
|
|| errorMessage.includes('gateway timeout') |
||||||
|
|| errorMessage.includes('nonce too low') |
||||||
|
) |
||||||
|
// ignore resubmit warnings, return early
|
||||||
|
if (isKnownTx) return |
||||||
|
// encountered real error - transition to error state
|
||||||
|
this.emit('txFailed', txMeta.id, err) |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
async _resubmitTx (txMeta) { |
||||||
|
const address = txMeta.txParams.from |
||||||
|
const balance = this.getBalance(address) |
||||||
|
if (balance === undefined) return |
||||||
|
if (!('retryCount' in txMeta)) txMeta.retryCount = 0 |
||||||
|
|
||||||
|
// if the value of the transaction is greater then the balance, fail.
|
||||||
|
if (!sufficientBalance(txMeta.txParams, balance)) { |
||||||
|
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') |
||||||
|
this.emit('txFailed', txMeta.id, insufficientFundsError) |
||||||
|
log.error(insufficientFundsError) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Only auto-submit already-signed txs:
|
||||||
|
if (!('rawTx' in txMeta)) return |
||||||
|
|
||||||
|
// Increment a try counter.
|
||||||
|
txMeta.retryCount++ |
||||||
|
const rawTx = txMeta.rawTx |
||||||
|
return await this.publishTransaction(rawTx) |
||||||
|
} |
||||||
|
|
||||||
|
async _checkPendingTx (txMeta) { |
||||||
|
const txHash = txMeta.hash |
||||||
|
const txId = txMeta.id |
||||||
|
// extra check in case there was an uncaught error during the
|
||||||
|
// signature and submission process
|
||||||
|
if (!txHash) { |
||||||
|
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') |
||||||
|
noTxHashErr.name = 'NoTxHashError' |
||||||
|
this.emit('txFailed', txId, noTxHashErr) |
||||||
|
return |
||||||
|
} |
||||||
|
// get latest transaction status
|
||||||
|
let txParams |
||||||
|
try { |
||||||
|
txParams = await this.query.getTransactionByHash(txHash) |
||||||
|
if (!txParams) return |
||||||
|
if (txParams.blockNumber) { |
||||||
|
this.emit('txConfirmed', txId) |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
txMeta.warning = { |
||||||
|
error: err, |
||||||
|
message: 'There was a problem loading this transaction.', |
||||||
|
} |
||||||
|
this.emit('txWarning', txMeta) |
||||||
|
throw err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// checks the network for signed txs and
|
||||||
|
// if confirmed sets the tx status as 'confirmed'
|
||||||
|
async _checkPendingTxs () { |
||||||
|
const signedTxList = this.getPendingTransactions() |
||||||
|
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||||
|
const nonceGlobalLock = await this.nonceTracker.getGlobalLock() |
||||||
|
try { |
||||||
|
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) |
||||||
|
} catch (err) { |
||||||
|
console.error('PendingTransactionWatcher - Error updating pending transactions') |
||||||
|
console.error(err) |
||||||
|
} |
||||||
|
nonceGlobalLock.releaseLock() |
||||||
|
} |
||||||
|
} |
@ -1,8 +1,39 @@ |
|||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const BN = ethUtil.BN |
||||||
|
|
||||||
module.exports = { |
module.exports = { |
||||||
getStack, |
getStack, |
||||||
|
sufficientBalance, |
||||||
|
hexToBn, |
||||||
|
bnToHex, |
||||||
|
BnMultiplyByFraction, |
||||||
} |
} |
||||||
|
|
||||||
function getStack () { |
function getStack () { |
||||||
const stack = new Error('Stack trace generator - not an error').stack |
const stack = new Error('Stack trace generator - not an error').stack |
||||||
return stack |
return stack |
||||||
} |
} |
||||||
|
|
||||||
|
function sufficientBalance (txParams, hexBalance) { |
||||||
|
const balance = hexToBn(hexBalance) |
||||||
|
const value = hexToBn(txParams.value) |
||||||
|
const gasLimit = hexToBn(txParams.gas) |
||||||
|
const gasPrice = hexToBn(txParams.gasPrice) |
||||||
|
|
||||||
|
const maxCost = value.add(gasLimit.mul(gasPrice)) |
||||||
|
return balance.gte(maxCost) |
||||||
|
} |
||||||
|
|
||||||
|
function bnToHex (inputBn) { |
||||||
|
return ethUtil.addHexPrefix(inputBn.toString(16)) |
||||||
|
} |
||||||
|
|
||||||
|
function hexToBn (inputHex) { |
||||||
|
return new BN(ethUtil.stripHexPrefix(inputHex), 16) |
||||||
|
} |
||||||
|
|
||||||
|
function BnMultiplyByFraction (targetBN, numerator, denominator) { |
||||||
|
const numBN = new BN(numerator) |
||||||
|
const denomBN = new BN(denominator) |
||||||
|
return targetBN.mul(numBN).div(denomBN) |
||||||
|
} |
||||||
|
@ -0,0 +1,25 @@ |
|||||||
|
const JsonRpcEngine = require('json-rpc-engine') |
||||||
|
const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
createEngineForTestData, |
||||||
|
providerFromEngine, |
||||||
|
scaffoldMiddleware, |
||||||
|
createStubedProvider |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function createEngineForTestData () { |
||||||
|
return new JsonRpcEngine() |
||||||
|
} |
||||||
|
|
||||||
|
function providerFromEngine (engine) { |
||||||
|
const provider = { sendAsync: engine.handle.bind(engine) } |
||||||
|
return provider |
||||||
|
} |
||||||
|
|
||||||
|
function createStubedProvider (resultStub) { |
||||||
|
const engine = createEngineForTestData() |
||||||
|
engine.push(scaffoldMiddleware(resultStub)) |
||||||
|
return providerFromEngine(engine) |
||||||
|
} |
@ -0,0 +1,260 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const EthTx = require('ethereumjs-tx') |
||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const clone = require('clone') |
||||||
|
const { createStubedProvider } = require('../stub/provider') |
||||||
|
const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker') |
||||||
|
const noop = () => true |
||||||
|
const currentNetworkId = 42 |
||||||
|
const otherNetworkId = 36 |
||||||
|
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') |
||||||
|
|
||||||
|
describe('PendingTransactionTracker', function () { |
||||||
|
let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider |
||||||
|
this.timeout(10000) |
||||||
|
beforeEach(function () { |
||||||
|
txMeta = { |
||||||
|
id: 1, |
||||||
|
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', |
||||||
|
status: 'signed', |
||||||
|
txParams: { |
||||||
|
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||||
|
nonce: '0x1', |
||||||
|
value: '0xfffff', |
||||||
|
}, |
||||||
|
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', |
||||||
|
} |
||||||
|
txMetaNoHash = { |
||||||
|
id: 2, |
||||||
|
status: 'signed', |
||||||
|
txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, |
||||||
|
} |
||||||
|
txMetaNoRawTx = { |
||||||
|
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', |
||||||
|
status: 'signed', |
||||||
|
txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, |
||||||
|
} |
||||||
|
providerResultStub = {} |
||||||
|
provider = createStubedProvider(providerResultStub) |
||||||
|
|
||||||
|
pendingTxTracker = new PendingTransactionTracker({ |
||||||
|
provider, |
||||||
|
getBalance: () => {}, |
||||||
|
nonceTracker: { |
||||||
|
getGlobalLock: async () => { |
||||||
|
return { releaseLock: () => {} } |
||||||
|
} |
||||||
|
}, |
||||||
|
getPendingTransactions: () => {return []}, |
||||||
|
sufficientBalance: () => {}, |
||||||
|
publishTransaction: () => {}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#checkForTxInBlock', function () { |
||||||
|
it('should return if no pending transactions', function () { |
||||||
|
// throw a type error if it trys to do anything on the block
|
||||||
|
// thus failing the test
|
||||||
|
const block = Proxy.revocable({}, {}).revoke() |
||||||
|
pendingTxTracker.checkForTxInBlock(block) |
||||||
|
}) |
||||||
|
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { |
||||||
|
const block = Proxy.revocable({}, {}).revoke() |
||||||
|
pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] |
||||||
|
pendingTxTracker.once('txFailed', (txId, err) => { |
||||||
|
assert(txId, txMetaNoHash.id, 'should pass txId') |
||||||
|
done() |
||||||
|
}) |
||||||
|
pendingTxTracker.checkForTxInBlock(block) |
||||||
|
}) |
||||||
|
it('should emit \'txConfirmed\' if the tx is in the block', function (done) { |
||||||
|
const block = { transactions: [txMeta]} |
||||||
|
pendingTxTracker.getPendingTransactions = () => [txMeta] |
||||||
|
pendingTxTracker.once('txConfirmed', (txId) => { |
||||||
|
assert(txId, txMeta.id, 'should pass txId') |
||||||
|
done() |
||||||
|
}) |
||||||
|
pendingTxTracker.once('txFailed', (_, err) => { done(err) }) |
||||||
|
pendingTxTracker.checkForTxInBlock(block) |
||||||
|
}) |
||||||
|
}) |
||||||
|
describe('#queryPendingTxs', function () { |
||||||
|
it('should call #_checkPendingTxs if their is no oldBlock', function (done) { |
||||||
|
let newBlock, oldBlock |
||||||
|
newBlock = { number: '0x01' } |
||||||
|
pendingTxTracker._checkPendingTxs = done |
||||||
|
pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) |
||||||
|
}) |
||||||
|
it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { |
||||||
|
let newBlock, oldBlock |
||||||
|
oldBlock = { number: '0x01' } |
||||||
|
newBlock = { number: '0x03' } |
||||||
|
pendingTxTracker._checkPendingTxs = done |
||||||
|
pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) |
||||||
|
}) |
||||||
|
it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { |
||||||
|
let newBlock, oldBlock |
||||||
|
oldBlock = { number: '0x1' } |
||||||
|
newBlock = { number: '0x2' } |
||||||
|
pendingTxTracker._checkPendingTxs = () => { |
||||||
|
const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') |
||||||
|
done(err) |
||||||
|
} |
||||||
|
pendingTxTracker.queryPendingTxs({oldBlock, newBlock}) |
||||||
|
done() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#_checkPendingTx', function () { |
||||||
|
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { |
||||||
|
pendingTxTracker.once('txFailed', (txId, err) => { |
||||||
|
assert(txId, txMetaNoHash.id, 'should pass txId') |
||||||
|
done() |
||||||
|
}) |
||||||
|
pendingTxTracker._checkPendingTx(txMetaNoHash) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should should return if query does not return txParams', function () { |
||||||
|
providerResultStub.eth_getTransactionByHash = null |
||||||
|
pendingTxTracker._checkPendingTx(txMeta) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should emit \'txConfirmed\'', function (done) { |
||||||
|
providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} |
||||||
|
pendingTxTracker.once('txConfirmed', (txId) => { |
||||||
|
assert(txId, txMeta.id, 'should pass txId') |
||||||
|
done() |
||||||
|
}) |
||||||
|
pendingTxTracker.once('txFailed', (_, err) => { done(err) }) |
||||||
|
pendingTxTracker._checkPendingTx(txMeta) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#_checkPendingTxs', function () { |
||||||
|
beforeEach(function () { |
||||||
|
const txMeta2 = txMeta3 = txMeta |
||||||
|
txMeta2.id = 2 |
||||||
|
txMeta3.id = 3 |
||||||
|
txList = [txMeta, txMeta2, txMeta3].map((tx) => { |
||||||
|
tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) |
||||||
|
return tx |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should warp all txMeta\'s in #_checkPendingTx', function (done) { |
||||||
|
pendingTxTracker.getPendingTransactions = () => txList |
||||||
|
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } |
||||||
|
const list = txList.map |
||||||
|
Promise.all(txList.map((tx) => tx.processed)) |
||||||
|
.then((txCompletedList) => done()) |
||||||
|
.catch(done) |
||||||
|
|
||||||
|
pendingTxTracker._checkPendingTxs() |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#resubmitPendingTxs', function () { |
||||||
|
beforeEach(function () { |
||||||
|
const txMeta2 = txMeta3 = txMeta |
||||||
|
txList = [txMeta, txMeta2, txMeta3].map((tx) => { |
||||||
|
tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) |
||||||
|
return tx |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return if no pending transactions', function () { |
||||||
|
pendingTxTracker.resubmitPendingTxs() |
||||||
|
}) |
||||||
|
it('should call #_resubmitTx for all pending tx\'s', function (done) { |
||||||
|
pendingTxTracker.getPendingTransactions = () => txList |
||||||
|
pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } |
||||||
|
Promise.all(txList.map((tx) => tx.processed)) |
||||||
|
.then((txCompletedList) => done()) |
||||||
|
.catch(done) |
||||||
|
pendingTxTracker.resubmitPendingTxs() |
||||||
|
}) |
||||||
|
it('should not emit \'txFailed\' if the txMeta throws a known txError', function (done) { |
||||||
|
knownErrors =[ |
||||||
|
// geth
|
||||||
|
' Replacement transaction Underpriced ', |
||||||
|
' known transaction', |
||||||
|
// parity
|
||||||
|
'Gas price too low to replace ', |
||||||
|
' transaction with the sAme hash was already imported', |
||||||
|
// other
|
||||||
|
' gateway timeout', |
||||||
|
' noncE too low ', |
||||||
|
] |
||||||
|
const enoughForAllErrors = txList.concat(txList) |
||||||
|
|
||||||
|
pendingTxTracker.on('txFailed', (_, err) => done(err)) |
||||||
|
|
||||||
|
pendingTxTracker.getPendingTransactions = () => enoughForAllErrors |
||||||
|
pendingTxTracker._resubmitTx = async (tx) => { |
||||||
|
tx.resolve() |
||||||
|
throw new Error(knownErrors.pop()) |
||||||
|
} |
||||||
|
Promise.all(txList.map((tx) => tx.processed)) |
||||||
|
.then((txCompletedList) => done()) |
||||||
|
.catch(done) |
||||||
|
|
||||||
|
pendingTxTracker.resubmitPendingTxs() |
||||||
|
}) |
||||||
|
it('should emit \'txFailed\' if it encountered a real error', function (done) { |
||||||
|
pendingTxTracker.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err)) |
||||||
|
|
||||||
|
pendingTxTracker.getPendingTransactions = () => txList |
||||||
|
pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') } |
||||||
|
Promise.all(txList.map((tx) => tx.processed)) |
||||||
|
.then((txCompletedList) => done()) |
||||||
|
.catch(done) |
||||||
|
|
||||||
|
pendingTxTracker.resubmitPendingTxs() |
||||||
|
}) |
||||||
|
}) |
||||||
|
describe('#_resubmitTx with a too-low balance', function () { |
||||||
|
it('should return before publishing the transaction because to low of balance', function (done) { |
||||||
|
const lowBalance = '0x0' |
||||||
|
pendingTxTracker.getBalance = (address) => { |
||||||
|
assert.equal(address, txMeta.txParams.from, 'Should pass the address') |
||||||
|
return lowBalance |
||||||
|
} |
||||||
|
pendingTxTracker.publishTransaction = async (rawTx) => { |
||||||
|
done(new Error('tried to publish transaction')) |
||||||
|
} |
||||||
|
|
||||||
|
// Stubbing out current account state:
|
||||||
|
// Adding the fake tx:
|
||||||
|
pendingTxTracker.once('txFailed', (txId, err) => { |
||||||
|
assert(err, 'Should have a error') |
||||||
|
done() |
||||||
|
}) |
||||||
|
pendingTxTracker._resubmitTx(txMeta) |
||||||
|
.catch((err) => { |
||||||
|
assert.ifError(err, 'should not throw an error') |
||||||
|
done(err) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should publishing the transaction', function (done) { |
||||||
|
const enoughBalance = '0x100000' |
||||||
|
pendingTxTracker.getBalance = (address) => { |
||||||
|
assert.equal(address, txMeta.txParams.from, 'Should pass the address') |
||||||
|
return enoughBalance |
||||||
|
} |
||||||
|
pendingTxTracker.publishTransaction = async (rawTx) => { |
||||||
|
assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx') |
||||||
|
} |
||||||
|
|
||||||
|
// Stubbing out current account state:
|
||||||
|
// Adding the fake tx:
|
||||||
|
pendingTxTracker._resubmitTx(txMeta) |
||||||
|
.then(() => done()) |
||||||
|
.catch((err) => { |
||||||
|
assert.ifError(err, 'should not throw an error') |
||||||
|
done(err) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,41 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const { sufficientBalance } = require('../../app/scripts/lib/util') |
||||||
|
|
||||||
|
|
||||||
|
describe('SufficientBalance', function () { |
||||||
|
it('returns true if max tx cost is equal to balance.', function () { |
||||||
|
const tx = { |
||||||
|
'value': '0x1', |
||||||
|
'gas': '0x2', |
||||||
|
'gasPrice': '0x3', |
||||||
|
} |
||||||
|
const balance = '0x8' |
||||||
|
|
||||||
|
const result = sufficientBalance(tx, balance) |
||||||
|
assert.ok(result, 'sufficient balance found.') |
||||||
|
}) |
||||||
|
|
||||||
|
it('returns true if max tx cost is less than balance.', function () { |
||||||
|
const tx = { |
||||||
|
'value': '0x1', |
||||||
|
'gas': '0x2', |
||||||
|
'gasPrice': '0x3', |
||||||
|
} |
||||||
|
const balance = '0x9' |
||||||
|
|
||||||
|
const result = sufficientBalance(tx, balance) |
||||||
|
assert.ok(result, 'sufficient balance found.') |
||||||
|
}) |
||||||
|
|
||||||
|
it('returns false if max tx cost is more than balance.', function () { |
||||||
|
const tx = { |
||||||
|
'value': '0x1', |
||||||
|
'gas': '0x2', |
||||||
|
'gasPrice': '0x3', |
||||||
|
} |
||||||
|
const balance = '0x6' |
||||||
|
|
||||||
|
const result = sufficientBalance(tx, balance) |
||||||
|
assert.ok(!result, 'insufficient balance found.') |
||||||
|
}) |
||||||
|
}) |
Loading…
Reference in new issue