Compare commits
	
		
			4 Commits
		
	
	
		
			@0x/contra
			...
			romain/sam
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7f8a4101d5 | ||
| 
						 | 
					15b7cf3986 | ||
| 
						 | 
					5d4481f0f0 | ||
| 
						 | 
					2a9871d706 | 
							
								
								
									
										40
									
								
								packages/asset-swapper/src/rpc_sampler_client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/asset-swapper/src/rpc_sampler_client.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import { Client as RPCClient, JSONRPCVersionOneResponse } from 'jayson';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    LiquidityResponse,
 | 
			
		||||
    RpcLiquidityRequest,
 | 
			
		||||
    RPCSamplerCallback,
 | 
			
		||||
} from './utils/market_operation_utils/sampler_types';
 | 
			
		||||
 | 
			
		||||
const RPC_SAMPLER_SERVICE_URL = '';
 | 
			
		||||
const RPC_SAMPLER_SERVICE_PORT = 7002;
 | 
			
		||||
export class RpcSamplerClient {
 | 
			
		||||
    private readonly _rpcUrl: string;
 | 
			
		||||
    private readonly _rpcClient: RPCClient;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param rpcUrl URL to the Sampler Service to which JSON RPC requests should be sent
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this._rpcUrl = RPC_SAMPLER_SERVICE_URL;
 | 
			
