1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-26 13:20:26 +02:00
metamask-extension/test/unit/app/controllers/swaps-test.js

1006 lines
31 KiB
JavaScript
Raw Normal View History

2020-10-06 20:28:38 +02:00
import assert from 'assert'
import sinon from 'sinon'
import { ethers } from 'ethers'
import BigNumber from 'bignumber.js'
import ObservableStore from 'obs-store'
2020-11-03 00:41:28 +01:00
import {
ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID,
} from '../../../../app/scripts/controllers/network/enums'
2020-10-06 20:28:38 +02:00
import { createTestProviderTools } from '../../../stub/provider'
2020-11-03 00:41:28 +01:00
import SwapsController, {
utils,
} from '../../../../app/scripts/controllers/swaps'
2020-10-06 20:28:38 +02:00
const MOCK_FETCH_PARAMS = {
slippage: 3,
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
sourceDecimals: 18,
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
value: '1000000000000000000',
fromAddress: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
exchangeList: 'zeroExV1',
}
const TEST_AGG_ID_1 = 'TEST_AGG_1'
const TEST_AGG_ID_2 = 'TEST_AGG_2'
const TEST_AGG_ID_BEST = 'TEST_AGG_BEST'
const TEST_AGG_ID_APPROVAL = 'TEST_AGG_APPROVAL'
const MOCK_APPROVAL_NEEDED = {
2020-11-03 00:41:28 +01:00
data:
'0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
amount: '0',
from: '0x2369267687A84ac7B494daE2f1542C40E37f4455',
gas: '12',
gasPrice: '34',
}
const MOCK_QUOTES_APPROVAL_REQUIRED = {
[TEST_AGG_ID_APPROVAL]: {
trade: {
data: '0x00',
from: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
value: '0x17647444f166000',
gas: '0xe09c0',
gasPrice: undefined,
to: '0x881d40237659c251811cec9c364ef91dc08d300c',
},
sourceAmount: '1000000000000000000000000000000000000',
destinationAmount: '396493201125465',
error: null,
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
maxGas: 920000,
averageGas: 312510,
estimatedRefund: 343090,
fetchTime: 559,
aggregator: TEST_AGG_ID_APPROVAL,
aggType: 'AGG',
slippage: 3,
approvalNeeded: MOCK_APPROVAL_NEEDED,
},
}
2020-10-06 20:28:38 +02:00
const MOCK_FETCH_METADATA = {
destinationTokenInfo: {
symbol: 'FOO',
decimals: 18,
},
}
const MOCK_TOKEN_RATES_STORE = new ObservableStore({
contractExchangeRates: { '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2 },
})
const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' })
const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({
gasLimit: 2000000,
simulationFails: undefined,
})
2020-11-03 00:41:28 +01:00
function getMockNetworkController() {
return {
store: {
getState: () => {
return {
network: ROPSTEN_NETWORK_ID,
}
},
},
on: sinon.stub().withArgs('networkDidChange').callsArgAsync(1),
}
}
2020-10-06 20:28:38 +02:00
const EMPTY_INIT_STATE = {
swapsState: {
quotes: {},
fetchParams: null,
tokens: null,
tradeTxId: null,
approveTxId: null,
quotesLastFetched: null,
customMaxGas: '',
customGasPrice: null,
selectedAggId: null,
customApproveTxData: '',
errorKey: '',
topAggId: null,
routeState: '',
swapsFeatureIsLive: false,
},
}
const sandbox = sinon.createSandbox()
const fetchTradesInfoStub = sandbox.stub()
const fetchSwapsFeatureLivenessStub = sandbox.stub()
describe('SwapsController', function () {
let provider
const getSwapsController = () => {
return new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController: getMockNetworkController(),
2020-10-06 20:28:38 +02:00
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
})
}
before(function () {
const providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0x',
}
2020-11-03 00:41:28 +01:00
provider = createTestProviderTools({
scaffold: providerResultStub,
networkId: 1,
chainId: 1,
}).provider
2020-10-06 20:28:38 +02:00
})
afterEach(function () {
sandbox.restore()
})
describe('constructor', function () {
it('should setup correctly', function () {
const swapsController = getSwapsController()
assert.deepStrictEqual(swapsController.store.getState(), EMPTY_INIT_STATE)
assert.deepStrictEqual(
swapsController.getBufferedGasLimit,
MOCK_GET_BUFFERED_GAS_LIMIT,
)
assert.strictEqual(swapsController.pollCount, 0)
assert.deepStrictEqual(
swapsController.getProviderConfig,
MOCK_GET_PROVIDER_CONFIG,
)
})
it('should replace ethers instance when network changes', function () {
const networkController = getMockNetworkController()
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
})
const currentEthersInstance = swapsController.ethersProvider
const onNetworkDidChange = networkController.on.getCall(0).args[1]
onNetworkDidChange(MAINNET_NETWORK_ID)
const newEthersInstance = swapsController.ethersProvider
assert.notStrictEqual(
currentEthersInstance,
newEthersInstance,
'Ethers provider should be replaced',
)
})
it('should not replace ethers instance when network changes to loading', function () {
const networkController = getMockNetworkController()
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
})
const currentEthersInstance = swapsController.ethersProvider
const onNetworkDidChange = networkController.on.getCall(0).args[1]
onNetworkDidChange('loading')
const newEthersInstance = swapsController.ethersProvider
assert.strictEqual(
currentEthersInstance,
newEthersInstance,
'Ethers provider should not be replaced',
)
})
it('should not replace ethers instance when network changes to the same network', function () {
const networkController = getMockNetworkController()
const swapsController = new SwapsController({
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
networkController,
provider,
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
})
const currentEthersInstance = swapsController.ethersProvider
const onNetworkDidChange = networkController.on.getCall(0).args[1]
onNetworkDidChange(ROPSTEN_NETWORK_ID)
const newEthersInstance = swapsController.ethersProvider
assert.strictEqual(
currentEthersInstance,
newEthersInstance,
'Ethers provider should not be replaced',
)
})
2020-10-06 20:28:38 +02:00
})
describe('API', function () {
let swapsController
beforeEach(function () {
swapsController = getSwapsController()
})
describe('setters', function () {
it('should set selected quote agg id', function () {
const selectedAggId = 'test'
swapsController.setSelectedQuoteAggId(selectedAggId)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.selectedAggId,
selectedAggId,
)
})
it('should set swaps tokens', function () {
const tokens = []
swapsController.setSwapsTokens(tokens)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.tokens,
tokens,
)
})
it('should set trade tx id', function () {
const tradeTxId = 'test'
swapsController.setTradeTxId(tradeTxId)
assert.strictEqual(
swapsController.store.getState().swapsState.tradeTxId,
tradeTxId,
)
})
it('should set swaps tx gas price', function () {
const gasPrice = 1
swapsController.setSwapsTxGasPrice(gasPrice)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.customGasPrice,
gasPrice,
)
})
it('should set swaps tx gas limit', function () {
const gasLimit = '1'
swapsController.setSwapsTxGasLimit(gasLimit)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.customMaxGas,
gasLimit,
)
})
it('should set background swap route state', function () {
const routeState = 'test'
swapsController.setBackgroundSwapRouteState(routeState)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.routeState,
routeState,
)
})
it('should set swaps error key', function () {
const errorKey = 'test'
swapsController.setSwapsErrorKey(errorKey)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.errorKey,
errorKey,
)
})
it('should set initial gas estimate', async function () {
const initialAggId = TEST_AGG_ID_1
2020-10-06 20:28:38 +02:00
const baseGasEstimate = 10
const { maxGas, estimatedRefund } = getMockQuotes()[TEST_AGG_ID_1]
2020-10-06 20:28:38 +02:00
const { swapsState } = swapsController.store.getState()
// Set mock quotes in order to have data for the test agg
swapsController.store.updateState({
swapsState: { ...swapsState, quotes: getMockQuotes() },
2020-10-06 20:28:38 +02:00
})
await swapsController.setInitialGasEstimate(
initialAggId,
baseGasEstimate,
)
const {
gasLimit: bufferedGasLimit,
} = await swapsController.getBufferedGasLimit()
const {
gasEstimate,
gasEstimateWithRefund,
} = swapsController.store.getState().swapsState.quotes[initialAggId]
assert.strictEqual(gasEstimate, bufferedGasLimit)
assert.strictEqual(
gasEstimateWithRefund,
new BigNumber(maxGas, 10).minus(estimatedRefund, 10).toString(16),
)
})
it('should set custom approve tx data', function () {
const data = 'test'
swapsController.setCustomApproveTxData(data)
assert.deepStrictEqual(
swapsController.store.getState().swapsState.customApproveTxData,
data,
)
})
})
describe('_findTopQuoteAndCalculateSavings', function () {
it('returns empty object if passed undefined or empty object', async function () {
assert.deepStrictEqual(
await swapsController._findTopQuoteAndCalculateSavings(),
{},
)
assert.deepStrictEqual(
await swapsController._findTopQuoteAndCalculateSavings({}),
{},
)
})
})
2020-10-06 20:28:38 +02:00
describe('fetchAndSetQuotes', function () {
it('returns null if fetchParams is not provided', async function () {
const quotes = await swapsController.fetchAndSetQuotes(undefined)
assert.strictEqual(quotes, null)
})
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
fetchTradesInfoStub.resolves(getMockQuotes())
2020-10-06 20:28:38 +02:00
// Make it so approval is not required
sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(1))
const [newQuotes] = await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
assert.deepStrictEqual(newQuotes[TEST_AGG_ID_BEST], {
...getMockQuotes()[TEST_AGG_ID_BEST],
2020-10-06 20:28:38 +02:00
sourceTokenInfo: undefined,
destinationTokenInfo: {
symbol: 'FOO',
decimals: 18,
},
isBestQuote: true,
// TODO: find a way to calculate these values dynamically
gasEstimate: 2000000,
gasEstimateWithRefund: 'b8cae',
savings: {
fee: '0',
performance: '6',
total: '6',
},
2020-10-06 20:28:38 +02:00
})
assert.strictEqual(
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS),
true,
)
})
it('performs the allowance check', async function () {
fetchTradesInfoStub.resolves(getMockQuotes())
2020-10-06 20:28:38 +02:00
// Make it so approval is not required
const allowanceStub = sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(1))
await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
assert.strictEqual(
allowanceStub.calledOnceWithExactly(
MOCK_FETCH_PARAMS.sourceToken,
MOCK_FETCH_PARAMS.fromAddress,
),
true,
)
})
it('gets the gas limit if approval is required', async function () {
fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED)
2020-10-06 20:28:38 +02:00
// Ensure approval is required
sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(0))
const timedoutGasReturnResult = { gasLimit: 1000000 }
const timedoutGasReturnStub = sandbox
.stub(swapsController, 'timedoutGasReturn')
.resolves(timedoutGasReturnResult)
await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
// Mocked quotes approvalNeeded is null, so it will only be called with the gas
assert.strictEqual(
timedoutGasReturnStub.calledOnceWithExactly(MOCK_APPROVAL_NEEDED),
2020-10-06 20:28:38 +02:00
true,
)
})
it('marks the best quote', async function () {
fetchTradesInfoStub.resolves(getMockQuotes())
2020-10-06 20:28:38 +02:00
// Make it so approval is not required
sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(1))
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
assert.strictEqual(topAggId, TEST_AGG_ID_BEST)
2020-10-06 20:28:38 +02:00
assert.strictEqual(newQuotes[topAggId].isBestQuote, true)
})
it('selects the best quote', async function () {
const bestAggId = 'bestAggId'
// Clone the existing mock quote and increase destination amount
const bestQuote = {
...getMockQuotes()[TEST_AGG_ID_1],
2020-10-06 20:28:38 +02:00
aggregator: bestAggId,
destinationAmount: ethers.BigNumber.from(
getMockQuotes()[TEST_AGG_ID_1].destinationAmount,
2020-10-06 20:28:38 +02:00
)
.add((100e18).toString())
2020-10-06 20:28:38 +02:00
.toString(),
}
const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote }
2020-10-06 20:28:38 +02:00
fetchTradesInfoStub.resolves(quotes)
// Make it so approval is not required
sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(1))
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
assert.strictEqual(topAggId, bestAggId)
assert.strictEqual(newQuotes[topAggId].isBestQuote, true)
})
it('does not mark as best quote if no conversion rate exists for destination token', async function () {
fetchTradesInfoStub.resolves(getMockQuotes())
2020-10-06 20:28:38 +02:00
// Make it so approval is not required
sandbox
.stub(swapsController, '_getERC20Allowance')
.resolves(ethers.BigNumber.from(1))
swapsController.tokenRatesStore.updateState({
contractExchangeRates: {},
})
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
)
assert.strictEqual(newQuotes[topAggId].isBestQuote, false)
2020-10-06 20:28:38 +02:00
})
})
describe('resetSwapsState', function () {
it('resets the swaps state correctly', function () {
const { swapsState: old } = swapsController.store.getState()
swapsController.resetSwapsState()
const { swapsState } = swapsController.store.getState()
assert.deepStrictEqual(swapsState, {
...EMPTY_INIT_STATE.swapsState,
tokens: old.tokens,
})
})
it('clears polling timeout', function () {
swapsController.pollingTimeout = setTimeout(
() => assert.fail(),
1000000,
)
swapsController.resetSwapsState()
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
})
})
describe('stopPollingForQuotes', function () {
it('clears polling timeout', function () {
swapsController.pollingTimeout = setTimeout(
() => assert.fail(),
1000000,
)
swapsController.stopPollingForQuotes()
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
})
it('resets quotes state correctly', function () {
swapsController.stopPollingForQuotes()
const { swapsState } = swapsController.store.getState()
assert.deepStrictEqual(swapsState.quotes, {})
assert.strictEqual(swapsState.quotesLastFetched, null)
})
})
describe('resetPostFetchState', function () {
it('clears polling timeout', function () {
swapsController.pollingTimeout = setTimeout(
() => assert.fail(),
1000000,
)
swapsController.resetPostFetchState()
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
})
it('updates state correctly', function () {
const tokens = 'test'
const fetchParams = 'test'
const swapsFeatureIsLive = false
swapsController.store.updateState({
swapsState: { tokens, fetchParams, swapsFeatureIsLive },
})
swapsController.resetPostFetchState()
const { swapsState } = swapsController.store.getState()
assert.deepStrictEqual(swapsState, {
...EMPTY_INIT_STATE.swapsState,
tokens,
fetchParams,
swapsFeatureIsLive,
})
})
})
describe('_setupSwapsLivenessFetching ', function () {
let clock
const EXPECTED_TIME = 600000
const getLivenessState = () => {
return swapsController.store.getState().swapsState.swapsFeatureIsLive
}
// We have to do this to overwrite window.navigator.onLine
const stubWindow = () => {
sandbox.replace(global, 'window', {
addEventListener: window.addEventListener,
navigator: { onLine: true },
dispatchEvent: window.dispatchEvent,
Event: window.Event,
})
}
beforeEach(function () {
stubWindow()
clock = sandbox.useFakeTimers()
sandbox.spy(clock, 'setInterval')
2020-11-03 00:41:28 +01:00
sandbox
.stub(SwapsController.prototype, '_fetchAndSetSwapsLiveness')
.resolves(undefined)
2020-10-06 20:28:38 +02:00
2020-11-03 00:41:28 +01:00
sandbox.spy(SwapsController.prototype, '_setupSwapsLivenessFetching')
2020-10-06 20:28:38 +02:00
sandbox.spy(window, 'addEventListener')
})
afterEach(function () {
sandbox.restore()
})
it('calls _setupSwapsLivenessFetching in constructor', function () {
swapsController = getSwapsController()
assert.ok(
swapsController._setupSwapsLivenessFetching.calledOnce,
'should have called _setupSwapsLivenessFetching once',
)
2020-11-03 00:41:28 +01:00
assert.ok(window.addEventListener.calledWith('online'))
assert.ok(window.addEventListener.calledWith('offline'))
2020-10-06 20:28:38 +02:00
assert.ok(
clock.setInterval.calledOnceWithExactly(
sinon.match.func,
EXPECTED_TIME,
),
'should have set an interval',
)
})
it('handles browser being offline on boot, then coming online', async function () {
window.navigator.onLine = false
swapsController = getSwapsController()
assert.ok(
swapsController._setupSwapsLivenessFetching.calledOnce,
'should have called _setupSwapsLivenessFetching once',
)
assert.ok(
swapsController._fetchAndSetSwapsLiveness.notCalled,
'should not have called _fetchAndSetSwapsLiveness',
)
assert.ok(
clock.setInterval.notCalled,
'should not have set an interval',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
2020-10-06 20:28:38 +02:00
'swaps feature should be disabled',
)
const fetchPromise = new Promise((resolve) => {
const originalFunction = swapsController._fetchAndSetSwapsLiveness
swapsController._fetchAndSetSwapsLiveness = () => {
originalFunction()
resolve()
swapsController._fetchAndSetSwapsLiveness = originalFunction
}
})
// browser comes online
window.navigator.onLine = true
window.dispatchEvent(new window.Event('online'))
await fetchPromise
assert.ok(
swapsController._fetchAndSetSwapsLiveness.calledOnce,
'should have called _fetchAndSetSwapsLiveness once',
)
assert.ok(
clock.setInterval.calledOnceWithExactly(
sinon.match.func,
EXPECTED_TIME,
),
'should have set an interval',
)
})
it('clears interval if browser goes offline', async function () {
swapsController = getSwapsController()
// set feature to live
const { swapsState } = swapsController.store.getState()
swapsController.store.updateState({
swapsState: { ...swapsState, swapsFeatureIsLive: true },
})
sandbox.spy(swapsController.store, 'updateState')
assert.ok(
clock.setInterval.calledOnceWithExactly(
sinon.match.func,
EXPECTED_TIME,
),
'should have set an interval',
)
const clearIntervalPromise = new Promise((resolve) => {
const originalFunction = clock.clearInterval
clock.clearInterval = (intervalId) => {
originalFunction(intervalId)
clock.clearInterval = originalFunction
resolve()
}
})
// browser goes offline
window.navigator.onLine = false
window.dispatchEvent(new window.Event('offline'))
// if this resolves, clearInterval was called
await clearIntervalPromise
assert.ok(
swapsController._fetchAndSetSwapsLiveness.calledOnce,
'should have called _fetchAndSetSwapsLiveness once',
)
assert.ok(
swapsController.store.updateState.calledOnce,
'should have called updateState once',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
2020-10-06 20:28:38 +02:00
'swaps feature should be disabled',
)
})
})
describe('_fetchAndSetSwapsLiveness', function () {
const getLivenessState = () => {
return swapsController.store.getState().swapsState.swapsFeatureIsLive
}
beforeEach(function () {
fetchSwapsFeatureLivenessStub.reset()
2020-11-03 00:41:28 +01:00
sandbox.stub(SwapsController.prototype, '_setupSwapsLivenessFetching')
2020-10-06 20:28:38 +02:00
swapsController = getSwapsController()
})
afterEach(function () {
sandbox.restore()
})
it('fetches feature liveness as expected when API is live', async function () {
fetchSwapsFeatureLivenessStub.resolves(true)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should be false on boot',
2020-10-06 20:28:38 +02:00
)
await swapsController._fetchAndSetSwapsLiveness()
assert.ok(
fetchSwapsFeatureLivenessStub.calledOnce,
'should have called fetch function once',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
true,
'liveness should be true after call',
2020-10-06 20:28:38 +02:00
)
})
it('does not update state if fetched value is same as state value', async function () {
fetchSwapsFeatureLivenessStub.resolves(false)
sandbox.spy(swapsController.store, 'updateState')
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should be false on boot',
2020-10-06 20:28:38 +02:00
)
await swapsController._fetchAndSetSwapsLiveness()
assert.ok(
fetchSwapsFeatureLivenessStub.calledOnce,
'should have called fetch function once',
)
assert.ok(
swapsController.store.updateState.notCalled,
'should not have called store.updateState',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should remain false after call',
2020-10-06 20:28:38 +02:00
)
})
it('tries three times before giving up if fetching fails', async function () {
const clock = sandbox.useFakeTimers()
fetchSwapsFeatureLivenessStub.rejects(new Error('foo'))
sandbox.spy(swapsController.store, 'updateState')
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should be false on boot',
2020-10-06 20:28:38 +02:00
)
swapsController._fetchAndSetSwapsLiveness()
await clock.runAllAsync()
assert.ok(
fetchSwapsFeatureLivenessStub.calledThrice,
'should have called fetch function three times',
)
assert.ok(
swapsController.store.updateState.notCalled,
'should not have called store.updateState',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should remain false after call',
2020-10-06 20:28:38 +02:00
)
})
it('sets state after fetching on successful retry', async function () {
const clock = sandbox.useFakeTimers()
fetchSwapsFeatureLivenessStub.onCall(0).rejects(new Error('foo'))
fetchSwapsFeatureLivenessStub.onCall(1).rejects(new Error('foo'))
fetchSwapsFeatureLivenessStub.onCall(2).resolves(true)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
false,
'liveness should be false on boot',
2020-10-06 20:28:38 +02:00
)
swapsController._fetchAndSetSwapsLiveness()
await clock.runAllAsync()
assert.strictEqual(
2020-11-03 00:41:28 +01:00
fetchSwapsFeatureLivenessStub.callCount,
3,
2020-10-06 20:28:38 +02:00
'should have called fetch function three times',
)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
getLivenessState(),
true,
'liveness should be true after call',
2020-10-06 20:28:38 +02:00
)
})
})
})
describe('utils', function () {
describe('getMedian', function () {
const { getMedian } = utils
it('calculates median correctly with uneven sample', function () {
const values = [3, 2, 6].map((value) => new BigNumber(value))
const median = getMedian(values)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
median.toNumber(),
3,
'should have returned correct median',
)
})
it('calculates median correctly with even sample', function () {
const values = [3, 2, 2, 6].map((value) => new BigNumber(value))
const median = getMedian(values)
assert.strictEqual(
2020-11-03 00:41:28 +01:00
median.toNumber(),
2.5,
'should have returned correct median',
)
})
it('throws on empty or non-array sample', function () {
2020-11-03 00:41:28 +01:00
assert.throws(() => getMedian([]), 'should throw on empty array')
2020-11-03 00:41:28 +01:00
assert.throws(() => getMedian(), 'should throw on non-array param')
2020-11-03 00:41:28 +01:00
assert.throws(() => getMedian({}), 'should throw on non-array param')
})
})
})
2020-10-06 20:28:38 +02:00
})
2020-11-03 00:41:28 +01:00
function getMockQuotes() {
return {
[TEST_AGG_ID_1]: {
2020-11-03 00:41:28 +01:00
trade: {
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
value: '0x0',
gas: '0x61a80', // 4e5
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
},
2020-11-03 00:41:28 +01:00
sourceAmount: '10000000000000000000', // 10e18
destinationAmount: '20000000000000000000', // 20e18
error: null,
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
approvalNeeded: null,
maxGas: 600000,
averageGas: 120000,
estimatedRefund: 80000,
fetchTime: 607,
aggregator: TEST_AGG_ID_1,
aggType: 'AGG',
slippage: 2,
sourceTokenInfo: {
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
symbol: 'DAI',
decimals: 18,
iconUrl: 'https://foo.bar/logo.png',
},
2020-11-03 00:41:28 +01:00
destinationTokenInfo: {
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
decimals: 18,
},
},
[TEST_AGG_ID_BEST]: {
2020-11-03 00:41:28 +01:00
trade: {
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
value: '0x0',
gas: '0x61a80',
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
},
2020-11-03 00:41:28 +01:00
sourceAmount: '10000000000000000000',
destinationAmount: '25000000000000000000', // 25e18
error: null,
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
approvalNeeded: null,
maxGas: 1100000,
averageGas: 411000,
estimatedRefund: 343090,
fetchTime: 1003,
aggregator: TEST_AGG_ID_BEST,
aggType: 'AGG',
slippage: 2,
sourceTokenInfo: {
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
symbol: 'DAI',
decimals: 18,
iconUrl: 'https://foo.bar/logo.png',
},
2020-11-03 00:41:28 +01:00
destinationTokenInfo: {
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
decimals: 18,
},
},
[TEST_AGG_ID_2]: {
2020-11-03 00:41:28 +01:00
trade: {
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
value: '0x0',
gas: '0x61a80',
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
},
2020-11-03 00:41:28 +01:00
sourceAmount: '10000000000000000000',
destinationAmount: '22000000000000000000', // 22e18
error: null,
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
approvalNeeded: null,
maxGas: 368000,
averageGas: 197000,
estimatedRefund: 18205,
fetchTime: 1354,
aggregator: TEST_AGG_ID_2,
aggType: 'AGG',
slippage: 2,
sourceTokenInfo: {
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
symbol: 'DAI',
decimals: 18,
iconUrl: 'https://foo.bar/logo.png',
},
2020-11-03 00:41:28 +01:00
destinationTokenInfo: {
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
decimals: 18,
},
},
}
}