You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1379 lines
44 KiB
1379 lines
44 KiB
4 years ago
|
import assert from 'assert';
|
||
|
import sinon from 'sinon';
|
||
4 years ago
|
|
||
4 years ago
|
import { ethers } from 'ethers';
|
||
|
import { mapValues } from 'lodash';
|
||
|
import BigNumber from 'bignumber.js';
|
||
|
import { ObservableStore } from '@metamask/obs-store';
|
||
4 years ago
|
import {
|
||
|
ROPSTEN_NETWORK_ID,
|
||
|
MAINNET_NETWORK_ID,
|
||
4 years ago
|
MAINNET_CHAIN_ID,
|
||
4 years ago
|
} from '../../../shared/constants/network';
|
||
|
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
|
||
|
import { createTestProviderTools } from '../../../test/stub/provider';
|
||
|
import SwapsController, { utils } from './swaps';
|
||
|
import { NETWORK_EVENTS } from './network';
|
||
4 years ago
|
|
||
|
const MOCK_FETCH_PARAMS = {
|
||
|
slippage: 3,
|
||
|
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||
|
sourceDecimals: 18,
|
||
|
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
value: '1000000000000000000',
|
||
|
fromAddress: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
|
||
|
exchangeList: 'zeroExV1',
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
4 years ago
|
const TEST_AGG_ID_1 = 'TEST_AGG_1';
|
||
|
const TEST_AGG_ID_2 = 'TEST_AGG_2';
|
||
|
const TEST_AGG_ID_3 = 'TEST_AGG_3';
|
||
|
const TEST_AGG_ID_4 = 'TEST_AGG_4';
|
||
|
const TEST_AGG_ID_5 = 'TEST_AGG_5';
|
||
|
const TEST_AGG_ID_6 = 'TEST_AGG_6';
|
||
|
const TEST_AGG_ID_BEST = 'TEST_AGG_BEST';
|
||
|
const TEST_AGG_ID_APPROVAL = 'TEST_AGG_APPROVAL';
|
||
4 years ago
|
|
||
4 years ago
|
const MOCK_APPROVAL_NEEDED = {
|
||
4 years ago
|
data:
|
||
|
'0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
|
||
|
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||
|
amount: '0',
|
||
|
from: '0x2369267687A84ac7B494daE2f1542C40E37f4455',
|
||
|
gas: '12',
|
||
|
gasPrice: '34',
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
4 years ago
|
const MOCK_QUOTES_APPROVAL_REQUIRED = {
|
||
4 years ago
|
[TEST_AGG_ID_APPROVAL]: {
|
||
4 years ago
|
trade: {
|
||
|
data: '0x00',
|
||
|
from: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
|
||
|
value: '0x17647444f166000',
|
||
|
gas: '0xe09c0',
|
||
|
gasPrice: undefined,
|
||
4 years ago
|
to: '0x881d40237659c251811cec9c364ef91dc08d300c',
|
||
4 years ago
|
},
|
||
|
sourceAmount: '1000000000000000000000000000000000000',
|
||
|
destinationAmount: '396493201125465',
|
||
|
error: null,
|
||
|
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||
|
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
maxGas: 920000,
|
||
|
averageGas: 312510,
|
||
|
estimatedRefund: 343090,
|
||
|
fetchTime: 559,
|
||
4 years ago
|
aggregator: TEST_AGG_ID_APPROVAL,
|
||
4 years ago
|
aggType: 'AGG',
|
||
|
slippage: 3,
|
||
|
approvalNeeded: MOCK_APPROVAL_NEEDED,
|
||
4 years ago
|
fee: 1,
|
||
4 years ago
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
4 years ago
|
const MOCK_FETCH_METADATA = {
|
||
|
destinationTokenInfo: {
|
||
|
symbol: 'FOO',
|
||
|
decimals: 18,
|
||
|
},
|
||
4 years ago
|
chainId: MAINNET_CHAIN_ID,
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const MOCK_TOKEN_RATES_STORE = new ObservableStore({
|
||
4 years ago
|
contractExchangeRates: {
|
||
|
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2,
|
||
|
'0x1111111111111111111111111111111111111111': 0.1,
|
||
|
},
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
4 years ago
|
const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' });
|
||
4 years ago
|
|
||
|
const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({
|
||
|
gasLimit: 2000000,
|
||
|
simulationFails: undefined,
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
4 years ago
|
function getMockNetworkController() {
|
||
4 years ago
|
return {
|
||
|
store: {
|
||
|
getState: () => {
|
||
|
return {
|
||
|
network: ROPSTEN_NETWORK_ID,
|
||
4 years ago
|
};
|
||
4 years ago
|
},
|
||
|
},
|
||
4 years ago
|
on: sinon
|
||
|
.stub()
|
||
|
.withArgs(NETWORK_EVENTS.NETWORK_DID_CHANGE)
|
||
|
.callsArgAsync(1),
|
||
4 years ago
|
};
|
||
4 years ago
|
}
|
||
|
|
||
4 years ago
|
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: '',
|
||
4 years ago
|
swapsFeatureIsLive: true,
|
||
4 years ago
|
swapsQuoteRefreshTime: 60000,
|
||
4 years ago
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
4 years ago
|
const sandbox = sinon.createSandbox();
|
||
|
const fetchTradesInfoStub = sandbox.stub();
|
||
|
const fetchSwapsFeatureLivenessStub = sandbox.stub();
|
||
|
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
|
||
4 years ago
|
const getCurrentChainIdStub = sandbox.stub();
|
||
|
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
||
4 years ago
|
|
||
|
describe('SwapsController', function () {
|
||
4 years ago
|
let provider;
|
||
4 years ago
|
|
||
|
const getSwapsController = () => {
|
||
|
return new SwapsController({
|
||
|
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
|
||
4 years ago
|
networkController: getMockNetworkController(),
|
||
4 years ago
|
provider,
|
||
|
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||
|
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||
|
fetchTradesInfo: fetchTradesInfoStub,
|
||
|
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||
4 years ago
|
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||
4 years ago
|
getCurrentChainId: getCurrentChainIdStub,
|
||
4 years ago
|
});
|
||
|
};
|
||
4 years ago
|
|
||
|
before(function () {
|
||
|
const providerResultStub = {
|
||
|
// 1 gwei
|
||
|
eth_gasPrice: '0x0de0b6b3a7640000',
|
||
|
// by default, all accounts are external accounts (not contracts)
|
||
|
eth_getCode: '0x',
|
||
4 years ago
|
};
|
||
4 years ago
|
provider = createTestProviderTools({
|
||
|
scaffold: providerResultStub,
|
||
|
networkId: 1,
|
||
|
chainId: 1,
|
||
4 years ago
|
}).provider;
|
||
|
});
|
||
4 years ago
|
|
||
|
afterEach(function () {
|
||
4 years ago
|
sandbox.restore();
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('constructor', function () {
|
||
|
it('should setup correctly', function () {
|
||
4 years ago
|
const swapsController = getSwapsController();
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState(),
|
||
|
EMPTY_INIT_STATE,
|
||
|
);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.getBufferedGasLimit,
|
||
|
MOCK_GET_BUFFERED_GAS_LIMIT,
|
||
4 years ago
|
);
|
||
|
assert.strictEqual(swapsController.pollCount, 0);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.getProviderConfig,
|
||
|
MOCK_GET_PROVIDER_CONFIG,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should replace ethers instance when network changes', function () {
|
||
4 years ago
|
const networkController = getMockNetworkController();
|
||
4 years ago
|
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,
|
||
4 years ago
|
getCurrentChainId: getCurrentChainIdStub,
|
||
4 years ago
|
});
|
||
|
const currentEthersInstance = swapsController.ethersProvider;
|
||
|
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||
4 years ago
|
|
||
4 years ago
|
onNetworkDidChange(MAINNET_NETWORK_ID);
|
||
4 years ago
|
|
||
4 years ago
|
const newEthersInstance = swapsController.ethersProvider;
|
||
4 years ago
|
assert.notStrictEqual(
|
||
|
currentEthersInstance,
|
||
|
newEthersInstance,
|
||
|
'Ethers provider should be replaced',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should not replace ethers instance when network changes to loading', function () {
|
||
4 years ago
|
const networkController = getMockNetworkController();
|
||
4 years ago
|
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,
|
||
4 years ago
|
getCurrentChainId: getCurrentChainIdStub,
|
||
4 years ago
|
});
|
||
|
const currentEthersInstance = swapsController.ethersProvider;
|
||
|
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||
4 years ago
|
|
||
4 years ago
|
onNetworkDidChange('loading');
|
||
4 years ago
|
|
||
4 years ago
|
const newEthersInstance = swapsController.ethersProvider;
|
||
4 years ago
|
assert.strictEqual(
|
||
|
currentEthersInstance,
|
||
|
newEthersInstance,
|
||
|
'Ethers provider should not be replaced',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should not replace ethers instance when network changes to the same network', function () {
|
||
4 years ago
|
const networkController = getMockNetworkController();
|
||
4 years ago
|
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,
|
||
4 years ago
|
getCurrentChainId: getCurrentChainIdStub,
|
||
4 years ago
|
});
|
||
|
const currentEthersInstance = swapsController.ethersProvider;
|
||
|
const onNetworkDidChange = networkController.on.getCall(0).args[1];
|
||
4 years ago
|
|
||
4 years ago
|
onNetworkDidChange(ROPSTEN_NETWORK_ID);
|
||
4 years ago
|
|
||
4 years ago
|
const newEthersInstance = swapsController.ethersProvider;
|
||
4 years ago
|
assert.strictEqual(
|
||
|
currentEthersInstance,
|
||
|
newEthersInstance,
|
||
|
'Ethers provider should not be replaced',
|
||
4 years ago
|
);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('API', function () {
|
||
4 years ago
|
let swapsController;
|
||
4 years ago
|
beforeEach(function () {
|
||
4 years ago
|
swapsController = getSwapsController();
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('setters', function () {
|
||
|
it('should set selected quote agg id', function () {
|
||
4 years ago
|
const selectedAggId = 'test';
|
||
|
swapsController.setSelectedQuoteAggId(selectedAggId);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.selectedAggId,
|
||
|
selectedAggId,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set swaps tokens', function () {
|
||
4 years ago
|
const tokens = [];
|
||
|
swapsController.setSwapsTokens(tokens);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.tokens,
|
||
|
tokens,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set trade tx id', function () {
|
||
4 years ago
|
const tradeTxId = 'test';
|
||
|
swapsController.setTradeTxId(tradeTxId);
|
||
4 years ago
|
assert.strictEqual(
|
||
|
swapsController.store.getState().swapsState.tradeTxId,
|
||
|
tradeTxId,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set swaps tx gas price', function () {
|
||
4 years ago
|
const gasPrice = 1;
|
||
|
swapsController.setSwapsTxGasPrice(gasPrice);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customGasPrice,
|
||
|
gasPrice,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set swaps tx gas limit', function () {
|
||
4 years ago
|
const gasLimit = '1';
|
||
|
swapsController.setSwapsTxGasLimit(gasLimit);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customMaxGas,
|
||
|
gasLimit,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set background swap route state', function () {
|
||
4 years ago
|
const routeState = 'test';
|
||
|
swapsController.setBackgroundSwapRouteState(routeState);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.routeState,
|
||
|
routeState,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set swaps error key', function () {
|
||
4 years ago
|
const errorKey = 'test';
|
||
|
swapsController.setSwapsErrorKey(errorKey);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.errorKey,
|
||
|
errorKey,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set initial gas estimate', async function () {
|
||
4 years ago
|
const initialAggId = TEST_AGG_ID_1;
|
||
|
const baseGasEstimate = 10;
|
||
|
const { maxGas, estimatedRefund } = getMockQuotes()[TEST_AGG_ID_1];
|
||
4 years ago
|
|
||
4 years ago
|
const { swapsState } = swapsController.store.getState();
|
||
4 years ago
|
// Set mock quotes in order to have data for the test agg
|
||
|
swapsController.store.updateState({
|
||
4 years ago
|
swapsState: { ...swapsState, quotes: getMockQuotes() },
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
|
await swapsController.setInitialGasEstimate(
|
||
|
initialAggId,
|
||
|
baseGasEstimate,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
|
const {
|
||
|
gasLimit: bufferedGasLimit,
|
||
4 years ago
|
} = await swapsController.getBufferedGasLimit();
|
||
4 years ago
|
const {
|
||
|
gasEstimate,
|
||
|
gasEstimateWithRefund,
|
||
4 years ago
|
} = swapsController.store.getState().swapsState.quotes[initialAggId];
|
||
|
assert.strictEqual(gasEstimate, bufferedGasLimit);
|
||
4 years ago
|
assert.strictEqual(
|
||
|
gasEstimateWithRefund,
|
||
|
new BigNumber(maxGas, 10).minus(estimatedRefund, 10).toString(16),
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('should set custom approve tx data', function () {
|
||
4 years ago
|
const data = 'test';
|
||
|
swapsController.setCustomApproveTxData(data);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customApproveTxData,
|
||
|
data,
|
||
4 years ago
|
);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
4 years ago
|
describe('_findTopQuoteAndCalculateSavings', function () {
|
||
4 years ago
|
beforeEach(function () {
|
||
4 years ago
|
const { swapsState } = swapsController.store.getState();
|
||
4 years ago
|
swapsController.store.updateState({
|
||
|
swapsState: { ...swapsState, customGasPrice: '0x174876e800' },
|
||
4 years ago
|
});
|
||
|
});
|
||
4 years ago
|
|
||
4 years ago
|
it('returns empty object if passed undefined or empty object', async function () {
|
||
|
assert.deepStrictEqual(
|
||
|
await swapsController._findTopQuoteAndCalculateSavings(),
|
||
|
{},
|
||
4 years ago
|
);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
await swapsController._findTopQuoteAndCalculateSavings({}),
|
||
|
{},
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId and quotes with savings and fee values if passed necessary data and an even number of quotes', async function () {
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
|
] = await swapsController._findTopQuoteAndCalculateSavings(
|
||
|
getTopQuoteAndSavingsMockQuotes(),
|
||
4 years ago
|
);
|
||
|
assert.equal(topAggId, TEST_AGG_ID_1);
|
||
4 years ago
|
assert.deepStrictEqual(
|
||
|
resultQuotes,
|
||
|
getTopQuoteAndSavingsBaseExpectedResults(),
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId and quotes with savings and fee values if passed necessary data and an odd number of quotes', async function () {
|
||
4 years ago
|
const testInput = getTopQuoteAndSavingsMockQuotes();
|
||
|
delete testInput[TEST_AGG_ID_6];
|
||
|
const expectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
|
||
|
delete expectedResultQuotes[TEST_AGG_ID_6];
|
||
4 years ago
|
expectedResultQuotes[TEST_AGG_ID_1].savings = {
|
||
|
total: '0.0292',
|
||
|
performance: '0.0297',
|
||
|
fee: '0.02',
|
||
|
metaMaskFee: '0.0205',
|
||
|
medianMetaMaskFee: '0.0202',
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
4 years ago
|
] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
|
||
|
assert.equal(topAggId, TEST_AGG_ID_1);
|
||
|
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId, without best quote flagged, and quotes with fee values if passed necessary data but no custom convert rate exists', async function () {
|
||
|
const testInput = mapValues(
|
||
|
getTopQuoteAndSavingsMockQuotes(),
|
||
|
(quote) => ({
|
||
|
...quote,
|
||
|
destinationToken: '0xnoConversionRateExists',
|
||
|
}),
|
||
4 years ago
|
);
|
||
4 years ago
|
const expectedResultQuotes = {
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
...testInput[TEST_AGG_ID_1],
|
||
|
ethFee: '0.01',
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
...testInput[TEST_AGG_ID_2],
|
||
|
ethFee: '0.02',
|
||
|
},
|
||
|
[TEST_AGG_ID_3]: {
|
||
|
...testInput[TEST_AGG_ID_3],
|
||
|
ethFee: '0.03',
|
||
|
},
|
||
|
[TEST_AGG_ID_4]: {
|
||
|
...testInput[TEST_AGG_ID_4],
|
||
|
ethFee: '0.04',
|
||
|
},
|
||
|
[TEST_AGG_ID_5]: {
|
||
|
...testInput[TEST_AGG_ID_5],
|
||
|
ethFee: '0.05',
|
||
|
},
|
||
|
[TEST_AGG_ID_6]: {
|
||
|
...testInput[TEST_AGG_ID_6],
|
||
|
ethFee: '0.06',
|
||
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
4 years ago
|
] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
|
||
|
assert.equal(topAggId, TEST_AGG_ID_1);
|
||
|
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH', async function () {
|
||
|
const testInput = mapValues(
|
||
|
getTopQuoteAndSavingsMockQuotes(),
|
||
|
(quote) => ({
|
||
|
...quote,
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
}),
|
||
4 years ago
|
);
|
||
|
const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
|
||
4 years ago
|
const expectedResultQuotes = {
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_1],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '2.0195',
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_2],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9996',
|
||
|
},
|
||
|
[TEST_AGG_ID_3]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_3],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9698',
|
||
|
},
|
||
|
[TEST_AGG_ID_4]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_4],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.94',
|
||
|
},
|
||
|
[TEST_AGG_ID_5]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_5],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9102',
|
||
|
},
|
||
|
[TEST_AGG_ID_6]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_6],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.8705',
|
||
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
4 years ago
|
] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
|
||
|
assert.equal(topAggId, TEST_AGG_ID_1);
|
||
|
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is ETH and an ETH fee is included in the trade value of what would be the best quote', async function () {
|
||
|
const testInput = mapValues(
|
||
|
getTopQuoteAndSavingsMockQuotes(),
|
||
|
(quote) => ({
|
||
|
...quote,
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
}),
|
||
4 years ago
|
);
|
||
4 years ago
|
// 0.04 ETH fee included in trade value
|
||
4 years ago
|
testInput[TEST_AGG_ID_1].trade.value = '0x8b553ece48ec0000';
|
||
|
const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
|
||
4 years ago
|
const expectedResultQuotes = {
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_1],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8b553ece48ec0000' },
|
||
|
overallValueOfQuote: '1.9795',
|
||
|
ethFee: '0.05',
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_2],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9996',
|
||
|
isBestQuote: true,
|
||
|
savings: {
|
||
|
total: '0.0243',
|
||
|
performance: '0.0297',
|
||
|
fee: '0.015',
|
||
|
metaMaskFee: '0.0204',
|
||
|
medianMetaMaskFee: '0.0201',
|
||
|
},
|
||
|
},
|
||
|
[TEST_AGG_ID_3]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_3],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9698',
|
||
|
},
|
||
|
[TEST_AGG_ID_4]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_4],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.94',
|
||
|
},
|
||
|
[TEST_AGG_ID_5]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_5],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.9102',
|
||
|
},
|
||
|
[TEST_AGG_ID_6]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_6],
|
||
4 years ago
|
sourceToken: ETH_SWAPS_TOKEN_OBJECT.address,
|
||
4 years ago
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
trade: { value: '0x8ac7230489e80000' },
|
||
|
overallValueOfQuote: '1.8705',
|
||
|
},
|
||
4 years ago
|
};
|
||
|
delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote;
|
||
|
delete expectedResultQuotes[TEST_AGG_ID_1].savings;
|
||
4 years ago
|
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
4 years ago
|
] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
|
||
|
assert.equal(topAggId, TEST_AGG_ID_2);
|
||
|
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('returns the top aggId and quotes with savings and fee values if passed necessary data and the source token is not ETH and an ETH fee is included in the trade value of what would be the best quote', async function () {
|
||
4 years ago
|
const testInput = getTopQuoteAndSavingsMockQuotes();
|
||
4 years ago
|
// 0.04 ETH fee included in trade value
|
||
4 years ago
|
testInput[TEST_AGG_ID_1].trade.value = '0x8e1bc9bf040000';
|
||
|
const baseExpectedResultQuotes = getTopQuoteAndSavingsBaseExpectedResults();
|
||
4 years ago
|
const expectedResultQuotes = {
|
||
|
...baseExpectedResultQuotes,
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_1],
|
||
|
trade: { value: '0x8e1bc9bf040000' },
|
||
|
overallValueOfQuote: '1.9795',
|
||
|
ethFee: '0.05',
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
...baseExpectedResultQuotes[TEST_AGG_ID_2],
|
||
|
isBestQuote: true,
|
||
|
savings: {
|
||
|
total: '0.0243',
|
||
|
performance: '0.0297',
|
||
|
fee: '0.015',
|
||
|
metaMaskFee: '0.0204',
|
||
|
medianMetaMaskFee: '0.0201',
|
||
|
},
|
||
|
},
|
||
4 years ago
|
};
|
||
|
delete expectedResultQuotes[TEST_AGG_ID_1].isBestQuote;
|
||
|
delete expectedResultQuotes[TEST_AGG_ID_1].savings;
|
||
4 years ago
|
|
||
|
const [
|
||
|
topAggId,
|
||
|
resultQuotes,
|
||
4 years ago
|
] = await swapsController._findTopQuoteAndCalculateSavings(testInput);
|
||
|
assert.equal(topAggId, [TEST_AGG_ID_2]);
|
||
|
assert.deepStrictEqual(resultQuotes, expectedResultQuotes);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
4 years ago
|
describe('fetchAndSetQuotes', function () {
|
||
|
it('returns null if fetchParams is not provided', async function () {
|
||
4 years ago
|
const quotes = await swapsController.fetchAndSetQuotes(undefined);
|
||
|
assert.strictEqual(quotes, null);
|
||
|
});
|
||
4 years ago
|
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||
4 years ago
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(1));
|
||
4 years ago
|
|
||
|
const [newQuotes] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.deepStrictEqual(newQuotes[TEST_AGG_ID_BEST], {
|
||
|
...getMockQuotes()[TEST_AGG_ID_BEST],
|
||
4 years ago
|
sourceTokenInfo: undefined,
|
||
|
destinationTokenInfo: {
|
||
|
symbol: 'FOO',
|
||
|
decimals: 18,
|
||
|
},
|
||
|
isBestQuote: true,
|
||
|
// TODO: find a way to calculate these values dynamically
|
||
|
gasEstimate: 2000000,
|
||
4 years ago
|
gasEstimateWithRefund: 'b8cae',
|
||
|
savings: {
|
||
|
fee: '0',
|
||
4 years ago
|
metaMaskFee: '0.5050505050505050505',
|
||
4 years ago
|
performance: '6',
|
||
4 years ago
|
total: '5.4949494949494949495',
|
||
|
medianMetaMaskFee: '0.44444444444444444444',
|
||
4 years ago
|
},
|
||
4 years ago
|
ethFee: '33554432',
|
||
|
overallValueOfQuote: '-33554382',
|
||
|
metaMaskFeeInEth: '0.5050505050505050505',
|
||
|
ethValueOfTokens: '50',
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
|
assert.strictEqual(
|
||
4 years ago
|
fetchTradesInfoStub.calledOnceWithExactly(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
),
|
||
4 years ago
|
true,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
it('performs the allowance check', async function () {
|
||
4 years ago
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Make it so approval is not required
|
||
|
const allowanceStub = sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(1));
|
||
4 years ago
|
|
||
|
await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
|
assert.strictEqual(
|
||
|
allowanceStub.calledOnceWithExactly(
|
||
|
MOCK_FETCH_PARAMS.sourceToken,
|
||
|
MOCK_FETCH_PARAMS.fromAddress,
|
||
4 years ago
|
MAINNET_CHAIN_ID,
|
||
4 years ago
|
),
|
||
|
true,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('gets the gas limit if approval is required', async function () {
|
||
4 years ago
|
fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED);
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Ensure approval is required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(0));
|
||
4 years ago
|
|
||
4 years ago
|
const timedoutGasReturnResult = { gasLimit: 1000000 };
|
||
4 years ago
|
const timedoutGasReturnStub = sandbox
|
||
|
.stub(swapsController, 'timedoutGasReturn')
|
||
4 years ago
|
.resolves(timedoutGasReturnResult);
|
||
4 years ago
|
|
||
|
await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
|
// Mocked quotes approvalNeeded is null, so it will only be called with the gas
|
||
|
assert.strictEqual(
|
||
4 years ago
|
timedoutGasReturnStub.calledOnceWithExactly(MOCK_APPROVAL_NEEDED),
|
||
4 years ago
|
true,
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('marks the best quote', async function () {
|
||
4 years ago
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(1));
|
||
4 years ago
|
|
||
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.strictEqual(topAggId, TEST_AGG_ID_BEST);
|
||
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, true);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('selects the best quote', async function () {
|
||
4 years ago
|
const bestAggId = 'bestAggId';
|
||
4 years ago
|
|
||
|
// Clone the existing mock quote and increase destination amount
|
||
|
const bestQuote = {
|
||
4 years ago
|
...getMockQuotes()[TEST_AGG_ID_1],
|
||
4 years ago
|
aggregator: bestAggId,
|
||
|
destinationAmount: ethers.BigNumber.from(
|
||
4 years ago
|
getMockQuotes()[TEST_AGG_ID_1].destinationAmount,
|
||
4 years ago
|
)
|
||
4 years ago
|
.add((100e18).toString())
|
||
4 years ago
|
.toString(),
|
||
4 years ago
|
};
|
||
|
const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote };
|
||
|
fetchTradesInfoStub.resolves(quotes);
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(1));
|
||
4 years ago
|
|
||
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.strictEqual(topAggId, bestAggId);
|
||
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, true);
|
||
|
});
|
||
4 years ago
|
|
||
4 years ago
|
it('does not mark as best quote if no conversion rate exists for destination token', async function () {
|
||
4 years ago
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||
|
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
||
4 years ago
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
4 years ago
|
.resolves(ethers.BigNumber.from(1));
|
||
4 years ago
|
|
||
|
swapsController.tokenRatesStore.updateState({
|
||
|
contractExchangeRates: {},
|
||
4 years ago
|
});
|
||
4 years ago
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, undefined);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('resetSwapsState', function () {
|
||
|
it('resets the swaps state correctly', function () {
|
||
4 years ago
|
const { swapsState: old } = swapsController.store.getState();
|
||
|
swapsController.resetSwapsState();
|
||
|
const { swapsState } = swapsController.store.getState();
|
||
4 years ago
|
assert.deepStrictEqual(swapsState, {
|
||
|
...EMPTY_INIT_STATE.swapsState,
|
||
|
tokens: old.tokens,
|
||
4 years ago
|
swapsQuoteRefreshTime: old.swapsQuoteRefreshTime,
|
||
4 years ago
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
4 years ago
|
);
|
||
|
swapsController.resetSwapsState();
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('stopPollingForQuotes', function () {
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
4 years ago
|
);
|
||
|
swapsController.stopPollingForQuotes();
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('resets quotes state correctly', function () {
|
||
4 years ago
|
swapsController.stopPollingForQuotes();
|
||
|
const { swapsState } = swapsController.store.getState();
|
||
|
assert.deepStrictEqual(swapsState.quotes, {});
|
||
|
assert.strictEqual(swapsState.quotesLastFetched, null);
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('resetPostFetchState', function () {
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
4 years ago
|
);
|
||
|
swapsController.resetPostFetchState();
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('updates state correctly', function () {
|
||
4 years ago
|
const tokens = 'test';
|
||
|
const fetchParams = 'test';
|
||
|
const swapsFeatureIsLive = false;
|
||
|
const swapsQuoteRefreshTime = 0;
|
||
4 years ago
|
swapsController.store.updateState({
|
||
4 years ago
|
swapsState: {
|
||
|
tokens,
|
||
|
fetchParams,
|
||
|
swapsFeatureIsLive,
|
||
|
swapsQuoteRefreshTime,
|
||
|
},
|
||
4 years ago
|
});
|
||
4 years ago
|
|
||
4 years ago
|
swapsController.resetPostFetchState();
|
||
4 years ago
|
|
||
4 years ago
|
const { swapsState } = swapsController.store.getState();
|
||
4 years ago
|
assert.deepStrictEqual(swapsState, {
|
||
|
...EMPTY_INIT_STATE.swapsState,
|
||
|
tokens,
|
||
|
fetchParams,
|
||
|
swapsFeatureIsLive,
|
||
4 years ago
|
swapsQuoteRefreshTime,
|
||
4 years ago
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
|
describe('utils', function () {
|
||
4 years ago
|
describe('getMedianEthValueQuote', function () {
|
||
4 years ago
|
const { getMedianEthValueQuote } = utils;
|
||
4 years ago
|
|
||
|
it('calculates median correctly with uneven sample', function () {
|
||
4 years ago
|
const expectedResult = {
|
||
|
ethFee: '10',
|
||
|
metaMaskFeeInEth: '5',
|
||
|
ethValueOfTokens: '0.3',
|
||
4 years ago
|
};
|
||
4 years ago
|
const values = [
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethFee: '10',
|
||
|
metaMaskFeeInEth: '5',
|
||
|
ethValueOfTokens: '0.3',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '2',
|
||
|
ethFee: '20',
|
||
|
metaMaskFeeInEth: '3',
|
||
|
ethValueOfTokens: '0.2',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '6',
|
||
|
ethFee: '40',
|
||
|
metaMaskFeeInEth: '6',
|
||
|
ethValueOfTokens: '0.6',
|
||
|
},
|
||
4 years ago
|
];
|
||
4 years ago
|
|
||
4 years ago
|
const median = getMedianEthValueQuote(values);
|
||
4 years ago
|
|
||
|
assert.deepEqual(
|
||
|
median,
|
||
|
expectedResult,
|
||
|
'should have returned correct median quote object',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('calculates median correctly with even sample', function () {
|
||
4 years ago
|
const expectedResult = {
|
||
|
ethFee: '20',
|
||
|
metaMaskFeeInEth: '6.5',
|
||
|
ethValueOfTokens: '0.25',
|
||
4 years ago
|
};
|
||
4 years ago
|
const values = [
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethFee: '10',
|
||
|
metaMaskFeeInEth: '5',
|
||
|
ethValueOfTokens: '0.3',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '1',
|
||
|
ethFee: '20',
|
||
|
metaMaskFeeInEth: '3',
|
||
|
ethValueOfTokens: '0.2',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '2',
|
||
|
ethFee: '30',
|
||
|
metaMaskFeeInEth: '8',
|
||
|
ethValueOfTokens: '0.2',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '6',
|
||
|
ethFee: '40',
|
||
|
metaMaskFeeInEth: '6',
|
||
|
ethValueOfTokens: '0.6',
|
||
|
},
|
||
4 years ago
|
];
|
||
|
const median = getMedianEthValueQuote(values);
|
||
4 years ago
|
|
||
4 years ago
|
assert.deepEqual(
|
||
|
median,
|
||
|
expectedResult,
|
||
|
'should have returned correct median quote object',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('calculates median correctly with an uneven sample where multiple quotes have the median overall value', function () {
|
||
|
const expectedResult = {
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.5',
|
||
|
ethValueOfTokens: '5',
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const values = [
|
||
|
{
|
||
|
overallValueOfQuote: '1',
|
||
|
ethValueOfTokens: '2',
|
||
|
ethFee: '1',
|
||
|
metaMaskFeeInEth: '0.2',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethValueOfTokens: '4',
|
||
|
ethFee: '1',
|
||
|
metaMaskFeeInEth: '0.4',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethValueOfTokens: '5',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.5',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethValueOfTokens: '6',
|
||
|
ethFee: '3',
|
||
|
metaMaskFeeInEth: '0.6',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '4',
|
||
|
ethValueOfTokens: '6',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.6',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '4',
|
||
|
ethValueOfTokens: '7',
|
||
|
ethFee: '3',
|
||
|
metaMaskFeeInEth: '0.7',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '6',
|
||
|
ethValueOfTokens: '8',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.8',
|
||
|
},
|
||
4 years ago
|
];
|
||
|
const median = getMedianEthValueQuote(values);
|
||
4 years ago
|
|
||
|
assert.deepEqual(
|
||
|
median,
|
||
|
expectedResult,
|
||
|
'should have returned correct median quote object',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('calculates median correctly with an even sample where multiple quotes have the same overall value as either of the two middle values', function () {
|
||
|
const expectedResult = {
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.55',
|
||
|
ethValueOfTokens: '5.5',
|
||
4 years ago
|
};
|
||
4 years ago
|
|
||
|
const values = [
|
||
|
{
|
||
|
overallValueOfQuote: '1',
|
||
|
ethValueOfTokens: '2',
|
||
|
ethFee: '1',
|
||
|
metaMaskFeeInEth: '0.2',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethValueOfTokens: '4',
|
||
|
ethFee: '1',
|
||
|
metaMaskFeeInEth: '0.4',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '3',
|
||
|
ethValueOfTokens: '5',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.5',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '4',
|
||
|
ethValueOfTokens: '6',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.6',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '4',
|
||
|
ethValueOfTokens: '7',
|
||
|
ethFee: '3',
|
||
|
metaMaskFeeInEth: '0.7',
|
||
|
},
|
||
|
{
|
||
|
overallValueOfQuote: '6',
|
||
|
ethValueOfTokens: '8',
|
||
|
ethFee: '2',
|
||
|
metaMaskFeeInEth: '0.8',
|
||
|
},
|
||
4 years ago
|
];
|
||
|
const median = getMedianEthValueQuote(values);
|
||
4 years ago
|
|
||
|
assert.deepEqual(
|
||
|
median,
|
||
|
expectedResult,
|
||
|
'should have returned correct median quote object',
|
||
4 years ago
|
);
|
||
|
});
|
||
4 years ago
|
|
||
|
it('throws on empty or non-array sample', function () {
|
||
4 years ago
|
assert.throws(
|
||
|
() => getMedianEthValueQuote([]),
|
||
|
'should throw on empty array',
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.throws(
|
||
|
() => getMedianEthValueQuote(),
|
||
|
'should throw on non-array param',
|
||
4 years ago
|
);
|
||
4 years ago
|
|
||
4 years ago
|
assert.throws(
|
||
|
() => getMedianEthValueQuote({}),
|
||
|
'should throw on non-array param',
|
||
4 years ago
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
4 years ago
|
|
||
4 years ago
|
function getMockQuotes() {
|
||
4 years ago
|
return {
|
||
|
[TEST_AGG_ID_1]: {
|
||
4 years ago
|
trade: {
|
||
|
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
|
||
|
value: '0x0',
|
||
|
gas: '0x61a80', // 4e5
|
||
|
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
|
||
4 years ago
|
},
|
||
4 years ago
|
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',
|
||
4 years ago
|
},
|
||
4 years ago
|
destinationTokenInfo: {
|
||
|
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
symbol: 'USDC',
|
||
|
decimals: 18,
|
||
4 years ago
|
},
|
||
4 years ago
|
fee: 1,
|
||
4 years ago
|
},
|
||
|
|
||
|
[TEST_AGG_ID_BEST]: {
|
||
4 years ago
|
trade: {
|
||
|
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
|
||
|
value: '0x0',
|
||
|
gas: '0x61a80',
|
||
|
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
|
||
4 years ago
|
},
|
||
4 years ago
|
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',
|
||
4 years ago
|
},
|
||
4 years ago
|
destinationTokenInfo: {
|
||
|
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
symbol: 'USDC',
|
||
|
decimals: 18,
|
||
4 years ago
|
},
|
||
4 years ago
|
fee: 1,
|
||
4 years ago
|
},
|
||
|
|
||
|
[TEST_AGG_ID_2]: {
|
||
4 years ago
|
trade: {
|
||
|
from: '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc',
|
||
|
value: '0x0',
|
||
|
gas: '0x61a80',
|
||
|
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
|
||
4 years ago
|
},
|
||
4 years ago
|
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',
|
||
4 years ago
|
},
|
||
4 years ago
|
destinationTokenInfo: {
|
||
|
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
symbol: 'USDC',
|
||
|
decimals: 18,
|
||
4 years ago
|
},
|
||
4 years ago
|
fee: 1,
|
||
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
}
|
||
|
|
||
|
function getTopQuoteAndSavingsMockQuotes() {
|
||
|
// These destination amounts are calculated using the following "pre-fee" amounts
|
||
|
// TEST_AGG_ID_1: 20.5
|
||
|
// TEST_AGG_ID_2: 20.4
|
||
|
// TEST_AGG_ID_3: 20.2
|
||
|
// TEST_AGG_ID_4: 20
|
||
|
// TEST_AGG_ID_5: 19.8
|
||
|
// TEST_AGG_ID_6: 19.5
|
||
|
|
||
|
return {
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
aggregator: TEST_AGG_ID_1,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x186a0',
|
||
|
destinationAmount: '20295000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
aggregator: TEST_AGG_ID_2,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x30d40',
|
||
|
destinationAmount: '20196000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
|
[TEST_AGG_ID_3]: {
|
||
|
aggregator: TEST_AGG_ID_3,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x493e0',
|
||
|
destinationAmount: '19998000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
|
[TEST_AGG_ID_4]: {
|
||
|
aggregator: TEST_AGG_ID_4,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x61a80',
|
||
|
destinationAmount: '19800000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
|
[TEST_AGG_ID_5]: {
|
||
|
aggregator: TEST_AGG_ID_5,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x7a120',
|
||
|
destinationAmount: '19602000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
|
[TEST_AGG_ID_6]: {
|
||
|
aggregator: TEST_AGG_ID_6,
|
||
|
approvalNeeded: null,
|
||
|
gasEstimate: '0x927c0',
|
||
|
destinationAmount: '19305000000000000000',
|
||
|
destinationToken: '0x1111111111111111111111111111111111111111',
|
||
|
destinationTokenInfo: { decimals: 18 },
|
||
|
sourceAmount: '10000000000000000000',
|
||
|
sourceToken: '0xsomeERC20TokenAddress',
|
||
|
trade: {
|
||
|
value: '0x0',
|
||
|
},
|
||
|
fee: 1,
|
||
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
}
|
||
|
|
||
|
function getTopQuoteAndSavingsBaseExpectedResults() {
|
||
4 years ago
|
const baseTestInput = getTopQuoteAndSavingsMockQuotes();
|
||
4 years ago
|
return {
|
||
|
[TEST_AGG_ID_1]: {
|
||
|
...baseTestInput[TEST_AGG_ID_1],
|
||
|
isBestQuote: true,
|
||
|
ethFee: '0.01',
|
||
|
overallValueOfQuote: '2.0195',
|
||
|
metaMaskFeeInEth: '0.0205',
|
||
|
ethValueOfTokens: '2.0295',
|
||
|
savings: {
|
||
|
total: '0.0441',
|
||
|
performance: '0.0396',
|
||
|
fee: '0.025',
|
||
|
metaMaskFee: '0.0205',
|
||
|
medianMetaMaskFee: '0.0201',
|
||
|
},
|
||
|
},
|
||
|
[TEST_AGG_ID_2]: {
|
||
|
...baseTestInput[TEST_AGG_ID_2],
|
||
|
ethFee: '0.02',
|
||
|
overallValueOfQuote: '1.9996',
|
||
|
metaMaskFeeInEth: '0.0204',
|
||
|
ethValueOfTokens: '2.0196',
|
||
|
},
|
||
|
[TEST_AGG_ID_3]: {
|
||
|
...baseTestInput[TEST_AGG_ID_3],
|
||
|
ethFee: '0.03',
|
||
|
overallValueOfQuote: '1.9698',
|
||
|
metaMaskFeeInEth: '0.0202',
|
||
|
ethValueOfTokens: '1.9998',
|
||
|
},
|
||
|
[TEST_AGG_ID_4]: {
|
||
|
...baseTestInput[TEST_AGG_ID_4],
|
||
|
ethFee: '0.04',
|
||
|
overallValueOfQuote: '1.94',
|
||
|
metaMaskFeeInEth: '0.02',
|
||
|
ethValueOfTokens: '1.98',
|
||
|
},
|
||
|
[TEST_AGG_ID_5]: {
|
||
|
...baseTestInput[TEST_AGG_ID_5],
|
||
|
ethFee: '0.05',
|
||
|
overallValueOfQuote: '1.9102',
|
||
|
metaMaskFeeInEth: '0.0198',
|
||
|
ethValueOfTokens: '1.9602',
|
||
|
},
|
||
|
[TEST_AGG_ID_6]: {
|
||
|
...baseTestInput[TEST_AGG_ID_6],
|
||
|
ethFee: '0.06',
|
||
|
overallValueOfQuote: '1.8705',
|
||
|
metaMaskFeeInEth: '0.0195',
|
||
|
ethValueOfTokens: '1.9305',
|
||
4 years ago
|
},
|
||
4 years ago
|
};
|
||
4 years ago
|
}
|
||
4 years ago
|
|
||
|
function getMockQuoteRefreshTime() {
|
||
4 years ago
|
return 45000;
|
||
4 years ago
|
}
|