		||||
        this._rpcClient = RPCClient.http({ port: RPC_SAMPLER_SERVICE_PORT });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getSellLiquidity(reqs: RpcLiquidityRequest[], rpcSamplerCallback: RPCSamplerCallback): void {
 | 
			
		||||
        try {
 | 
			
		||||
            this._rpcClient.request(`get_sell_liquidity`, [reqs], (err: any, response: JSONRPCVersionOneResponse) => {
 | 
			
		||||
                return rpcSamplerCallback(err, response.result);
 | 
			
		||||
            });
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            throw new Error(`error with sampler service: ${err}`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getSellLiquidityWrapperAsync(reqs: RpcLiquidityRequest[]): Promise<LiquidityResponse[]> {
 | 
			
		||||
        return new Promise(resolve => {
 | 
			
		||||
            this.getSellLiquidity(reqs, (err, liquidityResponses: LiquidityResponse[])  => {
 | 
			
		||||
                return resolve(liquidityResponses);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from './artifacts';
 | 
			
		||||
import { constants, INVALID_SIGNATURE, KEEP_ALIVE_TTL } from './constants';
 | 
			
		||||
import { RpcSamplerClient } from './rpc_sampler_client';
 | 
			
		||||
import {
 | 
			
		||||
    AssetSwapperContractAddresses,
 | 
			
		||||
    MarketBuySwapQuote,
 | 
			
		||||
@@ -143,6 +144,7 @@ export class SwapQuoter {
 | 
			
		||||
        this._marketOperationUtils = new MarketOperationUtils(
 | 
			
		||||
            new DexOrderSampler(
 | 
			
		||||
                this.chainId,
 | 
			
		||||
                new RpcSamplerClient(),
 | 
			
		||||
                samplerContract,
 | 
			
		||||
                samplerOverrides,
 | 
			
		||||
                undefined, // pools caches for balancer and cream
 | 
			
		||||
 
 | 
			
		||||
@@ -124,90 +124,90 @@ export class MarketOperationUtils {
 | 
			
		||||
    ): Promise<MarketSideLiquidity> {
 | 
			
		||||
        const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
 | 
			
		||||
        const { makerToken, takerToken } = nativeOrders[0].order;
 | 
			
		||||
        const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
 | 
			
		||||
        // const sampleAmounts = getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase);
 | 
			
		||||
 | 
			
		||||
        const requestFilters = new SourceFilters().exclude(_opts.excludedSources).include(_opts.includedSources);
 | 
			
		||||
        const quoteSourceFilters = this._sellSources.merge(requestFilters);
 | 
			
		||||
        const feeSourceFilters = this._feeSources.exclude(_opts.excludedFeeSources);
 | 
			
		||||
 | 
			
		||||
        // Used to determine whether the tx origin is an EOA or a contract
 | 
			
		||||
        const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
 | 
			
		||||
        // const txOrigin = (_opts.rfqt && _opts.rfqt.txOrigin) || NULL_ADDRESS;
 | 
			
		||||
 | 
			
		||||
        const sellQuotes = this._sampler.getCachedSellQuotesAsync(quoteSourceFilters.sources, makerToken, takerToken, takerAmount);
 | 
			
		||||
 | 
			
		||||
        // Call the sampler contract.
 | 
			
		||||
        const samplerPromise = this._sampler.executeAsync(
 | 
			
		||||
            this._sampler.getTokenDecimals([makerToken, takerToken]),
 | 
			
		||||
            // Get native order fillable amounts.
 | 
			
		||||
            this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
 | 
			
		||||
            // Get ETH -> maker token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                makerToken,
 | 
			
		||||
                this._nativeFeeToken,
 | 
			
		||||
                this._nativeFeeTokenAmount,
 | 
			
		||||
            ),
 | 
			
		||||
            // Get ETH -> taker token price.
 | 
			
		||||
            this._sampler.getMedianSellRate(
 | 
			
		||||
                feeSourceFilters.sources,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                this._nativeFeeToken,
 | 
			
		||||
                this._nativeFeeTokenAmount,
 | 
			
		||||
            ),
 | 
			
		||||
            // Get sell quotes for taker -> maker.
 | 
			
		||||
            this._sampler.getSellQuotes(quoteSourceFilters.sources, makerToken, takerToken, sampleAmounts),
 | 
			
		||||
            this._sampler.getTwoHopSellQuotes(
 | 
			
		||||
                quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
 | 
			
		||||
                makerToken,
 | 
			
		||||
                takerToken,
 | 
			
		||||
                takerAmount,
 | 
			
		||||
            ),
 | 
			
		||||
            this._sampler.isAddressContract(txOrigin),
 | 
			
		||||
        );
 | 
			
		||||
        // const samplerPromise = this._sampler.executeAsync(
 | 
			
		||||
        //     // this._sampler.getTokenDecimals([makerToken, takerToken]),
 | 
			
		||||
        //     // // Get native order fillable amounts.
 | 
			
		||||
        //     // this._sampler.getLimitOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchangeProxy),
 | 
			
		||||
        //     // // Get ETH -> maker token price.
 | 
			
		||||
        //     // this._sampler.getMedianSellRate(
 | 
			
		||||
        //     //     feeSourceFilters.sources,
 | 
			
		||||
        //     //     makerToken,
 | 
			
		||||
        //     //     this._nativeFeeToken,
 | 
			
		||||
        //     //     this._nativeFeeTokenAmount,
 | 
			
		||||
        //     // ),
 | 
			
		||||
        //     // // Get ETH -> taker token price.
 | 
			
		||||
        //     // this._sampler.getMedianSellRate(
 | 
			
		||||
        //     //     feeSourceFilters.sources,
 | 
			
		||||
        //     //     takerToken,
 | 
			
		||||
        //     //     this._nativeFeeToken,
 | 
			
		||||
        //     //     this._nativeFeeTokenAmount,
 | 
			
		||||
        //     // ),
 | 
			
		||||
        //     // Get sell quotes for taker -> maker.
 | 
			
		||||
        //     this._sampler.getCachedSellQuotesAsync(quoteSourceFilters.sources, makerToken, takerToken, takerAmount),
 | 
			
		||||
        //     // this._sampler.getTwoHopSellQuotes(
 | 
			
		||||
        //     //     quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
 | 
			
		||||
        //     //     makerToken,
 | 
			
		||||
        //     //     takerToken,
 | 
			
		||||
        //     //     takerAmount,
 | 
			
		||||
        //     // ),
 | 
			
		||||
        //     // this._sampler.isAddressContract(txOrigin),
 | 
			
		||||
        // );
 | 
			
		||||
 | 
			
		||||
        // Refresh the cached pools asynchronously if required
 | 
			
		||||
        void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken);
 | 
			
		||||
        // void this._refreshPoolCacheIfRequiredAsync(takerToken, makerToken);
 | 
			
		||||
 | 
			
		||||
        const [
 | 
			
		||||
            [
 | 
			
		||||
                tokenDecimals,
 | 
			
		||||
                orderFillableTakerAmounts,
 | 
			
		||||
                outputAmountPerEth,
 | 
			
		||||
                inputAmountPerEth,
 | 
			
		||||
                // tokenDecimals,
 | 
			
		||||
                // orderFillableTakerAmounts,
 | 
			
		||||
                // outputAmountPerEth,
 | 
			
		||||
                // inputAmountPerEth,
 | 
			
		||||
                dexQuotes,
 | 
			
		||||
                rawTwoHopQuotes,
 | 
			
		||||
                isTxOriginContract,
 | 
			
		||||
            ],
 | 
			
		||||
        ] = await Promise.all([samplerPromise]);
 | 
			
		||||
                // rawTwoHopQuotes,
 | 
			
		||||
                // isTxOriginContract,
 | 
			
		||||
        ] = await Promise.all([sellQuotes]);
 | 
			
		||||
 | 
			
		||||
        // Filter out any invalid two hop quotes where we couldn't find a route
 | 
			
		||||
        const twoHopQuotes = rawTwoHopQuotes.filter(
 | 
			
		||||
            q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
 | 
			
		||||
        );
 | 
			
		||||
        // const twoHopQuotes = rawTwoHopQuotes.filter(
 | 
			
		||||
        //     q => q && q.fillData && q.fillData.firstHopSource && q.fillData.secondHopSource,
 | 
			
		||||
        // );
 | 
			
		||||
 | 
			
		||||
        const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
 | 
			
		||||
        // const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
 | 
			
		||||
 | 
			
		||||
        const isRfqSupported = !!(_opts.rfqt && !isTxOriginContract);
 | 
			
		||||
        const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({
 | 
			
		||||
            ...order,
 | 
			
		||||
            ...getNativeAdjustedFillableAmountsFromTakerAmount(order, orderFillableTakerAmounts[i]),
 | 
			
		||||
        }));
 | 
			
		||||
        // const isRfqSupported = !!(_opts.rfqt && !isTxOriginContract);
 | 
			
		||||
        // const limitOrdersWithFillableAmounts = nativeOrders.map((order, i) => ({
 | 
			
		||||
        //     ...order,
 | 
			
		||||
        //     ...getNativeAdjustedFillableAmountsFromTakerAmount(order, orderFillableTakerAmounts[i]),
 | 
			
		||||
        // }));
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            side: MarketOperation.Sell,
 | 
			
		||||
            inputAmount: takerAmount,
 | 
			
		||||
            inputToken: takerToken,
 | 
			
		||||
            outputToken: makerToken,
 | 
			
		||||
            outputAmountPerEth,
 | 
			
		||||
            inputAmountPerEth,
 | 
			
		||||
            outputAmountPerEth: new BigNumber(1),
 | 
			
		||||
            inputAmountPerEth: new BigNumber(1),
 | 
			
		||||
            quoteSourceFilters,
 | 
			
		||||
            makerTokenDecimals: makerTokenDecimals.toNumber(),
 | 
			
		||||
            takerTokenDecimals: takerTokenDecimals.toNumber(),
 | 
			
		||||
            makerTokenDecimals: 18,
 | 
			
		||||
            takerTokenDecimals: 18,
 | 
			
		||||
            quotes: {
 | 
			
		||||
                nativeOrders: limitOrdersWithFillableAmounts,
 | 
			
		||||
                nativeOrders: [],
 | 
			
		||||
                rfqtIndicativeQuotes: [],
 | 
			
		||||
                twoHopQuotes,
 | 
			
		||||
                twoHopQuotes: [],
 | 
			
		||||
                dexQuotes,
 | 
			
		||||
            },
 | 
			
		||||
            isRfqSupported,
 | 
			
		||||
            isRfqSupported: true,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { ChainId } from '@0x/contract-addresses';
 | 
			
		||||
import { BigNumber, NULL_BYTES } from '@0x/utils';
 | 
			
		||||
import { RpcSamplerClient } from '../../rpc_sampler_client';
 | 
			
		||||
 | 
			
		||||
import { SamplerOverrides } from '../../types';
 | 
			
		||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
 | 
			
		||||
@@ -34,6 +35,7 @@ type BatchedOperationResult<T> = T extends BatchedOperation<infer TResult> ? TRe
 | 
			
		||||
export class DexOrderSampler extends SamplerOperations {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public readonly chainId: ChainId,
 | 
			
		||||
        rpcSamplerClient: RpcSamplerClient,
 | 
			
		||||
        _samplerContract: ERC20BridgeSamplerContract,
 | 
			
		||||
        private readonly _samplerOverrides?: SamplerOverrides,
 | 
			
		||||
        poolsCaches?: { [key in ERC20BridgeSource]: PoolsCache },
 | 
			
		||||
@@ -41,7 +43,7 @@ export class DexOrderSampler extends SamplerOperations {
 | 
			
		||||
        liquidityProviderRegistry?: LiquidityProviderRegistry,
 | 
			
		||||
        bancorServiceFn: () => Promise<BancorService | undefined> = async () => undefined,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(chainId, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
 | 
			
		||||
        super(chainId, rpcSamplerClient, _samplerContract, poolsCaches, tokenAdjacencyGraph, liquidityProviderRegistry, bancorServiceFn);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { LimitOrderFields } from '@0x/protocol-utils';
 | 
			
		||||
import { BigNumber, logUtils } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { RpcSamplerClient } from '../../rpc_sampler_client';
 | 
			
		||||
import { SamplerCallResult, SignedNativeOrder } from '../../types';
 | 
			
		||||
import { ERC20BridgeSamplerContract } from '../../wrappers';
 | 
			
		||||
 | 
			
		||||
@@ -44,6 +45,7 @@ import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
 | 
			
		||||
import { getIntermediateTokens } from './multihop_utils';
 | 
			
		||||
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
 | 
			
		||||
import { SamplerContractOperation } from './sampler_contract_operation';
 | 
			
		||||
import { Address, LiquidityResponse, RpcLiquidityRequest } from './sampler_types';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
import {
 | 
			
		||||
    BalancerFillData,
 | 
			
		||||
@@ -56,6 +58,7 @@ import {
 | 
			
		||||
    DexSample,
 | 
			
		||||
    DODOFillData,
 | 
			
		||||
    ERC20BridgeSource,
 | 
			
		||||
    FillData,
 | 
			
		||||
    GenericRouterFillData,
 | 
			
		||||
    HopInfo,
 | 
			
		||||
    KyberDmmFillData,
 | 
			
		||||
@@ -95,6 +98,8 @@ export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSour
 | 
			
		||||
 * Composable operations that can be batched in a single transaction,
 | 
			
		||||
 * for use with `DexOrderSampler.executeAsync()`.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export declare type JSONRPCQuoteCallback = (err: Error | null, dexQuotes: Array<Array<DexSample<FillData>>>) => void;
 | 
			
		||||
export class SamplerOperations {
 | 
			
		||||
    public readonly liquidityProviderRegistry: LiquidityProviderRegistry;
 | 
			
		||||
    public readonly poolsCaches: { [key in SourcesWithPoolsCache]: PoolsCache };
 | 
			
		||||
@@ -109,6 +114,7 @@ export class SamplerOperations {
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        public readonly chainId: ChainId,
 | 
			
		||||
        protected readonly rpcSamplerClient: RpcSamplerClient,
 | 
			
		||||
        protected readonly _samplerContract: ERC20BridgeSamplerContract,
 | 
			
		||||
        poolsCaches?: { [key in SourcesWithPoolsCache]: PoolsCache },
 | 
			
		||||
        protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] },
 | 
			
		||||
@@ -130,8 +136,101 @@ export class SamplerOperations {
 | 
			
		||||
        bancorServiceFn()
 | 
			
		||||
            .then(service => (this._bancorService = service))
 | 
			
		||||
            .catch(/* do nothing */);
 | 
			
		||||
        this.rpcSamplerClient = new RpcSamplerClient();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getTokenDecimalsAsync(tokens: Address[]): Promise<number[]> {
 | 
			
		||||
        return [];
 | 
			
		||||
        // return (await this.rpcSamplerClient.getTokensAsync(tokens)).map(t => t.decimals);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getCachedSellQuotesAsync(
 | 
			
		||||
        sources: ERC20BridgeSource[],
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        takerAmount: BigNumber,
 | 
			
		||||
        // callback: JSONRPCQuoteCallback,
 | 
			
		||||
    ): Promise<Array<Array<DexSample<FillData>>>> {
 | 
			
		||||
    // ): Promise<void> {
 | 
			
		||||
        const rpcLiquidityRequests: RpcLiquidityRequest[] = sources.map(source => {
 | 
			
		||||
            return {
 | 
			
		||||
                tokenPath: [makerToken, takerToken],
 | 
			
		||||
                inputAmount: takerAmount.toString(),
 | 
			
		||||
                source,
 | 
			
		||||
                demand: true,
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        const liquidityResponses: LiquidityResponse[] = await this.rpcSamplerClient.getSellLiquidityWrapperAsync(rpcLiquidityRequests);
 | 
			
		||||
        const dexQuotes: Array<Array<DexSample<FillData>>> = liquidityResponses.map((liquidityResponse: LiquidityResponse) => {
 | 
			
		||||
            const dexSample: Array<DexSample<FillData>> = liquidityResponse.liquidityCurves.map((point, j) => {
 | 
			
		||||
                const fillData: DexSample = {
 | 
			
		||||
                    source: liquidityResponse.source,
 | 
			
		||||
                    fillData: point[j].encodedFillData,
 | 
			
		||||
                    input: new BigNumber(point[j].sellAmount.toString()), // TODO Romain: prob a better way
 | 
			
		||||
                    output: new BigNumber(point[j].buyAmount.toString()),
 | 
			
		||||
                    // gasCost: new BigNumber(point[j].gasCost.toString()),
 | 
			
		||||
                };
 | 
			
		||||
                return fillData;
 | 
			
		||||
            });
 | 
			
		||||
            return dexSample;
 | 
			
		||||
        });
 | 
			
		||||
        return dexQuotes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getBuyQuotesAsync(
 | 
			
		||||
        sources: ERC20BridgeSource[],
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        takerAmount: BigNumber,
 | 
			
		||||
    ): Promise<Array<Array<DexSample<FillData>>>> {
 | 
			
		||||
        return [];
 | 
			
		||||
        // const rpcLiquidityRequests: RpcLiquidityRequest[] = sources.map(source => {
 | 
			
		||||
        //     return {
 | 
			
		||||
        //         tokenPath: [makerToken, takerToken],
 | 
			
		||||
        //         inputAmount: takerAmount.toString(),
 | 
			
		||||
        //         source,
 | 
			
		||||
        //         demand: true,
 | 
			
		||||
        //     };
 | 
			
		||||
        // });
 | 
			
		||||
        // const rpcLiquidityResponse = await this.rpcSamplerClient.getBuyLiquidityAsync(rpcLiquidityRequests);
 | 
			
		||||
        // const dexQuotes: Array<Array<DexSample<FillData>>> = rpcLiquidityResponse.map((response, i) => {
 | 
			
		||||
        //     const dexSample: Array<DexSample<FillData>> = response.liquidityCurve.map((point, j) => {
 | 
			
		||||
        //         const fillData: DexSample = {
 | 
			
		||||
        //             source: sources[i],
 | 
			
		||||
        //             fillData: point.encodedFillData,
 | 
			
		||||
        //             input: point.sellAmount,
 | 
			
		||||
        //             output: point.buyAmount,
 | 
			
		||||
        //         };
 | 
			
		||||
        //         return fillData;
 | 
			
		||||
        //     });
 | 
			
		||||
        //     return dexSample;
 | 
			
		||||
        // });
 | 
			
		||||
        // return dexQuotes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getMedianSellRateAsync(
 | 
			
		||||
        sources: ERC20BridgeSource[],
 | 
			
		||||
        makerToken: string,
 | 
			
		||||
        takerToken: string,
 | 
			
		||||
        takerFillAmount: BigNumber,
 | 
			
		||||
    ): Promise<BigNumber> {
 | 
			
		||||
        return new BigNumber(0);
 | 
			
		||||
        // const samples = await this.getSellQuotesAsync(sources, makerToken, takerToken, takerFillAmount);
 | 
			
		||||
        // if (samples.length === 0) {
 | 
			
		||||
        //     return ZERO_AMOUNT;
 | 
			
		||||
        // }
 | 
			
		||||
        // const flatSortedSamples = samples
 | 
			
		||||
        //     .reduce((acc, v) => acc.concat(...v))
 | 
			
		||||
        //     .filter(v => !v.output.isZero())
 | 
			
		||||
        //     .sort((a, b) => a.output.comparedTo(b.output));
 | 
			
		||||
        // if (flatSortedSamples.length === 0) {
 | 
			
		||||
        //     return ZERO_AMOUNT;
 | 
			
		||||
        // }
 | 
			
		||||
        // const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
 | 
			
		||||
        // return medianSample.output.div(takerFillAmount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Legacy
 | 
			
		||||
    public getTokenDecimals(tokens: string[]): BatchedOperation<BigNumber[]> {
 | 
			
		||||
        return new SamplerContractOperation({
 | 
			
		||||
            source: ERC20BridgeSource.Native,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { ERC20BridgeSource } from './types';
 | 
			
		||||
 | 
			
		||||
export type Bytes = string;
 | 
			
		||||
export type Address = Bytes;
 | 
			
		||||
 | 
			
		||||
export type LiquiditySource = ERC20BridgeSource;
 | 
			
		||||
 | 
			
		||||
export declare type RPCSamplerCallback = (err: Error | null, liquidityResponses: LiquidityResponse[]) => void;
 | 
			
		||||
 | 
			
		||||
export interface RpcLiquidityRequest {
 | 
			
		||||
    tokenPath: Address[];
 | 
			
		||||
    inputAmount: string;
 | 
			
		||||
    source: LiquiditySource;
 | 
			
		||||
    demand: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LiquidityCurvePoint {
 | 
			
		||||
    sellAmount: bigint;
 | 
			
		||||
    buyAmount: bigint;
 | 
			
		||||
    encodedFillData: Bytes;
 | 
			
		||||
    gasCost: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LiquidityResponse {
 | 
			
		||||
    source: LiquiditySource;
 | 
			
		||||
    liquidityCurves: LiquidityCurvePoint[][];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TokenResponse {
 | 
			
		||||
    address: Address;
 | 
			
		||||
    symbol: string;
 | 
			
		||||
    decimals: number;
 | 
			
		||||
    gasCost: number;
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@ import { QuoteRequestor } from '../../utils/quote_requestor';
 | 
			
		||||
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
 | 
			
		||||
 | 
			
		||||
import { CollapsedPath } from './path';
 | 
			
		||||
import { LiquiditySource } from './sampler_types';
 | 
			
		||||
import { SourceFilters } from './source_filters';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -175,6 +176,7 @@ export interface DexSample<TFillData extends FillData = FillData> {
 | 
			
		||||
    fillData: TFillData;
 | 
			
		||||
    input: BigNumber;
 | 
			
		||||
    output: BigNumber;
 | 
			
		||||
    // gasCost: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
export interface CurveFillData extends FillData {
 | 
			
		||||
    fromTokenIdx: number;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,567 +0,0 @@
 | 
			
		||||
import { ChainId, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
 | 
			
		||||
import {
 | 
			
		||||
    constants,
 | 
			
		||||
    expect,
 | 
			
		||||
    getRandomFloat,
 | 
			
		||||
    getRandomInteger,
 | 
			
		||||
    randomAddress,
 | 
			
		||||
    toBaseUnitAmount,
 | 
			
		||||
} from '@0x/contracts-test-utils';
 | 
			
		||||
import { FillQuoteTransformerOrderType, LimitOrderFields, SignatureType } from '@0x/protocol-utils';
 | 
			
		||||
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { SignedOrder } from '../src/types';
 | 
			
		||||
import { DexOrderSampler, getSampleAmounts } from '../src/utils/market_operation_utils/sampler';
 | 
			
		||||
import { ERC20BridgeSource, TokenAdjacencyGraph } from '../src/utils/market_operation_utils/types';
 | 
			
		||||
 | 
			
		||||
import { MockSamplerContract } from './utils/mock_sampler_contract';
 | 
			
		||||
import { generatePseudoRandomSalt } from './utils/utils';
 | 
			
		||||
 | 
			
		||||
const CHAIN_ID = 1;
 | 
			
		||||
const EMPTY_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
 | 
			
		||||
// tslint:disable: custom-no-magic-numbers
 | 
			
		||||
describe('DexSampler tests', () => {
 | 
			
		||||
    const MAKER_TOKEN = randomAddress();
 | 
			
		||||
    const TAKER_TOKEN = randomAddress();
 | 
			
		||||
    const chainId = ChainId.Mainnet;
 | 
			
		||||
 | 
			
		||||
    const wethAddress = getContractAddressesForChainOrThrow(CHAIN_ID).etherToken;
 | 
			
		||||
    const exchangeProxyAddress = getContractAddressesForChainOrThrow(CHAIN_ID).exchangeProxy;
 | 
			
		||||
 | 
			
		||||
    const tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [wethAddress] };
 | 
			
		||||
 | 
			
		||||
    describe('getSampleAmounts()', () => {
 | 
			
		||||
        const FILL_AMOUNT = getRandomInteger(1, 1e18);
 | 
			
		||||
        const NUM_SAMPLES = 16;
 | 
			
		||||
 | 
			
		||||
        it('generates the correct number of amounts', () => {
 | 
			
		||||
            const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
 | 
			
		||||
            expect(amounts).to.be.length(NUM_SAMPLES);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('first amount is nonzero', () => {
 | 
			
		||||
            const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
 | 
			
		||||
            expect(amounts[0]).to.not.bignumber.eq(0);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('last amount is the fill amount', () => {
 | 
			
		||||
            const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
 | 
			
		||||
            expect(amounts[NUM_SAMPLES - 1]).to.bignumber.eq(FILL_AMOUNT);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can generate a single amount', () => {
 | 
			
		||||
            const amounts = getSampleAmounts(FILL_AMOUNT, 1);
 | 
			
		||||
            expect(amounts).to.be.length(1);
 | 
			
		||||
            expect(amounts[0]).to.bignumber.eq(FILL_AMOUNT);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('generates ascending amounts', () => {
 | 
			
		||||
            const amounts = getSampleAmounts(FILL_AMOUNT, NUM_SAMPLES);
 | 
			
		||||
            for (const i of _.times(NUM_SAMPLES).slice(1)) {
 | 
			
		||||
                const prev = amounts[i - 1];
 | 
			
		||||
                const amount = amounts[i];
 | 
			
		||||
                expect(prev).to.bignumber.lt(amount);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function createOrder(overrides?: Partial<LimitOrderFields>): SignedOrder<LimitOrderFields> {
 | 
			
		||||
        const o: SignedOrder<LimitOrderFields> = {
 | 
			
		||||
            order: {
 | 
			
		||||
                salt: generatePseudoRandomSalt(),
 | 
			
		||||
                expiry: getRandomInteger(0, 2 ** 64),
 | 
			
		||||
                makerToken: MAKER_TOKEN,
 | 
			
		||||
                takerToken: TAKER_TOKEN,
 | 
			
		||||
                makerAmount: getRandomInteger(1, 1e18),
 | 
			
		||||
                takerAmount: getRandomInteger(1, 1e18),
 | 
			
		||||
                takerTokenFeeAmount: constants.ZERO_AMOUNT,
 | 
			
		||||
                chainId: CHAIN_ID,
 | 
			
		||||
                pool: EMPTY_BYTES32,
 | 
			
		||||
                feeRecipient: NULL_ADDRESS,
 | 
			
		||||
                sender: NULL_ADDRESS,
 | 
			
		||||
                maker: NULL_ADDRESS,
 | 
			
		||||
                taker: NULL_ADDRESS,
 | 
			
		||||
                verifyingContract: exchangeProxyAddress,
 | 
			
		||||
                ...overrides,
 | 
			
		||||
            },
 | 
			
		||||
            signature: { v: 1, r: hexUtils.random(), s: hexUtils.random(), signatureType: SignatureType.EthSign },
 | 
			
		||||
            type: FillQuoteTransformerOrderType.Limit,
 | 
			
		||||
        };
 | 
			
		||||
        return o;
 | 
			
		||||
    }
 | 
			
		||||
    const ORDERS = _.times(4, () => createOrder());
 | 
			
		||||
    const SIMPLE_ORDERS = ORDERS.map(o => _.omit(o.order, ['chainId', 'verifyingContract']));
 | 
			
		||||
 | 
			
		||||
    describe('operations', () => {
 | 
			
		||||
        it('getLimitOrderFillableMakerAssetAmounts()', async () => {
 | 
			
		||||
            const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                getLimitOrderFillableMakerAssetAmounts: (orders, signatures) => {
 | 
			
		||||
                    expect(orders).to.deep.eq(SIMPLE_ORDERS);
 | 
			
		||||
                    expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
 | 
			
		||||
                    return expectedFillableAmounts;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeProxyAddress),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getLimitOrderFillableTakerAssetAmounts()', async () => {
 | 
			
		||||
            const expectedFillableAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                getLimitOrderFillableTakerAssetAmounts: (orders, signatures) => {
 | 
			
		||||
                    expect(orders).to.deep.eq(SIMPLE_ORDERS);
 | 
			
		||||
                    expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
 | 
			
		||||
                    return expectedFillableAmounts;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeProxyAddress),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedFillableAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getKyberSellQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleSellsFromKyberNetwork: (_reserveOffset, takerToken, makerToken, fillAmounts) => {
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
                    return ['0x', '0x', expectedMakerFillAmounts];
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getKyberSellQuotes(
 | 
			
		||||
                    { hintHandler: randomAddress(), networkProxy: randomAddress(), weth: randomAddress() },
 | 
			
		||||
                    new BigNumber(0),
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    expectedTakerFillAmounts,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getLiquidityProviderSellQuotes()', async () => {
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const poolAddress = randomAddress();
 | 
			
		||||
            const gasCost = 123;
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleSellsFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
 | 
			
		||||
                    expect(providerAddress).to.eq(poolAddress);
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    return [toBaseUnitAmount(1001)];
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                {
 | 
			
		||||
                    [poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
 | 
			
		||||
                },
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [result] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getSellQuotes(
 | 
			
		||||
                    [ERC20BridgeSource.LiquidityProvider],
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    [toBaseUnitAmount(1000)],
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(result).to.deep.equal([
 | 
			
		||||
                [
 | 
			
		||||
                    {
 | 
			
		||||
                        source: 'LiquidityProvider',
 | 
			
		||||
                        output: toBaseUnitAmount(1001),
 | 
			
		||||
                        input: toBaseUnitAmount(1000),
 | 
			
		||||
                        fillData: { poolAddress, gasCost },
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            ]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getLiquidityProviderBuyQuotes()', async () => {
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const poolAddress = randomAddress();
 | 
			
		||||
            const gasCost = 321;
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleBuysFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
 | 
			
		||||
                    expect(providerAddress).to.eq(poolAddress);
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    return [toBaseUnitAmount(999)];
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                {
 | 
			
		||||
                    [poolAddress]: { tokens: [expectedMakerToken, expectedTakerToken], gasCost },
 | 
			
		||||
                },
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [result] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getBuyQuotes(
 | 
			
		||||
                    [ERC20BridgeSource.LiquidityProvider],
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    [toBaseUnitAmount(1000)],
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(result).to.deep.equal([
 | 
			
		||||
                [
 | 
			
		||||
                    {
 | 
			
		||||
                        source: 'LiquidityProvider',
 | 
			
		||||
                        output: toBaseUnitAmount(999),
 | 
			
		||||
                        input: toBaseUnitAmount(1000),
 | 
			
		||||
                        fillData: { poolAddress, gasCost },
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            ]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getUniswapSellQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleSellsFromUniswap: (_router, takerToken, makerToken, fillAmounts) => {
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
                    return expectedMakerFillAmounts;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getUniswapSellQuotes(
 | 
			
		||||
                    randomAddress(),
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    expectedTakerFillAmounts,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getUniswapV2SellQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleSellsFromUniswapV2: (_router, path, fillAmounts) => {
 | 
			
		||||
                    expect(path).to.deep.eq([expectedMakerToken, expectedTakerToken]);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
                    return expectedMakerFillAmounts;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getUniswapV2SellQuotes(
 | 
			
		||||
                    NULL_ADDRESS,
 | 
			
		||||
                    [expectedMakerToken, expectedTakerToken],
 | 
			
		||||
                    expectedTakerFillAmounts,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('getUniswapBuyQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10);
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleBuysFromUniswap: (_router, takerToken, makerToken, fillAmounts) => {
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
                    return expectedTakerFillAmounts;
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [fillableAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getUniswapBuyQuotes(
 | 
			
		||||
                    randomAddress(),
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    expectedMakerFillAmounts,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            expect(fillableAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        interface RatesBySource {
 | 
			
		||||
            [src: string]: BigNumber;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        it('getSellQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const sources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
 | 
			
		||||
            const ratesBySource: RatesBySource = {
 | 
			
		||||
                [ERC20BridgeSource.Kyber]: getRandomFloat(0, 100),
 | 
			
		||||
                [ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
 | 
			
		||||
                [ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
 | 
			
		||||
            };
 | 
			
		||||
            const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
 | 
			
		||||
            let uniswapRouter: string;
 | 
			
		||||
            let uniswapV2Router: string;
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleSellsFromUniswap: (router, takerToken, makerToken, fillAmounts) => {
 | 
			
		||||
                    uniswapRouter = router;
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
                    return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
 | 
			
		||||
                },
 | 
			
		||||
                sampleSellsFromUniswapV2: (router, path, fillAmounts) => {
 | 
			
		||||
                    uniswapV2Router = router;
 | 
			
		||||
                    if (path.length === 2) {
 | 
			
		||||
                        expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
 | 
			
		||||
                    } else if (path.length === 3) {
 | 
			
		||||
                        expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        expect(path).to.have.lengthOf.within(2, 3);
 | 
			
		||||
                    }
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts);
 | 
			
		||||
                    return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                tokenAdjacencyGraph,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [quotes] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getSellQuotes(
 | 
			
		||||
                    sources,
 | 
			
		||||
                    expectedMakerToken,
 | 
			
		||||
                    expectedTakerToken,
 | 
			
		||||
                    expectedTakerFillAmounts,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            const expectedQuotes = sources.map(s =>
 | 
			
		||||
                expectedTakerFillAmounts.map(a => ({
 | 
			
		||||
                    source: s,
 | 
			
		||||
                    input: a,
 | 
			
		||||
                    output: a.times(ratesBySource[s]).integerValue(),
 | 
			
		||||
                    fillData: (() => {
 | 
			
		||||
                        if (s === ERC20BridgeSource.UniswapV2) {
 | 
			
		||||
                            return {
 | 
			
		||||
                                router: uniswapV2Router,
 | 
			
		||||
                                tokenAddressPath: [expectedTakerToken, expectedMakerToken],
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                        // TODO jacob pass through
 | 
			
		||||
                        if (s === ERC20BridgeSource.Uniswap) {
 | 
			
		||||
                            return { router: uniswapRouter };
 | 
			
		||||
                        }
 | 
			
		||||
                        return {};
 | 
			
		||||
                    })(),
 | 
			
		||||
                })),
 | 
			
		||||
            );
 | 
			
		||||
            const uniswapV2ETHQuotes = [
 | 
			
		||||
                expectedTakerFillAmounts.map(a => ({
 | 
			
		||||
                    source: ERC20BridgeSource.UniswapV2,
 | 
			
		||||
                    input: a,
 | 
			
		||||
                    output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
 | 
			
		||||
                    fillData: {
 | 
			
		||||
                        router: uniswapV2Router,
 | 
			
		||||
                        tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
 | 
			
		||||
                    },
 | 
			
		||||
                })),
 | 
			
		||||
            ];
 | 
			
		||||
            //  extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB)
 | 
			
		||||
            const additionalSourceCount = 1;
 | 
			
		||||
            expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount);
 | 
			
		||||
            expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
 | 
			
		||||
        });
 | 
			
		||||
        it('getBuyQuotes()', async () => {
 | 
			
		||||
            const expectedTakerToken = randomAddress();
 | 
			
		||||
            const expectedMakerToken = randomAddress();
 | 
			
		||||
            const sources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2];
 | 
			
		||||
            const ratesBySource: RatesBySource = {
 | 
			
		||||
                [ERC20BridgeSource.Uniswap]: getRandomFloat(0, 100),
 | 
			
		||||
                [ERC20BridgeSource.UniswapV2]: getRandomFloat(0, 100),
 | 
			
		||||
            };
 | 
			
		||||
            const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3);
 | 
			
		||||
            let uniswapRouter: string;
 | 
			
		||||
            let uniswapV2Router: string;
 | 
			
		||||
            const sampler = new MockSamplerContract({
 | 
			
		||||
                sampleBuysFromUniswap: (router, takerToken, makerToken, fillAmounts) => {
 | 
			
		||||
                    uniswapRouter = router;
 | 
			
		||||
                    expect(takerToken).to.eq(expectedTakerToken);
 | 
			
		||||
                    expect(makerToken).to.eq(expectedMakerToken);
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
                    return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Uniswap]).integerValue());
 | 
			
		||||
                },
 | 
			
		||||
                sampleBuysFromUniswapV2: (router, path, fillAmounts) => {
 | 
			
		||||
                    uniswapV2Router = router;
 | 
			
		||||
                    if (path.length === 2) {
 | 
			
		||||
                        expect(path).to.deep.eq([expectedTakerToken, expectedMakerToken]);
 | 
			
		||||
                    } else if (path.length === 3) {
 | 
			
		||||
                        expect(path).to.deep.eq([expectedTakerToken, wethAddress, expectedMakerToken]);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        expect(path).to.have.lengthOf.within(2, 3);
 | 
			
		||||
                    }
 | 
			
		||||
                    expect(fillAmounts).to.deep.eq(expectedMakerFillAmounts);
 | 
			
		||||
                    return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                chainId,
 | 
			
		||||
                sampler,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                tokenAdjacencyGraph,
 | 
			
		||||
                undefined,
 | 
			
		||||
                async () => undefined,
 | 
			
		||||
            );
 | 
			
		||||
            const [quotes] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                dexOrderSampler.getBuyQuotes(sources, expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
 | 
			
		||||
            );
 | 
			
		||||
            const expectedQuotes = sources.map(s =>
 | 
			
		||||
                expectedMakerFillAmounts.map(a => ({
 | 
			
		||||
                    source: s,
 | 
			
		||||
                    input: a,
 | 
			
		||||
                    output: a.times(ratesBySource[s]).integerValue(),
 | 
			
		||||
                    fillData: (() => {
 | 
			
		||||
                        if (s === ERC20BridgeSource.UniswapV2) {
 | 
			
		||||
                            return {
 | 
			
		||||
                                router: uniswapV2Router,
 | 
			
		||||
                                tokenAddressPath: [expectedTakerToken, expectedMakerToken],
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                        if (s === ERC20BridgeSource.Uniswap) {
 | 
			
		||||
                            return { router: uniswapRouter };
 | 
			
		||||
                        }
 | 
			
		||||
                        return {};
 | 
			
		||||
                    })(),
 | 
			
		||||
                })),
 | 
			
		||||
            );
 | 
			
		||||
            const uniswapV2ETHQuotes = [
 | 
			
		||||
                expectedMakerFillAmounts.map(a => ({
 | 
			
		||||
                    source: ERC20BridgeSource.UniswapV2,
 | 
			
		||||
                    input: a,
 | 
			
		||||
                    output: a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue(),
 | 
			
		||||
                    fillData: {
 | 
			
		||||
                        router: uniswapV2Router,
 | 
			
		||||
                        tokenAddressPath: [expectedTakerToken, wethAddress, expectedMakerToken],
 | 
			
		||||
                    },
 | 
			
		||||
                })),
 | 
			
		||||
            ];
 | 
			
		||||
            //  extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB)
 | 
			
		||||
            expect(quotes).to.have.lengthOf(sources.length + 1);
 | 
			
		||||
            expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes));
 | 
			
		||||
        });
 | 
			
		||||
        describe('batched operations', () => {
 | 
			
		||||
            it('getLimitOrderFillableMakerAssetAmounts(), getLimitOrderFillableTakerAssetAmounts()', async () => {
 | 
			
		||||
                const expectedFillableTakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
 | 
			
		||||
                const expectedFillableMakerAmounts = ORDERS.map(() => getRandomInteger(0, 100e18));
 | 
			
		||||
                const sampler = new MockSamplerContract({
 | 
			
		||||
                    getLimitOrderFillableMakerAssetAmounts: (orders, signatures) => {
 | 
			
		||||
                        expect(orders).to.deep.eq(SIMPLE_ORDERS);
 | 
			
		||||
                        expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
 | 
			
		||||
                        return expectedFillableMakerAmounts;
 | 
			
		||||
                    },
 | 
			
		||||
                    getLimitOrderFillableTakerAssetAmounts: (orders, signatures) => {
 | 
			
		||||
                        expect(orders).to.deep.eq(SIMPLE_ORDERS);
 | 
			
		||||
                        expect(signatures).to.deep.eq(ORDERS.map(o => o.signature));
 | 
			
		||||
                        return expectedFillableTakerAmounts;
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
                const dexOrderSampler = new DexOrderSampler(
 | 
			
		||||
                    chainId,
 | 
			
		||||
                    sampler,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    async () => undefined,
 | 
			
		||||
                );
 | 
			
		||||
                const [fillableMakerAmounts, fillableTakerAmounts] = await dexOrderSampler.executeAsync(
 | 
			
		||||
                    dexOrderSampler.getLimitOrderFillableMakerAmounts(ORDERS, exchangeProxyAddress),
 | 
			
		||||
                    dexOrderSampler.getLimitOrderFillableTakerAmounts(ORDERS, exchangeProxyAddress),
 | 
			
		||||
                );
 | 
			
		||||
                expect(fillableMakerAmounts).to.deep.eq(expectedFillableMakerAmounts);
 | 
			
		||||
                expect(fillableTakerAmounts).to.deep.eq(expectedFillableTakerAmounts);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// tslint:disable-next-line: max-file-line-count
 | 
			
		||||
		Reference in New Issue
	
	Block a user