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