diff --git a/packages/asset-swapper/src/swap_quoter.ts b/packages/asset-swapper/src/swap_quoter.ts index 958349e826..3ca32d35f1 100644 --- a/packages/asset-swapper/src/swap_quoter.ts +++ b/packages/asset-swapper/src/swap_quoter.ts @@ -144,7 +144,7 @@ export class SwapQuoter { * @return An instance of SwapQuoter */ constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial = {}) { - const { chainId, expiryBufferMs, permittedOrderFeeTypes, samplerGasLimit, plpAddress } = _.merge( + const { chainId, expiryBufferMs, permittedOrderFeeTypes, samplerGasLimit, plpRegistryAddress } = _.merge( {}, constants.DEFAULT_SWAP_QUOTER_OPTS, options, @@ -170,7 +170,7 @@ export class SwapQuoter { this._marketOperationUtils = new MarketOperationUtils(sampler, this._contractAddresses, { chainId, exchangeAddress: this._contractAddresses.exchange, - }, plpAddress); + }, plpRegistryAddress); this._swapQuoteCalculator = new SwapQuoteCalculator(this._protocolFeeUtils, this._marketOperationUtils); } diff --git a/packages/asset-swapper/src/types.ts b/packages/asset-swapper/src/types.ts index ea6591e48e..928169475f 100644 --- a/packages/asset-swapper/src/types.ts +++ b/packages/asset-swapper/src/types.ts @@ -212,7 +212,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts { expiryBufferMs: number; contractAddresses?: ContractAddresses; samplerGasLimit?: number; - plpAddress?: string; + plpRegistryAddress?: string; } /** diff --git a/packages/asset-swapper/src/utils/market_operation_utils/index.ts b/packages/asset-swapper/src/utils/market_operation_utils/index.ts index 394a22f56d..9754206d5b 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/index.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/index.ts @@ -79,19 +79,21 @@ export class MarketOperationUtils { makerToken.toLowerCase() === this._wethAddress.toLowerCase() ? DexOrderSampler.ops.constant(new BigNumber(1)) : DexOrderSampler.ops.getMedianSellRate( - difference(FEE_QUOTE_SOURCES, _opts.excludedSources), + difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(this._plpSourceIfAvailable()), makerToken, this._wethAddress, ONE_ETHER, this._plpRegistryAddress, ), DexOrderSampler.ops.getSellQuotes( - difference(SELL_SOURCES, _opts.excludedSources), + difference(SELL_SOURCES, _opts.excludedSources).concat(this._plpSourceIfAvailable()), makerToken, takerToken, getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase), + this._plpRegistryAddress, ), ); + const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts( nativeOrders, fillableAmounts, @@ -158,21 +160,24 @@ export class MarketOperationUtils { ...opts, }; const [makerToken, takerToken] = getOrderTokens(nativeOrders[0]); - const [fillableAmounts, ethToTakerAssetRate, dexQuotes] = await this._sampler.executeAsync( + const [fillableAmounts, plpPoolAddress, ethToTakerAssetRate, dexQuotes] = await this._sampler.executeAsync( DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders), + DexOrderSampler.ops.getLiquidityProviderFromRegistry(this._plpRegistryAddress, takerToken, makerToken), takerToken.toLowerCase() === this._wethAddress.toLowerCase() ? DexOrderSampler.ops.constant(new BigNumber(1)) : DexOrderSampler.ops.getMedianSellRate( - difference(FEE_QUOTE_SOURCES, _opts.excludedSources), + difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(this._plpSourceIfAvailable()), takerToken, this._wethAddress, ONE_ETHER, + this._plpRegistryAddress, ), DexOrderSampler.ops.getBuyQuotes( - difference(BUY_SOURCES, _opts.excludedSources), + difference(BUY_SOURCES, _opts.excludedSources).concat(this._plpSourceIfAvailable()), makerToken, takerToken, getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase), + this._plpRegistryAddress, ), ); const signedOrderWithFillableAmounts = this._createBuyOrdersPathFromSamplerResultIfExists( @@ -182,6 +187,7 @@ export class MarketOperationUtils { dexQuotes, ethToTakerAssetRate, _opts, + plpPoolAddress, ); if (!signedOrderWithFillableAmounts) { throw new Error(AggregationError.NoOptimalPath); @@ -192,6 +198,9 @@ export class MarketOperationUtils { /** * gets the orders required for a batch of market buy operations by (potentially) merging native orders with * generated bridge orders. + * + * NOTE: Currently `getBatchMarketBuyOrdersAsync()` does not support external liquidity providers. + * * @param batchNativeOrders Batch of Native orders. * @param makerAmounts Array amount of maker asset to buy for each batch. * @param opts Options object. @@ -244,6 +253,10 @@ export class MarketOperationUtils { ); } + private _plpSourceIfAvailable(): ERC20BridgeSource[] { + return this._plpRegistryAddress !== NULL_ADDRESS ? [ERC20BridgeSource.Plp] : []; + } + private _createBuyOrdersPathFromSamplerResultIfExists( nativeOrders: SignedOrder[], makerAmount: BigNumber, @@ -251,6 +264,7 @@ export class MarketOperationUtils { dexQuotes: DexSample[][], ethToTakerAssetRate: BigNumber, opts: GetMarketOrdersOpts, + plpPoolAddress?: string | undefined, ): OptimizedMarketOrder[] | undefined { const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts( nativeOrders, @@ -293,6 +307,7 @@ export class MarketOperationUtils { outputToken, collapsePath(optimalPath, true), opts.bridgeSlippage, + plpPoolAddress, ); } } @@ -474,6 +489,9 @@ function sourceToFillFlags(source: ERC20BridgeSource): number { if (source === ERC20BridgeSource.Uniswap) { return FillFlags.SourceUniswap; } + if (source === ERC20BridgeSource.Plp) { + return FillFlags.SourcePlp; + } return FillFlags.SourceNative; } diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 66bc0cf6eb..25c64122be 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -61,6 +61,7 @@ export enum FillFlags { SourceUniswap = 0x2, SourceEth2Dai = 0x4, SourceKyber = 0x8, + SourcePlp = 0x10, } /** diff --git a/packages/asset-swapper/test/dex_sampler_test.ts b/packages/asset-swapper/test/dex_sampler_test.ts index fbd44e36dd..ad749f7b4c 100644 --- a/packages/asset-swapper/test/dex_sampler_test.ts +++ b/packages/asset-swapper/test/dex_sampler_test.ts @@ -329,7 +329,7 @@ describe('DexSampler tests', () => { expect(quotes).to.deep.eq(expectedQuotes); }); - describe.only('PLP Operations', () => { + describe('PLP Operations', () => { const xAsset = randomAddress(); const yAsset = randomAddress(); const zAsset = randomAddress(); diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 1579cbc1f3..d3f32ae5de 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -21,6 +21,7 @@ import { constants as marketOperationUtilConstants } from '../src/utils/market_o import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler'; import { DexSample, ERC20BridgeSource } from '../src/utils/market_operation_utils/types'; import { Web3Wrapper } from '@0x/dev-utils'; +import { OnlyCallableIfNotInCatastrophicFailureError } from '@0x/utils/lib/src/revert_errors/staking/staking_revert_errors'; const { BUY_SOURCES, SELL_SOURCES } = marketOperationUtilConstants; @@ -143,6 +144,7 @@ describe('MarketOperationUtils tests', () => { makerToken: string, takerToken: string, fillAmounts: BigNumber[], + plpRegistryAddress?: string | undefined, ) => DexSample[][]; function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { @@ -157,6 +159,21 @@ describe('MarketOperationUtils tests', () => { }; } + function createGetMultipleSellQuotesOperationFromRatesAndRetainPLPParams(rates: RatesBySource): [{sources: ERC20BridgeSource[], plpRegistryAddress?: string}, GetMultipleQuotesOperation] { + const plpParams: {sources: ERC20BridgeSource[], plpRegistryAddress?: string} = { + sources: [], + plpRegistryAddress: undefined, + } + const fn = (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[], plpRegistryAddress: string | undefined) => { + plpParams.plpRegistryAddress = plpRegistryAddress; + plpParams.sources = sources; + return createGetMultipleSellQuotesOperationFromRates(rates)( + sources, makerToken, takerToken, fillAmounts, plpRegistryAddress, + ); + }; + return [plpParams, fn]; + } + function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation { return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => { return sources.map(s => @@ -174,6 +191,7 @@ describe('MarketOperationUtils tests', () => { makerToken: string, takerToken: string, fillAmounts: BigNumber[], + plpRegistryAddress?: string | undefined, ) => BigNumber; type GetLiquidityProviderFromRegistryOperation = ( @@ -194,6 +212,21 @@ describe('MarketOperationUtils tests', () => { } } + function getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityPoolAddress: string = NULL_ADDRESS): [{registryAddress?: string, takerToken?: string, makerToken?: string}, GetLiquidityProviderFromRegistryOperation] { + const callArgs: {registryAddress?: string, takerToken?: string, makerToken?: string} = { + registryAddress: undefined, + takerToken: undefined, + makerToken: undefined, + } + const fn = (registryAddress: string, takerToken: string, makerToken: string): string => { + callArgs.makerToken = makerToken; + callArgs.takerToken = takerToken; + callArgs.registryAddress = registryAddress; + return liquidityPoolAddress; + } + return [callArgs, fn]; + } + function createDecreasingRates(count: number): BigNumber[] { const rates: BigNumber[] = []; const initialRate = getRandomFloat(1e-3, 1e2); @@ -218,6 +251,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.CurveUsdcDai]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.CurveUsdcDaiUsdt]: _.times(NUM_SAMPLES, () => 0), [ERC20BridgeSource.CurveUsdcDaiUsdtTusd]: _.times(NUM_SAMPLES, () => 0), + [ERC20BridgeSource.Plp]: _.times(NUM_SAMPLES, () => 0), }; function findSourceWithMaxOutput(rates: RatesBySource): ERC20BridgeSource { @@ -331,6 +365,21 @@ describe('MarketOperationUtils tests', () => { expect(sourcesPolled.sort()).to.deep.eq(SELL_SOURCES.slice().sort()); }); + it('polls the liquidity provider when the registry is provided in the arguments', async () => { + const [args, fn] = createGetMultipleSellQuotesOperationFromRatesAndRetainPLPParams(DEFAULT_RATES); + replaceSamplerOps({ + getSellQuotes: fn, + }); + const registryAddress = randomAddress(); + const newMarketOperationUtils = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN, registryAddress); + await newMarketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, { + ...DEFAULT_OPTS, + excludedSources: [], + }); + expect(args.sources.sort()).to.deep.eq(SELL_SOURCES.concat([ERC20BridgeSource.Plp]).sort()); + expect(args.plpRegistryAddress).to.eql(registryAddress); + }) + it('does not poll DEXes in `excludedSources`', async () => { const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length)); let sourcesPolled: ERC20BridgeSource[] = []; @@ -782,35 +831,22 @@ describe('MarketOperationUtils tests', () => { expect(orderSources).to.deep.eq(expectedSources); }); - it.only('is able to create a order from PLP', async () => { + it('is able to create a order from PLP', async () => { const registryAddress = randomAddress(); const liquidityPoolAddress = randomAddress(); const xAsset = randomAddress(); const yAsset = randomAddress(); const toSell = Web3Wrapper.toBaseUnitAmount(10, 18); - const swapAmount = Web3Wrapper.toBaseUnitAmount(10.5, 18); - const MOCK_SAMPLER = ({ - async executeAsync(...ops: any[]): Promise { - return [ - [new BigNumber(0)], - liquidityPoolAddress, - new BigNumber(1), - [ - [ - { - source: 'PLP', - output: swapAmount, - input: toSell, - }, - ], - ], - ] - }, - async executeBatchAsync(ops: any[]): Promise { - return ops; - }, - } as any) as DexOrderSampler; + const [getSellQuiotesParams, getSellQuotesFn] = createGetMultipleSellQuotesOperationFromRatesAndRetainPLPParams({ + [ERC20BridgeSource.Plp]: createDecreasingRates(5), + }); + const [getLiquidityProviderParams, getLiquidityProviderFn] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityPoolAddress); + replaceSamplerOps({ + getOrderFillableTakerAmounts: () => [new BigNumber(0)], + getSellQuotes: getSellQuotesFn, + getLiquidityProviderFromRegistry: getLiquidityProviderFn, + }); const sampler = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN, registryAddress); const result = await sampler.getMarketSellOrdersAsync( @@ -821,6 +857,7 @@ describe('MarketOperationUtils tests', () => { }), ], Web3Wrapper.toBaseUnitAmount(10, 18), + {excludedSources: SELL_SOURCES, numSamples: 4} ); expect(result.length).to.eql(1); expect(result[0].makerAddress).to.eql(liquidityPoolAddress); @@ -830,11 +867,11 @@ describe('MarketOperationUtils tests', () => { expect(decodedAssetData.assetProxyId).to.eql(AssetProxyId.ERC20Bridge); expect(decodedAssetData.bridgeAddress).to.eql(liquidityPoolAddress); expect(result[0].takerAssetAmount).to.bignumber.eql(toSell); - - const makerAmountWithSlippage = swapAmount.times( - 1 - marketOperationUtilConstants.DEFAULT_GET_MARKET_ORDERS_OPTS.bridgeSlippage, - ); - expect(result[0].makerAssetAmount).to.eql(makerAmountWithSlippage); + expect(getSellQuiotesParams.sources).contains(ERC20BridgeSource.Plp); + expect(getSellQuiotesParams.plpRegistryAddress).is.eql(registryAddress); + expect(getLiquidityProviderParams.registryAddress).is.eql(registryAddress); + expect(getLiquidityProviderParams.makerToken).is.eql(xAsset); + expect(getLiquidityProviderParams.takerToken).is.eql(yAsset); }); }); });