* 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>
344 lines
15 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|
|
});
|