1554 lines
67 KiB
TypeScript
1554 lines
67 KiB
TypeScript
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
|
import { OtcOrder } from '@0x/protocol-utils';
|
|
import { Signature, SignatureType } from '@0x/protocol-utils/lib/src/signature_utils';
|
|
import { TokenMetadata } from '@0x/token-metadata';
|
|
import { MarketOperation } from '@0x/types';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { AxiosInstance } from 'axios';
|
|
|
|
import { Integrator } from '../../src/config';
|
|
import { DEFAULT_MIN_EXPIRY_DURATION_MS, NULL_ADDRESS, ONE_SECOND_MS } from '../../src/core/constants';
|
|
import { RfqMaker } from '../../src/entities';
|
|
import { QuoteRequestor } from '../../src/quoteRequestor/QuoteRequestor';
|
|
import { FeeService } from '../../src/services/fee_service';
|
|
import { RfqtService } from '../../src/services/RfqtService';
|
|
import { RfqMakerBalanceCacheService } from '../../src/services/rfq_maker_balance_cache_service';
|
|
import { FirmQuoteContext, QuoteContext, QuoteContextVolumeRequired } from '../../src/services/types';
|
|
import { FeeWithDetails, IndicativeQuote, RfqtV2Price } from '../../src/core/types';
|
|
import { CacheClient } from '../../src/utils/cache_client';
|
|
import { ConfigManager } from '../../src/utils/config_manager';
|
|
import { GasStationAttendant } from '../../src/utils/GasStationAttendant';
|
|
import { QuoteServerClient } from '../../src/utils/quote_server_client';
|
|
import { RfqBalanceCheckUtils, RfqBlockchainUtils } from '../../src/utils/rfq_blockchain_utils';
|
|
import { RfqMakerDbUtils } from '../../src/utils/rfq_maker_db_utils';
|
|
import { RfqMakerAssetOfferings, RfqMakerManager } from '../../src/utils/rfq_maker_manager';
|
|
import { TokenMetadataManager } from '../../src/utils/TokenMetadataManager';
|
|
import { TokenPriceOracle } from '../../src/utils/TokenPriceOracle';
|
|
import { ZeroExApiClient } from '../../src/utils/ZeroExApiClient';
|
|
import { RfqDynamicBlacklist } from '../../src/utils/rfq_dynamic_blacklist';
|
|
|
|
jest.mock('../../src/utils/rfq_maker_manager', () => ({
|
|
RfqMakerManager: jest.fn().mockImplementation(() => {
|
|
return {
|
|
getRfqtV2MakersForPair: jest.fn(),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
jest.mock('../../src/quoteRequestor/QuoteRequestor', () => ({
|
|
QuoteRequestor: jest.fn().mockImplementation(() => {
|
|
return {
|
|
requestRfqtIndicativeQuotesAsync: jest.fn().mockResolvedValue([]),
|
|
requestRfqtFirmQuotesAsync: jest.fn().mockResolvedValue([]),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
jest.mock('../../src/utils/quote_server_client', () => ({
|
|
QuoteServerClient: jest.fn().mockImplementation(() => {
|
|
return {
|
|
batchGetPriceV2Async: jest.fn().mockResolvedValue([]),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
jest.mock('../../src/services/rfq_maker_balance_cache_service', () => ({
|
|
RfqMakerBalanceCacheService: jest.fn().mockImplementation(() => {
|
|
return {
|
|
getERC20OwnerBalancesAsync: jest.fn().mockResolvedValue([]),
|
|
};
|
|
}),
|
|
}));
|
|
|
|
// TODO (rhinodavid): Find a better way to initialize mocked classes
|
|
const mockQuoteRequestor = jest.mocked(new QuoteRequestor({} as RfqMakerAssetOfferings, {} as AxiosInstance));
|
|
const mockRfqMakerManager = jest.mocked(new RfqMakerManager({} as ConfigManager, {} as RfqMakerDbUtils, 0));
|
|
const mockQuoteServerClient = jest.mocked(new QuoteServerClient({} as AxiosInstance));
|
|
const mockFeeService = jest.mocked(
|
|
new FeeService(
|
|
1337,
|
|
{} as TokenMetadata,
|
|
{} as ConfigManager,
|
|
{} as GasStationAttendant,
|
|
{} as TokenPriceOracle,
|
|
{} as ZeroExApiClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
),
|
|
);
|
|
const mockRfqBlockchainUtils = jest.mocked({} as RfqBlockchainUtils);
|
|
const mockTokenMetadataManager = jest.mocked(new TokenMetadataManager(1337, {} as RfqBlockchainUtils));
|
|
const mockContractAddresses = getContractAddressesForChainOrThrow(1337);
|
|
const mockRfqMakerBalanceCacheService = jest.mocked(
|
|
new RfqMakerBalanceCacheService({} as CacheClient, {} as RfqBalanceCheckUtils),
|
|
);
|
|
const mockCacheClient = jest.mocked({} as CacheClient);
|
|
const mockTokenPriceOracle = jest.mocked({} as TokenPriceOracle);
|
|
const mockRfqDynamicBlacklist = jest.mocked(new Set() as RfqDynamicBlacklist);
|
|
|
|
describe('Rfqt Service', () => {
|
|
beforeEach(() => {
|
|
mockQuoteRequestor.requestRfqtFirmQuotesAsync.mockClear();
|
|
mockQuoteRequestor.requestRfqtIndicativeQuotesAsync.mockClear();
|
|
mockQuoteServerClient.batchGetPriceV2Async.mockClear();
|
|
mockRfqMakerManager.getRfqtV2MakersForPair.mockClear();
|
|
mockRfqMakerBalanceCacheService.getERC20OwnerBalancesAsync.mockClear();
|
|
});
|
|
describe('v1', () => {
|
|
describe('getV1PricesAsync', () => {
|
|
it('passes through calls to QuoteRequestor::requestRfqtIndicativeQuotesAsync', async () => {
|
|
const rfqtService = new RfqtService(
|
|
0,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
await rfqtService.getV1PricesAsync({
|
|
altRfqAssetOfferings: {
|
|
'alt-mm': [
|
|
{
|
|
id: 'id',
|
|
baseAsset: '0xbaseasset',
|
|
quoteAsset: '0xquoteasset',
|
|
baseAssetDecimals: 420,
|
|
quoteAssetDecimals: 69,
|
|
},
|
|
],
|
|
},
|
|
assetFillAmount: new BigNumber(111),
|
|
comparisonPrice: new BigNumber(666),
|
|
makerToken: '0xmakertoken',
|
|
marketOperation: MarketOperation.Buy,
|
|
takerAddress: '0xtakeraddress',
|
|
takerToken: '0xtakertoken',
|
|
intentOnFilling: false,
|
|
integrator: {
|
|
apiKeys: [],
|
|
allowedChainIds: [],
|
|
integratorId: 'uuid-integrator',
|
|
rfqm: false,
|
|
label: 'Scam Integrator 1',
|
|
},
|
|
txOrigin: '0xtxorigin',
|
|
});
|
|
|
|
const args = mockQuoteRequestor.requestRfqtIndicativeQuotesAsync.mock.calls[0];
|
|
expect(args).toMatchInlineSnapshot(`
|
|
[
|
|
"0xmakertoken",
|
|
"0xtakertoken",
|
|
"111",
|
|
"Buy",
|
|
"666",
|
|
{
|
|
"altRfqAssetOfferings": {
|
|
"alt-mm": [
|
|
{
|
|
"baseAsset": "0xbaseasset",
|
|
"baseAssetDecimals": 420,
|
|
"id": "id",
|
|
"quoteAsset": "0xquoteasset",
|
|
"quoteAssetDecimals": 69,
|
|
},
|
|
],
|
|
},
|
|
"integrator": {
|
|
"allowedChainIds": [],
|
|
"apiKeys": [],
|
|
"integratorId": "uuid-integrator",
|
|
"label": "Scam Integrator 1",
|
|
"rfqm": false,
|
|
},
|
|
"intentOnFilling": false,
|
|
"isIndicative": true,
|
|
"isLastLook": false,
|
|
"makerEndpointMaxResponseTimeMs": 600,
|
|
"takerAddress": "0xtakeraddress",
|
|
"txOrigin": "0xtxorigin",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
describe('getV1QuotesAsync', () => {
|
|
it('passes through calls to QuoteRequestor::requestRfqtFirmQuotesAsync', async () => {
|
|
mockQuoteRequestor.requestRfqtFirmQuotesAsync.mockResolvedValue([]);
|
|
const rfqtService = new RfqtService(
|
|
0,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
await rfqtService.getV1QuotesAsync({
|
|
altRfqAssetOfferings: {
|
|
'alt-mm': [
|
|
{
|
|
id: 'id',
|
|
baseAsset: '0xbaseasset',
|
|
quoteAsset: '0xquoteasset',
|
|
baseAssetDecimals: 420,
|
|
quoteAssetDecimals: 69,
|
|
},
|
|
],
|
|
},
|
|
assetFillAmount: new BigNumber(111),
|
|
comparisonPrice: new BigNumber(666),
|
|
makerToken: '0xmakertoken',
|
|
marketOperation: MarketOperation.Buy,
|
|
takerAddress: '0xtakeraddress',
|
|
takerToken: '0xtakertoken',
|
|
intentOnFilling: false,
|
|
integrator: {
|
|
allowedChainIds: [],
|
|
apiKeys: [],
|
|
integratorId: 'uuid-integrator',
|
|
rfqm: false,
|
|
label: 'Scam Integrator 1',
|
|
},
|
|
txOrigin: '0xtxorigin',
|
|
});
|
|
|
|
const args = mockQuoteRequestor.requestRfqtFirmQuotesAsync.mock.calls[0];
|
|
expect(args).toMatchInlineSnapshot(`
|
|
[
|
|
"0xmakertoken",
|
|
"0xtakertoken",
|
|
"111",
|
|
"Buy",
|
|
"666",
|
|
{
|
|
"altRfqAssetOfferings": {
|
|
"alt-mm": [
|
|
{
|
|
"baseAsset": "0xbaseasset",
|
|
"baseAssetDecimals": 420,
|
|
"id": "id",
|
|
"quoteAsset": "0xquoteasset",
|
|
"quoteAssetDecimals": 69,
|
|
},
|
|
],
|
|
},
|
|
"integrator": {
|
|
"allowedChainIds": [],
|
|
"apiKeys": [],
|
|
"integratorId": "uuid-integrator",
|
|
"label": "Scam Integrator 1",
|
|
"rfqm": false,
|
|
},
|
|
"intentOnFilling": false,
|
|
"isIndicative": false,
|
|
"isLastLook": false,
|
|
"makerEndpointMaxResponseTimeMs": 600,
|
|
"takerAddress": "0xtakeraddress",
|
|
"txOrigin": "0xtxorigin",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('v2', () => {
|
|
const maker = new RfqMaker({
|
|
makerId: 'maker-id',
|
|
chainId: 1337,
|
|
updatedAt: new Date(),
|
|
pairs: [['0x1', '0x2']],
|
|
rfqmUri: null,
|
|
rfqtUri: 'maker.uri',
|
|
});
|
|
const maker2 = new RfqMaker({
|
|
makerId: 'maker2-id',
|
|
chainId: 1337,
|
|
updatedAt: new Date(),
|
|
pairs: [['0x1', '0x2']],
|
|
rfqmUri: null,
|
|
rfqtUri: 'maker2.uri',
|
|
});
|
|
const altonomy = new RfqMaker({
|
|
makerId: 'fc8468a7-8bc3-4df0-abce-2bbd04c24cb0',
|
|
chainId: 1337,
|
|
updatedAt: new Date(),
|
|
pairs: [['0x1', '0x2']],
|
|
rfqmUri: null,
|
|
rfqtUri: 'altonomy.uri',
|
|
});
|
|
const integrator: Integrator = {
|
|
allowedChainIds: [1337],
|
|
apiKeys: [],
|
|
integratorId: 'integrator-id',
|
|
label: 'test integrator',
|
|
rfqm: false,
|
|
};
|
|
describe('getV2PricesAsync', () => {
|
|
it('transforms the API request into a quote server client request for buys', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0x1',
|
|
isSelling: false,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
await rfqtService.getV2PricesAsync(quoteContext);
|
|
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls[0]).toMatchInlineSnapshot(`
|
|
[
|
|
[
|
|
"maker.uri",
|
|
],
|
|
{
|
|
"allowedChainIds": [
|
|
1337,
|
|
],
|
|
"apiKeys": [],
|
|
"integratorId": "integrator-id",
|
|
"label": "test integrator",
|
|
"rfqm": false,
|
|
},
|
|
{
|
|
"buyAmountBaseUnits": "1000",
|
|
"buyTokenAddress": "0x1",
|
|
"chainId": "1337",
|
|
"feeAmount": "100",
|
|
"feeToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
|
"integratorId": "integrator-id",
|
|
"protocolVersion": "4",
|
|
"sellTokenAddress": "0x2",
|
|
"takerAddress": "0x0",
|
|
"txOrigin": "0xtakeraddress",
|
|
"workflow": "rfqt",
|
|
},
|
|
[Function],
|
|
"rfqt",
|
|
]
|
|
`);
|
|
});
|
|
it('[workflow: gasless-rfqt] transforms the API request into a quote server client request for buys', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'gasless-rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0x1',
|
|
isSelling: false,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([altonomy]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
await rfqtService.getV2PricesAsync(quoteContext);
|
|
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls[0]).toMatchInlineSnapshot(`
|
|
[
|
|
[
|
|
"altonomy.uri",
|
|
],
|
|
{
|
|
"allowedChainIds": [
|
|
1337,
|
|
],
|
|
"apiKeys": [],
|
|
"integratorId": "integrator-id",
|
|
"label": "test integrator",
|
|
"rfqm": false,
|
|
},
|
|
{
|
|
"buyAmountBaseUnits": "1000",
|
|
"buyTokenAddress": "0x1",
|
|
"chainId": "1337",
|
|
"feeAmount": "100",
|
|
"feeToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
|
"integratorId": "integrator-id",
|
|
"protocolVersion": "4",
|
|
"sellTokenAddress": "0x2",
|
|
"takerAddress": "0x0",
|
|
"txOrigin": "0xtakeraddress",
|
|
"workflow": "gasless-rfqt",
|
|
},
|
|
[Function],
|
|
"gasless-rfqt",
|
|
]
|
|
`);
|
|
});
|
|
it('transforms the API request into a quote server client request for sells', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0x1',
|
|
isSelling: true,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
await rfqtService.getV2PricesAsync(quoteContext);
|
|
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls[0]).toMatchInlineSnapshot(`
|
|
[
|
|
[
|
|
"maker.uri",
|
|
],
|
|
{
|
|
"allowedChainIds": [
|
|
1337,
|
|
],
|
|
"apiKeys": [],
|
|
"integratorId": "integrator-id",
|
|
"label": "test integrator",
|
|
"rfqm": false,
|
|
},
|
|
{
|
|
"buyTokenAddress": "0x1",
|
|
"chainId": "1337",
|
|
"feeAmount": "100",
|
|
"feeToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
|
"integratorId": "integrator-id",
|
|
"protocolVersion": "4",
|
|
"sellAmountBaseUnits": "1000",
|
|
"sellTokenAddress": "0x2",
|
|
"takerAddress": "0x0",
|
|
"txOrigin": "0xtakeraddress",
|
|
"workflow": "rfqt",
|
|
},
|
|
[Function],
|
|
"rfqt",
|
|
]
|
|
`);
|
|
});
|
|
it('gets prices', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0x1',
|
|
isSelling: true,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
|
|
const price: IndicativeQuote = {
|
|
// $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me
|
|
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
expiry: new BigNumber(9999999999999999),
|
|
maker: '0xmakeraddress',
|
|
makerAmount: new BigNumber(1000),
|
|
makerToken: '0x1',
|
|
makerUri: 'maker.uri',
|
|
takerAmount: new BigNumber(1001),
|
|
takerToken: '0x2',
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([price]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2PricesAsync(quoteContext);
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0].makerId).toEqual('maker-id');
|
|
expect(result[0]).toMatchInlineSnapshot(`
|
|
{
|
|
"expiry": "10000000000000000",
|
|
"makerAddress": "0xmakeraddress",
|
|
"makerAmount": "1000",
|
|
"makerId": "maker-id",
|
|
"makerToken": "0x1",
|
|
"makerUri": "maker.uri",
|
|
"requestedSellAmount": "1000",
|
|
"takerAmount": "1001",
|
|
"takerToken": "0x2",
|
|
}
|
|
`);
|
|
});
|
|
it('gets prices from whitelisted makers only', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator: {
|
|
...integrator,
|
|
whitelistMakerIds: ['maker1'],
|
|
},
|
|
makerToken: '0x1',
|
|
isSelling: true,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
|
|
const price: IndicativeQuote = {
|
|
// $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me
|
|
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
expiry: new BigNumber(9999999999999999),
|
|
maker: '0xmakeraddress',
|
|
makerAmount: new BigNumber(1000),
|
|
makerToken: '0x1',
|
|
makerUri: 'maker.uri',
|
|
takerAmount: new BigNumber(1001),
|
|
takerToken: '0x2',
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([
|
|
{
|
|
...maker,
|
|
makerId: 'maker1',
|
|
},
|
|
{
|
|
...maker,
|
|
makerId: 'maker2',
|
|
},
|
|
]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([price]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2PricesAsync(quoteContext);
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0].makerId).toEqual('maker1');
|
|
expect(result[0]).toMatchInlineSnapshot(`
|
|
{
|
|
"expiry": "10000000000000000",
|
|
"makerAddress": "0xmakeraddress",
|
|
"makerAmount": "1000",
|
|
"makerId": "maker1",
|
|
"makerToken": "0x1",
|
|
"makerUri": "maker.uri",
|
|
"requestedSellAmount": "1000",
|
|
"takerAmount": "1001",
|
|
"takerToken": "0x2",
|
|
}
|
|
`);
|
|
});
|
|
});
|
|
describe('getV2QuotesAsync', () => {
|
|
const makerToken = '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE';
|
|
const makerAddress = '0x79b7a69d90c82E014Bf0315e164208119B510FA0';
|
|
const maker2Address = '0xe1bA72a87fb38bd7323f61177f158Fbb2D4549f4';
|
|
const takerToken = '0x42d6622deCe394b54999Fbd73D108123806f6a18';
|
|
const takerAddress = '0xE06fFA8146bBdECcBaaF72B6043b29091071AEB8';
|
|
const fakeNow = new Date(1657069278103);
|
|
const expiry = new BigNumber(fakeNow.getTime() + 1_000_000).dividedBy(ONE_SECOND_MS).decimalPlaces(0);
|
|
|
|
mockRfqBlockchainUtils.isValidOrderSignerAsync = jest.fn().mockResolvedValue(true);
|
|
it('filters out quotes with no signatures', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockResolvedValue(undefined);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext);
|
|
|
|
expect(result.length).toEqual(0);
|
|
});
|
|
|
|
it('filters out quotes with no signatures and still handles fillable amounts correctly', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker, maker2]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
{
|
|
maker: maker2Address,
|
|
makerUri: maker2.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
// First maker has no signature
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockResolvedValueOnce(undefined).mockResolvedValueOnce({
|
|
v: 27,
|
|
r: '0x123',
|
|
s: '0x456',
|
|
signatureType: SignatureType.EIP712,
|
|
});
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
// Second maker has a smaller fillable amount
|
|
mockRfqMakerBalanceCacheService.getERC20OwnerBalancesAsync = jest
|
|
.fn()
|
|
.mockResolvedValue([new BigNumber(1000), new BigNumber(100)]);
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext, fakeNow);
|
|
const order = new OtcOrder({
|
|
chainId: 1337,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
taker: NULL_ADDRESS,
|
|
makerToken,
|
|
takerToken,
|
|
maker: maker2Address,
|
|
txOrigin: quoteContext.txOrigin,
|
|
expiryAndNonce: new BigNumber(
|
|
'10401598717691489530826623925864187439861993812812831231287826374367',
|
|
),
|
|
verifyingContract: mockContractAddresses.exchangeProxy,
|
|
});
|
|
|
|
expect(result.length).toEqual(1);
|
|
expect(result).toEqual([
|
|
{
|
|
fee: undefined,
|
|
fillableMakerAmount: new BigNumber(100),
|
|
fillableTakerAmount: new BigNumber(100),
|
|
fillableTakerFeeAmount: new BigNumber(0),
|
|
makerId: maker2.makerId,
|
|
makerUri: maker2.rfqtUri,
|
|
order,
|
|
requestedBuyAmount: new BigNumber(1000),
|
|
requestedSellAmount: undefined,
|
|
signature: {
|
|
v: 27,
|
|
r: '0x0000000000000000000000000000000000000000000000000000000000000123',
|
|
s: '0x0000000000000000000000000000000000000000000000000000000000000456',
|
|
signatureType: SignatureType.EIP712,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("doesn't blow up if a sign request fails", async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockRejectedValue(new Error('EXPLODE'));
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext);
|
|
|
|
expect(result.length).toEqual(0);
|
|
});
|
|
|
|
it('[workflow: rfqt] creates orders with unique nonces', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(900),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
mockRfqMakerBalanceCacheService.getERC20OwnerBalancesAsync = jest
|
|
.fn()
|
|
.mockResolvedValue([new BigNumber(10000), new BigNumber(10000)]);
|
|
|
|
const signature: Signature = { r: 'r', v: 21, s: 's', signatureType: SignatureType.EIP712 };
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockResolvedValue(signature);
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext, fakeNow);
|
|
|
|
const [{ nonce: nonce1 }, { nonce: nonce2 }] = [
|
|
OtcOrder.parseExpiryAndNonce(result[0].order.expiryAndNonce),
|
|
OtcOrder.parseExpiryAndNonce(result[1].order.expiryAndNonce),
|
|
];
|
|
|
|
expect(nonce1.toString()).not.toEqual(nonce2.toString());
|
|
});
|
|
|
|
it('[workflow: gasless-rfqt] creates orders with unique buckets', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'gasless-rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([altonomy]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: altonomy.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: altonomy.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(900),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
mockRfqMakerBalanceCacheService.getERC20OwnerBalancesAsync = jest
|
|
.fn()
|
|
.mockResolvedValue([new BigNumber(10000), new BigNumber(10000)]);
|
|
|
|
const signature: Signature = { r: 'r', v: 21, s: 's', signatureType: SignatureType.EIP712 };
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockResolvedValue(signature);
|
|
|
|
// 0 is a special return value since this will trigger a wrap around
|
|
mockCacheClient.getNextNOtcOrderBucketsAsync = jest.fn().mockResolvedValue(0);
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext, fakeNow);
|
|
console.log('result', result);
|
|
|
|
const [{ nonceBucket: bucket1 }, { nonceBucket: bucket2 }] = [
|
|
OtcOrder.parseExpiryAndNonce(result[0].order.expiryAndNonce),
|
|
OtcOrder.parseExpiryAndNonce(result[1].order.expiryAndNonce),
|
|
];
|
|
|
|
expect(bucket1.toString()).not.toEqual(bucket2.toString());
|
|
// check that buckets are greater than zero (successfully wrapped around for negative numbers)
|
|
expect(bucket1.toNumber()).toBeGreaterThanOrEqual(0);
|
|
expect(bucket2.toNumber()).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('gets a signed quote', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken,
|
|
isSelling: false,
|
|
takerAddress: NULL_ADDRESS,
|
|
trader: takerAddress,
|
|
takerToken,
|
|
txOrigin: takerAddress,
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([
|
|
{
|
|
maker: makerAddress,
|
|
makerUri: maker.rfqtUri,
|
|
makerToken,
|
|
takerToken,
|
|
makerAmount: new BigNumber(999),
|
|
takerAmount: new BigNumber(1000),
|
|
expiry,
|
|
},
|
|
]);
|
|
const signature: Signature = { r: 'r', v: 21, s: 's', signatureType: SignatureType.EIP712 };
|
|
mockQuoteServerClient.signV2Async = jest.fn().mockResolvedValue(signature);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
mockRfqMakerBalanceCacheService.getERC20OwnerBalancesAsync = jest
|
|
.fn()
|
|
.mockResolvedValue([new BigNumber(10000)]);
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2QuotesAsync(quoteContext, fakeNow);
|
|
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0]).toMatchObject({
|
|
fillableMakerAmount: new BigNumber(999),
|
|
fillableTakerAmount: new BigNumber(1000),
|
|
fillableTakerFeeAmount: new BigNumber(0),
|
|
makerId: maker.makerId,
|
|
makerUri: maker.rfqtUri,
|
|
signature,
|
|
});
|
|
expect(result[0].order).toMatchInlineSnapshot(`
|
|
OtcOrder {
|
|
"chainId": 1337,
|
|
"expiry": "1657070278",
|
|
"expiryAndNonce": "10401598717691489530826623925864187439861993812812831231287826374366",
|
|
"maker": "0x79b7a69d90c82E014Bf0315e164208119B510FA0",
|
|
"makerAmount": "999",
|
|
"makerToken": "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE",
|
|
"nonce": "1657069278",
|
|
"nonceBucket": "0",
|
|
"taker": "0x0000000000000000000000000000000000000000",
|
|
"takerAmount": "1000",
|
|
"takerToken": "0x42d6622deCe394b54999Fbd73D108123806f6a18",
|
|
"txOrigin": "0xE06fFA8146bBdECcBaaF72B6043b29091071AEB8",
|
|
"verifyingContract": "0x5315e44798395d4a952530d131249fe00f554565",
|
|
}
|
|
`);
|
|
});
|
|
|
|
describe('test_big_order_sampling', () => {
|
|
// public async _getV2SampledPricesInternalAsync(
|
|
// TESTED static V2AssignFee(prices: RfqtV2Price[], fee: FeeWithDetails): RfqtV2Price[] {
|
|
// ******* static composeBigSizePrices(fullSizePrices: RfqtV2Price[], smallSizePrices: RfqtV2Price[]): RfqtV2Price[] {
|
|
// TESTED ******** add more cases static generateModifiedSizeOrderQuoteContext(
|
|
// static removeFeeFromPrices(pricesWithFee: RfqtV2Price[]): RfqtV2Price[] {
|
|
|
|
it('tests QuoteContext build', async () => {
|
|
const quoteContext: FirmQuoteContext = {
|
|
isFirm: true,
|
|
trader: '0x000000',
|
|
takerToken: '0x000000',
|
|
makerToken: '0x000000',
|
|
|
|
txOrigin: '0x000000',
|
|
chainId: 1337,
|
|
|
|
takerAddress: '',
|
|
workflow: 'rfqt',
|
|
originalMakerToken: '',
|
|
takerTokenDecimals: 0,
|
|
makerTokenDecimals: 0,
|
|
integrator: {
|
|
apiKeys: [],
|
|
allowedChainIds: [],
|
|
integratorId: 'uuid-integrator',
|
|
rfqm: false,
|
|
label: 'Scam Integrator 1',
|
|
},
|
|
isUnwrap: false,
|
|
isSelling: false,
|
|
feeModelVersion: 0,
|
|
takerAmount: new BigNumber(2000),
|
|
makerAmount: new BigNumber(3000), // TODO one of these two must be undefined
|
|
volumeUSD: new BigNumber(5000),
|
|
assetFillAmount: new BigNumber(4000),
|
|
};
|
|
|
|
quoteContext.makerAmount = undefined;
|
|
quoteContext.takerAmount = new BigNumber(3000);
|
|
|
|
const modifiedQuoteContext = RfqtService.generateModifiedSizeOrderQuoteContext(
|
|
quoteContext as QuoteContextVolumeRequired,
|
|
new BigNumber(1000),
|
|
);
|
|
|
|
expect(modifiedQuoteContext.takerAmount).toEqual(new BigNumber(3000 / (5000 / 1000)));
|
|
expect(modifiedQuoteContext.makerAmount).toEqual(undefined);
|
|
expect(modifiedQuoteContext.assetFillAmount).toEqual(new BigNumber(4000 / (5000 / 1000)));
|
|
expect(modifiedQuoteContext.volumeUSD).toEqual(new BigNumber(5000 / (5000 / 1000)));
|
|
});
|
|
|
|
it('tests_assign_fee_remove_fee', async () => {
|
|
const feeWithDetails: FeeWithDetails = {
|
|
details: {
|
|
kind: 'default',
|
|
tradeSizeBps: 0,
|
|
zeroExFeeAmount: new BigNumber(0),
|
|
feeTokenBaseUnitPriceUsd: new BigNumber(0),
|
|
takerTokenBaseUnitPriceUsd: new BigNumber(0),
|
|
makerTokenBaseUnitPriceUsd: new BigNumber(0),
|
|
feeModelVersion: 0,
|
|
gasFeeAmount: new BigNumber(0),
|
|
gasPrice: new BigNumber(0),
|
|
},
|
|
breakdown: {},
|
|
conversionRates: {
|
|
nativeTokenBaseUnitPriceUsd: null,
|
|
feeTokenBaseUnitPriceUsd: null,
|
|
takerTokenBaseUnitPriceUsd: null,
|
|
makerTokenBaseUnitPriceUsd: null,
|
|
},
|
|
token: '',
|
|
amount: new BigNumber(123),
|
|
type: 'fixed',
|
|
};
|
|
|
|
const prices: RfqtV2Price[] = [
|
|
{
|
|
expiry: new BigNumber(0),
|
|
makerAddress: '',
|
|
makerAmount: new BigNumber(0),
|
|
makerId: '',
|
|
makerToken: '',
|
|
makerUri: '',
|
|
takerAmount: new BigNumber(0),
|
|
takerToken: '',
|
|
fee: undefined,
|
|
},
|
|
];
|
|
RfqtService.V2AssignFee(prices, feeWithDetails);
|
|
|
|
expect(prices[0].fee).toEqual(feeWithDetails);
|
|
|
|
RfqtService.removeFeeFromPrices(prices);
|
|
|
|
expect(prices[0].fee).toEqual(undefined);
|
|
});
|
|
|
|
it('gets prices for bigger than threshold size', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(253),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
isSelling: false,
|
|
takerAddress: '0x0',
|
|
takerToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
txOrigin: '0xtakeraddress',
|
|
volumeUSD: new BigNumber(500000),
|
|
makerAmount: new BigNumber(253), // forcing not integer result to see rounding works
|
|
};
|
|
|
|
const fullSizePrice: IndicativeQuote = {
|
|
// $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me
|
|
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
expiry: new BigNumber(9999999999999999),
|
|
maker: '0xmakeraddress',
|
|
makerAmount: new BigNumber(253),
|
|
makerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
makerUri: 'maker.uri',
|
|
takerAmount: new BigNumber(500000),
|
|
takerToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
};
|
|
|
|
const smallSizePrice: IndicativeQuote = {
|
|
// $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me
|
|
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
expiry: new BigNumber(9999999999999999),
|
|
maker: '0xmakeraddress',
|
|
makerAmount: new BigNumber(50),
|
|
makerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
makerUri: 'maker.uri',
|
|
takerAmount: new BigNumber(100000),
|
|
takerToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
};
|
|
|
|
mockQuoteServerClient.batchGetPriceV2Async.mockClear();
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest
|
|
.fn()
|
|
.mockResolvedValueOnce([fullSizePrice])
|
|
.mockResolvedValueOnce([smallSizePrice]);
|
|
|
|
mockFeeService.calculateFeeAsync = jest
|
|
.fn()
|
|
.mockResolvedValueOnce({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(5),
|
|
type: 'fixed',
|
|
},
|
|
})
|
|
.mockResolvedValueOnce({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(1),
|
|
type: 'fixed',
|
|
},
|
|
})
|
|
.mockResolvedValueOnce({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(4),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2PricesAsync(quoteContext);
|
|
expect(result.length).toEqual(2);
|
|
|
|
console.log(`result[0]`, result[0]);
|
|
console.log(`result[1]`, result[1]);
|
|
expect(result[0].makerId).toEqual('maker-id');
|
|
expect(result[0]).toMatchInlineSnapshot(`
|
|
{
|
|
"expiry": "10000000000000000",
|
|
"makerAddress": "0xmakeraddress",
|
|
"makerAmount": "50",
|
|
"makerId": "maker-id",
|
|
"makerToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
|
"makerUri": "maker.uri",
|
|
"requestedBuyAmount": "50",
|
|
"takerAmount": "100000",
|
|
"takerToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
}
|
|
`);
|
|
expect(result[1]).toMatchInlineSnapshot(`
|
|
{
|
|
"expiry": "10000000000000000",
|
|
"makerAddress": "0xmakeraddress",
|
|
"makerAmount": "203",
|
|
"makerId": "maker-id",
|
|
"makerToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
|
"makerUri": "maker.uri",
|
|
"takerAmount": "400000",
|
|
"takerToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
}
|
|
`);
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls.length).toEqual(2);
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls[0][2]).toMatchInlineSnapshot(`
|
|
{
|
|
"buyAmountBaseUnits": "253",
|
|
"buyTokenAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
|
"chainId": "1337",
|
|
"feeAmount": "5",
|
|
"feeToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
|
"integratorId": "integrator-id",
|
|
"protocolVersion": "4",
|
|
"sellTokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
"takerAddress": "0x0",
|
|
"txOrigin": "0xtakeraddress",
|
|
"workflow": "rfqt",
|
|
}
|
|
`);
|
|
expect(mockQuoteServerClient.batchGetPriceV2Async.mock.calls[1][2]).toMatchInlineSnapshot(`
|
|
{
|
|
"buyAmountBaseUnits": "50",
|
|
"buyTokenAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
|
"chainId": "1337",
|
|
"feeAmount": "1",
|
|
"feeToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
|
"integratorId": "integrator-id",
|
|
"protocolVersion": "4",
|
|
"sellTokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
"takerAddress": "0x0",
|
|
"txOrigin": "0xtakeraddress",
|
|
"workflow": "rfqt",
|
|
}
|
|
`);
|
|
});
|
|
it('gets prices', async () => {
|
|
const quoteContext: QuoteContext = {
|
|
isFirm: false,
|
|
workflow: 'rfqt',
|
|
isUnwrap: false,
|
|
originalMakerToken: '0x1',
|
|
takerTokenDecimals: 18,
|
|
makerTokenDecimals: 18,
|
|
feeModelVersion: 1,
|
|
assetFillAmount: new BigNumber(1000),
|
|
chainId: 1337,
|
|
integrator,
|
|
makerToken: '0x1',
|
|
isSelling: true,
|
|
takerAddress: '0x0',
|
|
takerToken: '0x2',
|
|
txOrigin: '0xtakeraddress',
|
|
};
|
|
|
|
const price: IndicativeQuote = {
|
|
// $eslint-fix-me https://github.com/rhinodavid/eslint-fix-me
|
|
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
expiry: new BigNumber(9999999999999999),
|
|
maker: '0xmakeraddress',
|
|
makerAmount: new BigNumber(1000),
|
|
makerToken: '0x1',
|
|
makerUri: 'maker.uri',
|
|
takerAmount: new BigNumber(1001),
|
|
takerToken: '0x2',
|
|
};
|
|
|
|
mockRfqMakerManager.getRfqtV2MakersForPair = jest.fn().mockReturnValue([maker]);
|
|
mockQuoteServerClient.batchGetPriceV2Async = jest.fn().mockResolvedValue([price]);
|
|
mockFeeService.calculateFeeAsync = jest.fn().mockResolvedValue({
|
|
feeWithDetails: {
|
|
token: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
|
amount: new BigNumber(100),
|
|
type: 'fixed',
|
|
},
|
|
});
|
|
|
|
const rfqtService = new RfqtService(
|
|
1337,
|
|
mockRfqMakerManager,
|
|
mockQuoteRequestor,
|
|
mockQuoteServerClient,
|
|
DEFAULT_MIN_EXPIRY_DURATION_MS,
|
|
mockRfqBlockchainUtils,
|
|
mockTokenMetadataManager,
|
|
mockContractAddresses,
|
|
mockFeeService,
|
|
1,
|
|
mockRfqMakerBalanceCacheService,
|
|
mockCacheClient,
|
|
mockTokenPriceOracle,
|
|
mockRfqDynamicBlacklist,
|
|
);
|
|
|
|
const result = await rfqtService.getV2PricesAsync(quoteContext);
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0].makerId).toEqual('maker-id');
|
|
expect(result[0]).toMatchInlineSnapshot(`
|
|
{
|
|
"expiry": "10000000000000000",
|
|
"makerAddress": "0xmakeraddress",
|
|
"makerAmount": "1000",
|
|
"makerId": "maker-id",
|
|
"makerToken": "0x1",
|
|
"makerUri": "maker.uri",
|
|
"requestedSellAmount": "1000",
|
|
"takerAmount": "1001",
|
|
"takerToken": "0x2",
|
|
}
|
|
`);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|