Files
protocol/packages/asset-swapper/test/quote_requestor_test.ts
Jacob Evans 3f4bb933d1 feat: v4 final (#136)
* v4 FillQuoteTransformer (#104)

* Update FQT to support v4 orders

* `@0x/contracts-zero-ex`: Tweak FQT
`@0x/contracts-zero-ex`: Drop `ERC20BridgeTransfer` event and add `PartialQuoteFill` event.

* `@0x/contracts-utils`: Add `LibSafeMathV06.downcastToUint128()`

* `@0x/protocol-utils`: Update transformer utils for V4 FQT

* `@0x/contracts-zero-ex`: Fixing FQT tests...

* `@0x/contracts-zero-ex`: rename FQT bridge event

* `@0x/contracts-zero-ex`: Un-`only` tests

* `@0x/migrations`: Update `BridgeAdapter` deployment

* `@0x/contracts-integrations`: Delete `mtx_tests`

* `@0x/protocol-utils`: Address review comments

* `@0x/contracts-zero-ex`: Address review comments

* `@0x/migrations`: Update migrations

Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* v4: Asset-swapper (main branch) (#113)

* refactor quote_requestor

* WIP v4/asset-swapper: Clean up SwapQuoter and remove @0x/orderbook

* Start replacing SignedOrder everywhere

* wip: new order type

* wip

* remove order-utils from most places

* hack: Play around with VerboseX types (#119)

* hack: Play around with VerboseX types

* More hacks

* Fix up the bridgeData encodings

* Rework Orderbook return type

* feat: Don't charge a protocol fee for RFQ orders WIP (#121)

* fix simple build errors

* simplify types a little

* remove SwapQuoteCalculator: unnecessary abstraction

* Fix all ./src build errors; make types consistent

* export more types for use in 0x API; modify Orderbook interface

* stop overriding APIOrder

* feat: RFQ v4 + consolidated bridge encoders (#125)

* feat: check if taker address is contract

* Rework bridge data

* Worst case adjustments

* RFQT v4

* Future/v4 validate orders (#126)

* RFQT v4

* v4 validate native orders

* use default invalid signature

* refactor rfqt validations in swap quoter

* fix types

* fix RFQT unlisted api key

* remove priceAwareRFQFlag

* adjust maker/taker amounts

* update JSON schemas

* filter zero fillable orders

Co-authored-by: xianny <xianny@gmail.com>

* fix type export

Co-authored-by: xianny <xianny@gmail.com>

* remove order-utils as much as possible

* work on tests compile

* Comment out quote reporter test

* updated tests

* restore order-utils accidental changes

* some lints

* Remove old fill_test

* ts lint disable for now

* update quote report

* Re-enable quote report tests

* make fill data required field

* fix lint

* type guards

* force fillData as required

* fix lint

* fix naming

* exports

* adjust MultiBridge by slippage

* cleanups (checkpoint 1)

* cleanup types (checkpoint #2)

* remove unused deps

* `@0x/contract-addresses`: Deploy new FQT (#129)

Co-authored-by: Lawrence Forman <me@merklejerk.com>

* commit bump to republish

* DRY up the rfqt mocker

* fix: Balancer load top pools (#131)

* fix: Balancer load top 250 pools

* refetch top pools on an interval

Co-authored-by: Jacob Evans <jacob@dekz.net>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>

* Update post rebase

* prettier

* Remove test helpers exported in asset-swapper

* Clean up from review comments

* prettier

* lint

* recreate rfqt mocker

* change merge and INVALID_SIGNATURE

Co-authored-by: Lawrence Forman <lawrence@0xproject.com>
Co-authored-by: Michael Zhu <mchl.zhu.96@gmail.com>
Co-authored-by: Lawrence Forman <me@merklejerk.com>
Co-authored-by: Xianny <8582774+xianny@users.noreply.github.com>
Co-authored-by: Kim Persson <kimpers@users.noreply.github.com>
2021-02-10 19:20:15 +10:00

344 lines
15 KiB
TypeScript

import { tokenUtils } from '@0x/dev-utils';
import { FillQuoteTransformerOrderType, SignatureType } from '@0x/protocol-utils';
import { TakerRequestQueryParams } from '@0x/quote-server';
import { StatusCodes } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import _ = require('lodash');
import 'mocha';
import { constants } from '../src/constants';
import { MarketOperation, MockedRfqtQuoteResponse } from '../src/types';
import { NULL_ADDRESS } from '../src/utils/market_operation_utils/constants';
import { QuoteRequestor, quoteRequestorHttpClient } from '../src/utils/quote_requestor';
import { chaiSetup } from './utils/chai_setup';
import { RfqtQuoteEndpoint, testHelpers } from './utils/test_helpers';
chaiSetup.configure();
const expect = chai.expect;
function makeThreeMinuteExpiry(): BigNumber {
const expiry = new Date(Date.now());
expiry.setMinutes(expiry.getMinutes() + 3);
return new BigNumber(Math.round(expiry.valueOf() / constants.ONE_SECOND_MS));
}
describe('QuoteRequestor', async () => {
const [makerToken, takerToken, otherToken1] = tokenUtils.getDummyERC20TokenAddresses();
const validSignature = { v: 28, r: '0x', s: '0x', signatureType: SignatureType.EthSign };
describe('requestRfqtFirmQuotesAsync for firm quotes', async () => {
it('should return successful RFQT requests', async () => {
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
const txOrigin = takerAddress;
const apiKey = 'my-ko0l-api-key';
// Set up RFQT responses
// tslint:disable-next-line:array-type
const mockedRequests: MockedRfqtQuoteResponse[] = [];
const expectedParams: TakerRequestQueryParams = {
sellTokenAddress: takerToken,
buyTokenAddress: makerToken,
sellAmountBaseUnits: '10000',
comparisonPrice: undefined,
takerAddress,
txOrigin,
protocolVersion: '4',
};
const mockedDefaults = {
requestApiKey: apiKey,
requestParams: expectedParams,
responseCode: StatusCodes.Success,
};
const validSignedOrder = {
makerToken,
takerToken,
makerAmount: new BigNumber('1000'),
takerAmount: new BigNumber('1000'),
maker: takerAddress,
taker: takerAddress,
pool: '0x',
salt: '0',
chainId: 1,
verifyingContract: takerAddress,
txOrigin,
expiry: makeThreeMinuteExpiry(),
signature: validSignature,
};
// Successful response
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://1337.0.0.1',
responseData: {
signedOrder: validSignedOrder,
},
});
// Another Successful response
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://37.0.0.1',
responseData: { signedOrder: validSignedOrder },
});
// Test out a bad response code, ensure it doesnt cause throw
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://420.0.0.1',
responseData: { error: 'bad request' },
responseCode: StatusCodes.InternalError,
});
// Test out a successful response code but a partial order
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://421.0.0.1',
responseData: { signedOrder: { makerToken: '123' } },
});
// A successful response code and invalid response data (encoding)
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://421.1.0.1',
responseData: 'this is not JSON!',
});
// A successful response code and valid order, but for wrong maker asset data
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://422.0.0.1',
responseData: { signedOrder: { ...validSignedOrder, makerToken: '0x1234' } },
});
// A successful response code and valid order, but for wrong taker asset data
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://423.0.0.1',
responseData: { signedOrder: { ...validSignedOrder, takerToken: '0x1234' } },
});
// A successful response code and good order but its unsigned
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://424.0.0.1',
responseData: { signedOrder: _.omit(validSignedOrder, ['signature']) },
});
// A successful response code and good order but for the wrong txOrigin
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://425.0.0.1',
responseData: { signedOrder: { ...validSignedOrder, txOrigin: NULL_ADDRESS } },
});
const normalizedSuccessfulOrder = {
order: {
..._.omit(validSignedOrder, ['signature']),
makerAmount: new BigNumber(validSignedOrder.makerAmount),
takerAmount: new BigNumber(validSignedOrder.takerAmount),
expiry: new BigNumber(validSignedOrder.expiry),
salt: new BigNumber(validSignedOrder.salt),
},
signature: validSignedOrder.signature,
type: FillQuoteTransformerOrderType.Rfq,
};
return testHelpers.withMockedRfqtQuotes(
mockedRequests,
RfqtQuoteEndpoint.Firm,
async () => {
const qr = new QuoteRequestor({
'https://1337.0.0.1': [[makerToken, takerToken]],
'https://420.0.0.1': [[makerToken, takerToken]],
'https://421.0.0.1': [[makerToken, takerToken]],
'https://421.1.0.1': [[makerToken, takerToken]],
'https://422.0.0.1': [[makerToken, takerToken]],
'https://423.0.0.1': [[makerToken, takerToken]],
'https://424.0.0.1': [[makerToken, takerToken]],
'https://425.0.0.1': [[makerToken, takerToken]],
'https://426.0.0.1': [] /* Shouldn't ping an RFQ-T provider when they don't support the requested asset pair. */,
'https://37.0.0.1': [[makerToken, takerToken]],
});
const resp = await qr.requestRfqtFirmQuotesAsync(
makerToken,
takerToken,
new BigNumber(10000),
MarketOperation.Sell,
undefined,
{
apiKey,
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
},
);
expect(resp).to.deep.eq([normalizedSuccessfulOrder, normalizedSuccessfulOrder]);
},
quoteRequestorHttpClient,
);
});
});
describe('requestRfqtIndicativeQuotesAsync for Indicative quotes', async () => {
it('should optionally accept a "comparisonPrice" parameter', async () => {
const response = QuoteRequestor.makeQueryParameters(
otherToken1, // tx origin
otherToken1, // taker
MarketOperation.Sell,
makerToken,
takerToken,
new BigNumber(1000),
new BigNumber(300.2),
);
expect(response.comparisonPrice).to.eql('300.2');
});
it('should return successful RFQT requests', async () => {
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
const apiKey = 'my-ko0l-api-key';
// Set up RFQT responses
// tslint:disable-next-line:array-type
const mockedRequests: MockedRfqtQuoteResponse[] = [];
const expectedParams: TakerRequestQueryParams = {
sellTokenAddress: takerToken,
buyTokenAddress: makerToken,
sellAmountBaseUnits: '10000',
comparisonPrice: undefined,
takerAddress,
txOrigin: takerAddress,
protocolVersion: '4',
};
const mockedDefaults = {
requestApiKey: apiKey,
requestParams: expectedParams,
responseCode: StatusCodes.Success,
};
// Successful response
const successfulQuote1 = {
makerToken,
takerToken,
makerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
takerAmount: new BigNumber(expectedParams.sellAmountBaseUnits),
expiry: makeThreeMinuteExpiry(),
};
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://1337.0.0.1',
responseData: successfulQuote1,
});
// Test out a bad response code, ensure it doesnt cause throw
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://420.0.0.1',
responseData: { error: 'bad request' },
responseCode: StatusCodes.InternalError,
});
// Test out a successful response code but an invalid order
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://421.0.0.1',
responseData: { makerToken: '123' },
});
// A successful response code and valid response data, but for wrong maker asset data
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://422.0.0.1',
responseData: { ...successfulQuote1, makerToken: otherToken1 },
});
// A successful response code and valid response data, but for wrong taker asset data
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://423.0.0.1',
responseData: { ...successfulQuote1, takerToken: otherToken1 },
});
// Another Successful response
mockedRequests.push({
...mockedDefaults,
endpoint: 'https://37.0.0.1',
responseData: successfulQuote1,
});
return testHelpers.withMockedRfqtQuotes(
mockedRequests,
RfqtQuoteEndpoint.Indicative,
async () => {
const qr = new QuoteRequestor({
'https://1337.0.0.1': [[makerToken, takerToken]],
'https://420.0.0.1': [[makerToken, takerToken]],
'https://421.0.0.1': [[makerToken, takerToken]],
'https://422.0.0.1': [[makerToken, takerToken]],
'https://423.0.0.1': [[makerToken, takerToken]],
'https://424.0.0.1': [[makerToken, takerToken]],
'https://37.0.0.1': [[makerToken, takerToken]],
});
const resp = await qr.requestRfqtIndicativeQuotesAsync(
makerToken,
takerToken,
new BigNumber(10000),
MarketOperation.Sell,
undefined,
{
apiKey,
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
},
);
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
},
quoteRequestorHttpClient,
);
});
it('should return successful RFQT indicative quote requests (Buy)', async () => {
const takerAddress = '0xd209925defc99488e3afff1174e48b4fa628302a';
const apiKey = 'my-ko0l-api-key';
// Set up RFQT responses
// tslint:disable-next-line:array-type
const mockedRequests: MockedRfqtQuoteResponse[] = [];
const expectedParams: TakerRequestQueryParams = {
sellTokenAddress: takerToken,
buyTokenAddress: makerToken,
buyAmountBaseUnits: '10000',
comparisonPrice: undefined,
takerAddress,
txOrigin: takerAddress,
protocolVersion: '4',
};
// Successful response
const successfulQuote1 = {
makerToken,
takerToken,
makerAmount: new BigNumber(expectedParams.buyAmountBaseUnits),
takerAmount: new BigNumber(expectedParams.buyAmountBaseUnits),
expiry: makeThreeMinuteExpiry(),
};
mockedRequests.push({
endpoint: 'https://1337.0.0.1',
requestApiKey: apiKey,
requestParams: expectedParams,
responseData: successfulQuote1,
responseCode: StatusCodes.Success,
});
return testHelpers.withMockedRfqtQuotes(
mockedRequests,
RfqtQuoteEndpoint.Indicative,
async () => {
const qr = new QuoteRequestor({ 'https://1337.0.0.1': [[makerToken, takerToken]] });
const resp = await qr.requestRfqtIndicativeQuotesAsync(
makerToken,
takerToken,
new BigNumber(10000),
MarketOperation.Buy,
undefined,
{
apiKey,
takerAddress,
txOrigin: takerAddress,
intentOnFilling: true,
},
);
expect(resp.sort()).to.eql([successfulQuote1].sort());
},
quoteRequestorHttpClient,
);
});
});
});