Feature/liquidity provider sandbox (#16)

* Update liquidity provider feature to use sandbox

* add support for liquidity provider feature in the exchange proxy swap quote consumer

* Move to off-chain liquidity provider registry

* Update ILiquidityProvider interface

* Remove some unused artifacts and wrappers

* Consolidate ILiquidityProvider

* prettier

* lint

* Address PR feedback

* Add failover to sandbox

* Add test for failover behavior in LiquidityProviderSandbox

* Update changelogs

* Emit events for the new LiquidityProvider scenarios

* Fix swap quote consumer bug

* post-rebase fixes

* `@0x/contracts-zero-ex`: bump feature versions

* Add default field to TokenAdjacencyGraph

* update addresses

Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
mzhu25
2020-11-13 12:22:21 -08:00
committed by GitHub
parent 75dcd687e2
commit 7403c0255a
66 changed files with 1083 additions and 2098 deletions

View File

@@ -11,7 +11,7 @@ import {
} from '@0x/contracts-test-utils';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { AssetProxyId, ERC20BridgeAssetData, SignedOrder } from '@0x/types';
import { BigNumber, fromTokenUnitAmount, hexUtils, NULL_ADDRESS } from '@0x/utils';
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import * as TypeMoq from 'typemoq';
@@ -63,10 +63,11 @@ const DEFAULT_EXCLUDED = [
ERC20BridgeSource.Shell,
ERC20BridgeSource.Cream,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.LiquidityProvider,
];
const BUY_SOURCES = BUY_SOURCE_FILTER.sources;
const SELL_SOURCES = SELL_SOURCE_FILTER.sources;
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = {};
const TOKEN_ADJACENCY_GRAPH: TokenAdjacencyGraph = { default: [] };
const PRICE_AWARE_RFQ_ENABLED: PriceAwareRFQFlags = {
isFirmPriceAwareEnabled: true,
isIndicativePriceAwareEnabled: true,
@@ -77,7 +78,6 @@ describe('MarketOperationUtils tests', () => {
const CHAIN_ID = ChainId.Mainnet;
const contractAddresses = {
...getContractAddressesForChainOrThrow(CHAIN_ID),
multiBridge: NULL_ADDRESS,
...BRIDGE_ADDRESSES_BY_CHAIN[CHAIN_ID],
};
@@ -242,38 +242,6 @@ describe('MarketOperationUtils tests', () => {
};
}
function callTradeOperationAndRetainLiquidityProviderParams(
tradeOperation: (rates: RatesBySource) => GetMultipleQuotesOperation,
rates: RatesBySource,
): [{ sources: ERC20BridgeSource[]; liquidityProviderAddress?: string }, GetMultipleQuotesOperation] {
const liquidityPoolParams: { sources: ERC20BridgeSource[]; liquidityProviderAddress?: string } = {
sources: [],
liquidityProviderAddress: undefined,
};
const fn = (
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
fillAmounts: BigNumber[],
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph = TOKEN_ADJACENCY_GRAPH,
liquidityProviderAddress?: string,
) => {
liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress;
liquidityPoolParams.sources = liquidityPoolParams.sources.concat(sources);
return tradeOperation(rates)(
sources,
makerToken,
takerToken,
fillAmounts,
wethAddress,
TOKEN_ADJACENCY_GRAPH,
liquidityProviderAddress,
);
};
return [liquidityPoolParams, fn];
}
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
return (
sources: ERC20BridgeSource[],
@@ -335,7 +303,6 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Bancor]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Curve]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MStable]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Mooniswap]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.Swerve]: _.times(NUM_SAMPLES, () => 0),
@@ -480,6 +447,7 @@ describe('MarketOperationUtils tests', () => {
},
balancerPoolsCache: new BalancerPoolsCache(),
creamPoolsCache: new CreamPoolsCache(),
liquidityProviderRegistry: {},
} as any) as DexOrderSampler;
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
@@ -617,54 +585,6 @@ describe('MarketOperationUtils tests', () => {
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(SELL_SOURCES.slice().sort());
});
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
const [args, fn] = callTradeOperationAndRetainLiquidityProviderParams(
createGetMultipleSellQuotesOperationFromRates,
DEFAULT_RATES,
);
replaceSamplerOps({
getSellQuotes: fn,
getTwoHopSellQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
if (sources.length !== 0) {
args.sources.push(ERC20BridgeSource.MultiHop);
args.sources.push(...sources);
}
return DEFAULT_OPS.getTwoHopSellQuotes(..._args);
},
getBalancerSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
getCreamSellQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamSellQuotesOffChainAsync(makerToken, takerToken, takerFillAmounts);
},
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
await newMarketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
excludedSources: [],
});
expect(_.uniq(args.sources).sort()).to.deep.equals(
SELL_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
);
expect(args.liquidityProviderAddress).to.eql(registryAddress);
});
it('does not poll DEXes in `excludedSources`', async () => {
const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
let sourcesPolled: ERC20BridgeSource[] = [];
@@ -1341,41 +1261,28 @@ describe('MarketOperationUtils tests', () => {
});
it('is able to create a order from LiquidityProvider', async () => {
const registryAddress = randomAddress();
const liquidityProviderAddress = (DEFAULT_FILL_DATA[ERC20BridgeSource.LiquidityProvider] as any)
.poolAddress;
const xAsset = randomAddress();
const yAsset = randomAddress();
const toSell = fromTokenUnitAmount(10);
const [getSellQuotesParams, getSellQuotesFn] = callTradeOperationAndRetainLiquidityProviderParams(
createGetMultipleSellQuotesOperationFromRates,
{
[ERC20BridgeSource.LiquidityProvider]: createDecreasingRates(5),
},
);
const rates: RatesBySource = {};
rates[ERC20BridgeSource.LiquidityProvider] = [1, 1, 1, 1];
MOCK_SAMPLER.liquidityProviderRegistry[liquidityProviderAddress] = [MAKER_TOKEN, TAKER_TOKEN];
replaceSamplerOps({
getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT],
getSellQuotes: getSellQuotesFn,
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
});
const sampler = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
const sampler = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
const ordersAndReport = await sampler.getMarketSellOrdersAsync(
[
createOrder({
makerAssetData: assetDataUtils.encodeERC20AssetData(xAsset),
takerAssetData: assetDataUtils.encodeERC20AssetData(yAsset),
makerAssetData: assetDataUtils.encodeERC20AssetData(MAKER_TOKEN),
takerAssetData: assetDataUtils.encodeERC20AssetData(TAKER_TOKEN),
}),
],
Web3Wrapper.toBaseUnitAmount(10, 18),
FILL_AMOUNT,
{
excludedSources: SELL_SOURCES.concat(ERC20BridgeSource.Bancor),
includedSources: [ERC20BridgeSource.LiquidityProvider],
excludedSources: [],
numSamples: 4,
bridgeSlippage: 0,
},
@@ -1390,9 +1297,7 @@ describe('MarketOperationUtils tests', () => {
) as ERC20BridgeAssetData;
expect(decodedAssetData.assetProxyId).to.eql(AssetProxyId.ERC20Bridge);
expect(decodedAssetData.bridgeAddress).to.eql(liquidityProviderAddress);
expect(result[0].takerAssetAmount).to.bignumber.eql(toSell);
expect(getSellQuotesParams.sources).contains(ERC20BridgeSource.LiquidityProvider);
expect(getSellQuotesParams.liquidityProviderAddress).is.eql(registryAddress);
expect(result[0].takerAssetAmount).to.bignumber.eql(FILL_AMOUNT);
});
it('factors in exchange proxy gas overhead', async () => {
@@ -1403,20 +1308,16 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Uniswap]: [1, 1, 1, 1],
[ERC20BridgeSource.LiquidityProvider]: [0.9999, 0.9999, 0.9999, 0.9999],
};
MOCK_SAMPLER.liquidityProviderRegistry[randomAddress()] = [MAKER_TOKEN, TAKER_TOKEN];
replaceSamplerOps({
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_MAKER_RATE),
});
const optimizer = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
randomAddress(), // liquidity provider registry
);
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
const gasPrice = 100e9; // 100 gwei
const exchangeProxyOverhead = (sourceFlags: number) =>
sourceFlags === SOURCE_FLAGS.LiquidityProvider
? new BigNumber(3e4).times(gasPrice)
? constants.ZERO_AMOUNT
: new BigNumber(1.3e5).times(gasPrice);
const improvedOrdersResponse = await optimizer.getMarketSellOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -1424,12 +1325,12 @@ describe('MarketOperationUtils tests', () => {
{
...DEFAULT_OPTS,
numSamples: 4,
excludedSources: [
...(DEFAULT_OPTS.excludedSources as ERC20BridgeSource[]),
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Bancor,
includedSources: [
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.LiquidityProvider,
],
excludedSources: [],
exchangeProxyOverhead,
},
);
@@ -1529,54 +1430,6 @@ describe('MarketOperationUtils tests', () => {
expect(_.uniq(sourcesPolled).sort()).to.deep.equals(BUY_SOURCES.sort());
});
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
const [args, fn] = callTradeOperationAndRetainLiquidityProviderParams(
createGetMultipleBuyQuotesOperationFromRates,
DEFAULT_RATES,
);
replaceSamplerOps({
getBuyQuotes: fn,
getTwoHopBuyQuotes: (sources: ERC20BridgeSource[], ..._args: any[]) => {
if (sources.length !== 0) {
args.sources.push(ERC20BridgeSource.MultiHop);
args.sources.push(...sources);
}
return DEFAULT_OPS.getTwoHopBuyQuotes(..._args);
},
getBalancerBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Balancer);
return DEFAULT_OPS.getBalancerBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
getCreamBuyQuotesOffChainAsync: (
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
) => {
args.sources = args.sources.concat(ERC20BridgeSource.Cream);
return DEFAULT_OPS.getCreamBuyQuotesOffChainAsync(makerToken, takerToken, makerFillAmounts);
},
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
await newMarketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
excludedSources: [],
});
expect(_.uniq(args.sources).sort()).to.deep.eq(
BUY_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
);
expect(args.liquidityProviderAddress).to.eql(registryAddress);
});
it('does not poll DEXes in `excludedSources`', async () => {
const excludedSources = [ERC20BridgeSource.Uniswap, ERC20BridgeSource.Eth2Dai];
let sourcesPolled: ERC20BridgeSource[] = [];
@@ -1874,20 +1727,16 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.Uniswap]: [1, 1, 1, 1],
[ERC20BridgeSource.LiquidityProvider]: [0.9999, 0.9999, 0.9999, 0.9999],
};
MOCK_SAMPLER.liquidityProviderRegistry[randomAddress()] = [MAKER_TOKEN, TAKER_TOKEN];
replaceSamplerOps({
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(rates),
getMedianSellRate: createGetMedianSellRate(ETH_TO_TAKER_RATE),
});
const optimizer = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
randomAddress(), // liquidity provider registry
);
const optimizer = new MarketOperationUtils(MOCK_SAMPLER, contractAddresses, ORDER_DOMAIN);
const gasPrice = 100e9; // 100 gwei
const exchangeProxyOverhead = (sourceFlags: number) =>
sourceFlags === SOURCE_FLAGS.LiquidityProvider
? new BigNumber(3e4).times(gasPrice)
? constants.ZERO_AMOUNT
: new BigNumber(1.3e5).times(gasPrice);
const improvedOrdersResponse = await optimizer.getMarketBuyOrdersAsync(
createOrdersFromSellRates(FILL_AMOUNT, rates[ERC20BridgeSource.Native]),
@@ -1895,11 +1744,12 @@ describe('MarketOperationUtils tests', () => {
{
...DEFAULT_OPTS,
numSamples: 4,
excludedSources: [
...(DEFAULT_OPTS.excludedSources as ERC20BridgeSource[]),
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
includedSources: [
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.LiquidityProvider,
],
excludedSources: [],
exchangeProxyOverhead,
},
);