MultiBridge support in AssetSwapper

This commit is contained in:
Michael Zhu 2020-06-11 11:10:34 -07:00
parent 0fbbabe208
commit 44262bf747
22 changed files with 358 additions and 40 deletions

View File

@ -21,6 +21,10 @@
{
"note": "Add UniswapV2",
"pr": 2595
},
{
"note": "Sample from MultiBridge",
"pr": 2593
}
]
},

View File

@ -35,6 +35,7 @@ import "./ICurve.sol";
import "./ILiquidityProvider.sol";
import "./ILiquidityProviderRegistry.sol";
import "./IUniswapV2Router01.sol";
import "./IMultiBridge.sol";
contract ERC20BridgeSampler is
@ -55,7 +56,7 @@ contract ERC20BridgeSampler is
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
uint256 constant internal CURVE_CALL_GAS = 600e3; // 600k
/// @dev Default gas limit for liquidity provider calls.
uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k
uint256 constant internal DEFAULT_CALL_GAS = 400e3; // 400k
/// @dev The Kyber Uniswap Reserve address
address constant internal KYBER_UNIWAP_RESERVE = 0x31E085Afd48a1d6e51Cc193153d625e8f0514C7F;
/// @dev The Kyber Eth2Dai Reserve address
@ -593,6 +594,56 @@ contract ERC20BridgeSampler is
}
}
/// @dev Sample sell quotes from MultiBridge.
/// @param multibridge Address of the MultiBridge contract.
/// @param takerToken Address of the taker token (what to sell).
/// @param intermediateToken The address of the intermediate token to
/// use in an indirect route.
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMultiBridge(
address multibridge,
address takerToken,
address intermediateToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts)
{
// Initialize array of maker token amounts.
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// If no address provided, return all zeros.
if (multibridge == address(0)) {
return makerTokenAmounts;
}
for (uint256 i = 0; i < numSamples; i++) {
(bool didSucceed, bytes memory resultData) =
multibridge.staticcall.gas(DEFAULT_CALL_GAS)(
abi.encodeWithSelector(
IMultiBridge(0).getSellQuote.selector,
takerToken,
intermediateToken,
makerToken,
takerTokenAmounts[i]
));
uint256 buyAmount = 0;
if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256));
} else {
// Exit early if the amount is too high for the liquidity provider to serve
break;
}
makerTokenAmounts[i] = buyAmount;
}
}
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param takerToken Address of the taker token (what to sell).

View File

@ -206,6 +206,26 @@ interface IERC20BridgeSampler {
view
returns (uint256[] memory makerTokenAmounts);
/// @dev Sample sell quotes from MultiBridge.
/// @param multibridge Address of the MultiBridge contract.
/// @param takerToken Address of the taker token (what to sell).
/// @param intermediateToken The address of the intermediate token to
/// use in an indirect route.
/// @param makerToken Address of the maker token (what to buy).
/// @param takerTokenAmounts Taker token sell amount for each sample.
/// @return makerTokenAmounts Maker amounts bought at each taker token
/// amount.
function sampleSellsFromMultiBridge(
address multibridge,
address takerToken,
address intermediateToken,
address makerToken,
uint256[] calldata takerTokenAmounts
)
external
view
returns (uint256[] memory makerTokenAmounts);
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param takerToken Address of the taker token (what to sell).

View File

@ -0,0 +1,58 @@
/*
Copyright 2020 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
interface IMultiBridge {
/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
/// @param tokenAddress The address of the ERC20 token to transfer.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
/// @return success The magic bytes `0xdc1600f3` if successful.
function bridgeTransferFrom(
address tokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4 success);
/// @dev Quotes the amount of `makerToken` that would be obtained by
/// selling `sellAmount` of `takerToken`.
/// @param takerToken Address of the taker token (what to sell).
/// @param intermediateToken The address of the intermediate token to
/// use in an indirect route.
/// @param makerToken Address of the maker token (what to buy).
/// @param sellAmount Amount of `takerToken` to sell.
/// @return makerTokenAmount Amount of `makerToken` that would be obtained.
function getSellQuote(
address takerToken,
address intermediateToken,
address makerToken,
uint256 sellAmount
)
external
view
returns (uint256 makerTokenAmount);
}

View File

@ -38,7 +38,7 @@
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IKyberNetworkProxy|ILiquidityProvider|ILiquidityProviderRegistry|IUniswapExchangeQuotes|IUniswapV2Router01|TestERC20BridgeSampler).json"
"abis": "./test/generated-artifacts/@(DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|IKyberNetworkProxy|ILiquidityProvider|ILiquidityProviderRegistry|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|TestERC20BridgeSampler).json"
},
"repository": {
"type": "git",

View File

@ -16,6 +16,7 @@ import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json';
import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json';
import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquidityProviderRegistry.json';
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
@ -31,6 +32,7 @@ export const artifacts = {
IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
IMultiBridge: IMultiBridge as ContractArtifact,
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,

View File

@ -14,6 +14,7 @@ export * from '../test/generated-wrappers/i_kyber_network';
export * from '../test/generated-wrappers/i_kyber_network_proxy';
export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_registry';
export * from '../test/generated-wrappers/i_multi_bridge';
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';

View File

@ -20,6 +20,7 @@
"test/generated-artifacts/IKyberNetworkProxy.json",
"test/generated-artifacts/ILiquidityProvider.json",
"test/generated-artifacts/ILiquidityProviderRegistry.json",
"test/generated-artifacts/IMultiBridge.json",
"test/generated-artifacts/IUniswapExchangeQuotes.json",
"test/generated-artifacts/IUniswapV2Router01.json",
"test/generated-artifacts/TestERC20BridgeSampler.json"

View File

@ -81,6 +81,10 @@
{
"note": "Add support for Uniswap V2",
"pr": 2599
},
{
"note": "Add support for MultiBridge",
"pr": 2593
}
]
},

View File

@ -233,6 +233,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
contractAddresses?: ContractAddresses;
samplerGasLimit?: number;
liquidityProviderRegistryAddress?: string;
multiBridgeAddress?: string;
rfqt?: {
takerApiKeyWhitelist: string[];
makerAssetOfferings: RfqtMakerAssetOfferings;

View File

@ -158,10 +158,16 @@ function dexQuotesToPaths(
}
function sourceToFillFlags(source: ERC20BridgeSource): number {
if (source === ERC20BridgeSource.Kyber) {
return FillFlags.Kyber;
switch (source) {
case ERC20BridgeSource.Uniswap:
return FillFlags.ConflictsWithMultiBridge;
case ERC20BridgeSource.LiquidityProvider:
return FillFlags.ConflictsWithMultiBridge;
case ERC20BridgeSource.MultiBridge:
return FillFlags.MultiBridge;
default:
return 0;
}
return 0;
}
export function getPathSize(path: Fill[], targetInput: BigNumber = POSITIVE_INF): [BigNumber, BigNumber] {
@ -217,8 +223,8 @@ export function isValidPath(path: Fill[], skipDuplicateCheck: boolean = false):
}
flags |= path[i].flags;
}
const conflictFlags = FillFlags.Kyber | FillFlags.ConflictsWithKyber;
return (flags & conflictFlags) !== conflictFlags;
const multiBridgeConflict = FillFlags.MultiBridge | FillFlags.ConflictsWithMultiBridge;
return (flags & multiBridgeConflict) !== multiBridgeConflict;
}
export function clipPathToInput(path: Fill[], targetInput: BigNumber = POSITIVE_INF): Fill[] {

View File

@ -47,6 +47,7 @@ async function getRfqtIndicativeQuotesAsync(
export class MarketOperationUtils {
private readonly _wethAddress: string;
private readonly _multiBridge: string;
constructor(
private readonly _sampler: DexOrderSampler,
@ -55,6 +56,7 @@ export class MarketOperationUtils {
private readonly _liquidityProviderRegistry: string = NULL_ADDRESS,
) {
this._wethAddress = contractAddresses.etherToken.toLowerCase();
this._multiBridge = contractAddresses.multiBridge.toLowerCase();
}
/**
@ -75,6 +77,11 @@ export class MarketOperationUtils {
}
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
const optionalSources = (this._liquidityProviderRegistry !== NULL_ADDRESS
? [ERC20BridgeSource.LiquidityProvider]
: []
).concat(this._multiBridge !== NULL_ADDRESS ? [ERC20BridgeSource.MultiBridge] : []);
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
// Get native order fillable amounts.
@ -87,25 +94,23 @@ export class MarketOperationUtils {
),
// Get ETH -> maker token price.
DexOrderSampler.ops.getMedianSellRate(
difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
difference(FEE_QUOTE_SOURCES.concat(optionalSources), _opts.excludedSources),
makerToken,
this._wethAddress,
ONE_ETHER,
this._wethAddress,
this._liquidityProviderRegistry,
this._multiBridge,
),
// Get sell quotes for taker -> maker.
DexOrderSampler.ops.getSellQuotes(
difference(SELL_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
difference(SELL_SOURCES.concat(optionalSources), _opts.excludedSources),
makerToken,
takerToken,
getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
this._wethAddress,
this._liquidityProviderRegistry,
this._multiBridge,
),
);
const rfqtPromise = getRfqtIndicativeQuotesAsync(
@ -125,6 +130,7 @@ export class MarketOperationUtils {
dexQuotes,
rfqtIndicativeQuotes,
liquidityProviderAddress,
multiBridgeAddress: this._multiBridge,
inputToken: takerToken,
outputToken: makerToken,
side: MarketOperation.Sell,
@ -157,6 +163,10 @@ export class MarketOperationUtils {
}
const _opts = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
const [makerToken, takerToken] = getNativeOrderTokens(nativeOrders[0]);
const optionalSources = (this._liquidityProviderRegistry !== NULL_ADDRESS
? [ERC20BridgeSource.LiquidityProvider]
: []
).concat(this._multiBridge !== NULL_ADDRESS ? [ERC20BridgeSource.MultiBridge] : []);
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
// Get native order fillable amounts.
@ -169,19 +179,21 @@ export class MarketOperationUtils {
),
// Get ETH -> taker token price.
DexOrderSampler.ops.getMedianSellRate(
difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
difference(FEE_QUOTE_SOURCES.concat(optionalSources), _opts.excludedSources),
takerToken,
this._wethAddress,
ONE_ETHER,
this._wethAddress,
this._liquidityProviderRegistry,
this._multiBridge,
),
// Get buy quotes for taker -> maker.
DexOrderSampler.ops.getBuyQuotes(
difference(BUY_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
difference(
BUY_SOURCES.concat(
this._liquidityProviderRegistry !== NULL_ADDRESS ? [ERC20BridgeSource.LiquidityProvider] : [],
),
_opts.excludedSources,
),
makerToken,
takerToken,
@ -208,6 +220,7 @@ export class MarketOperationUtils {
dexQuotes,
rfqtIndicativeQuotes,
liquidityProviderAddress,
multiBridgeAddress: this._multiBridge,
inputToken: makerToken,
outputToken: takerToken,
side: MarketOperation.Buy,
@ -324,6 +337,7 @@ export class MarketOperationUtils {
allowFallback?: boolean;
shouldBatchBridgeOrders?: boolean;
liquidityProviderAddress?: string;
multiBridgeAddress?: string;
}): OptimizedMarketOrder[] {
const { inputToken, outputToken, side, inputAmount } = opts;
const maxFallbackSlippage = opts.maxFallbackSlippage || 0;
@ -385,16 +399,10 @@ export class MarketOperationUtils {
contractAddresses: this.contractAddresses,
bridgeSlippage: opts.bridgeSlippage || 0,
liquidityProviderAddress: opts.liquidityProviderAddress,
multiBridgeAddress: opts.multiBridgeAddress,
shouldBatchBridgeOrders: !!opts.shouldBatchBridgeOrders,
});
}
private _liquidityProviderSourceIfAvailable(excludedSources: ERC20BridgeSource[]): ERC20BridgeSource[] {
return this._liquidityProviderRegistry !== NULL_ADDRESS &&
!excludedSources.includes(ERC20BridgeSource.LiquidityProvider)
? [ERC20BridgeSource.LiquidityProvider]
: [];
}
}
// tslint:disable: max-file-line-count

View File

@ -0,0 +1,20 @@
import { NULL_ADDRESS } from './constants';
// tslint:disable completed-docs
export const TOKENS = {
WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
DAI: '0x6b175474e89094c44da98b954eedeac495271d0f',
USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
MKR: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
};
export function getMultiBridgeIntermediateToken(takerToken: string, makerToken: string): string {
let intermediateToken = NULL_ADDRESS;
if (takerToken !== TOKENS.WETH && makerToken !== TOKENS.WETH) {
intermediateToken = TOKENS.WETH;
} else if (takerToken === TOKENS.USDC || makerToken === TOKENS.USDC) {
intermediateToken = TOKENS.DAI;
}
return intermediateToken;
}

View File

@ -17,6 +17,7 @@ import {
ZERO_AMOUNT,
} from './constants';
import { collapsePath } from './fills';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
import {
AggregationError,
CollapsedFill,
@ -141,6 +142,7 @@ export interface CreateOrderFromPathOpts {
bridgeSlippage: number;
shouldBatchBridgeOrders: boolean;
liquidityProviderAddress?: string;
multiBridgeAddress?: string;
}
// Convert sell fills into orders.
@ -195,6 +197,11 @@ function getBridgeAddressFromSource(source: ERC20BridgeSource, opts: CreateOrder
throw new Error('Cannot create a LiquidityProvider order without a LiquidityProvider pool address.');
}
return opts.liquidityProviderAddress;
case ERC20BridgeSource.MultiBridge:
if (opts.multiBridgeAddress === undefined) {
throw new Error('Cannot create a MultiBridge order without a MultiBridge address.');
}
return opts.multiBridgeAddress;
default:
break;
}
@ -242,6 +249,13 @@ function createBridgeOrder(fill: CollapsedFill, opts: CreateOrderFromPathOpts):
createUniswapV2BridgeData([makerToken, opts.contractAddresses.etherToken, takerToken]),
);
break;
case ERC20BridgeSource.MultiBridge:
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createMultiBridgeData(takerToken, makerToken),
);
break;
default:
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
@ -315,6 +329,15 @@ function createBridgeData(tokenAddress: string): string {
return encoder.encode({ tokenAddress });
}
function createMultiBridgeData(takerToken: string, makerToken: string): string {
const intermediateToken = getMultiBridgeIntermediateToken(takerToken, makerToken);
const encoder = AbiEncoder.create([
{ name: 'takerToken', type: 'address' },
{ name: 'intermediateToken', type: 'address' },
]);
return encoder.encode({ takerToken, intermediateToken });
}
function createCurveBridgeData(
curveAddress: string,
fromTokenIdx: number,

View File

@ -2,6 +2,7 @@ import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..';
import { getCurveInfo, isCurveSource } from '../source_utils';
import { DEFAULT_FAKE_BUY_OPTS } from './constants';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
import { BatchedOperation, DexSample, FakeBuyOpts } from './types';
/**
@ -123,7 +124,7 @@ export const samplerOperations = {
};
},
getLiquidityProviderSellQuotes(
liquidityProviderRegistryAddress: string,
registryAddress: string,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
@ -131,9 +132,31 @@ export const samplerOperations = {
return {
encodeCall: contract => {
return contract
.sampleSellsFromLiquidityProviderRegistry(
liquidityProviderRegistryAddress,
.sampleSellsFromLiquidityProviderRegistry(registryAddress, takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>(
'sampleSellsFromLiquidityProviderRegistry',
callResults,
);
},
};
},
getMultiBridgeSellQuotes(
multiBridgeAddress: string,
makerToken: string,
intermediateToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromMultiBridge(
multiBridgeAddress,
takerToken,
intermediateToken,
makerToken,
takerFillAmounts,
)
@ -148,7 +171,7 @@ export const samplerOperations = {
};
},
getLiquidityProviderBuyQuotes(
liquidityProviderRegistryAddress: string,
registryAddress: string,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
@ -158,7 +181,7 @@ export const samplerOperations = {
encodeCall: contract => {
return contract
.sampleBuysFromLiquidityProviderRegistry(
liquidityProviderRegistryAddress,
registryAddress,
takerToken,
makerToken,
makerFillAmounts,
@ -256,7 +279,8 @@ export const samplerOperations = {
takerToken: string,
takerFillAmount: BigNumber,
wethAddress: string,
liquidityProviderRegistryAddress?: string | undefined,
liquidityProviderRegistryAddress?: string,
multiBridgeAddress?: string,
): BatchedOperation<BigNumber> {
if (makerToken.toLowerCase() === takerToken.toLowerCase()) {
return samplerOperations.constant(new BigNumber(1));
@ -268,6 +292,7 @@ export const samplerOperations = {
[takerFillAmount],
wethAddress,
liquidityProviderRegistryAddress,
multiBridgeAddress,
);
return {
encodeCall: contract => {
@ -324,7 +349,8 @@ export const samplerOperations = {
takerToken: string,
takerFillAmounts: BigNumber[],
wethAddress: string,
liquidityProviderRegistryAddress?: string | undefined,
liquidityProviderRegistryAddress?: string,
multiBridgeAddress?: string,
): BatchedOperation<DexSample[][]> {
const subOps = sources
.map(source => {
@ -367,6 +393,18 @@ export const samplerOperations = {
takerToken,
takerFillAmounts,
);
} else if (source === ERC20BridgeSource.MultiBridge) {
if (multiBridgeAddress === undefined) {
throw new Error('Cannot sample liquidity from MultiBridge if an address is not provided.');
}
const intermediateToken = getMultiBridgeIntermediateToken(takerToken, makerToken);
batchedOperation = samplerOperations.getMultiBridgeSellQuotes(
multiBridgeAddress,
makerToken,
intermediateToken,
takerToken,
takerFillAmounts,
);
} else {
throw new Error(`Unsupported sell sample source: ${source}`);
}
@ -404,7 +442,7 @@ export const samplerOperations = {
takerToken: string,
makerFillAmounts: BigNumber[],
wethAddress: string,
liquidityProviderRegistryAddress?: string | undefined,
liquidityProviderRegistryAddress?: string,
fakeBuyOpts: FakeBuyOpts = DEFAULT_FAKE_BUY_OPTS,
): BatchedOperation<DexSample[][]> {
const subOps = sources

View File

@ -38,6 +38,7 @@ export enum ERC20BridgeSource {
CurveUsdcDaiUsdtBusd = 'Curve_USDC_DAI_USDT_BUSD',
CurveUsdcDaiUsdtSusd = 'Curve_USDC_DAI_USDT_SUSD',
LiquidityProvider = 'LiquidityProvider',
MultiBridge = 'MultiBridge',
}
// Internal `fillData` field for `Fill` objects.
@ -67,6 +68,8 @@ export interface DexSample {
export enum FillFlags {
ConflictsWithKyber = 0x1,
Kyber = 0x2,
ConflictsWithMultiBridge = 0x4,
MultiBridge = 0x8,
}
/**

View File

@ -212,6 +212,48 @@ describe('DexSampler tests', () => {
]);
});
it('getMultiBridgeSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
const multiBridge = randomAddress();
const sampler = new MockSamplerContract({
sampleSellsFromMultiBridge: (
multiBridgeAddress,
takerToken,
_intermediateToken,
makerToken,
_fillAmounts,
) => {
expect(multiBridgeAddress).to.eq(multiBridge);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [toBaseUnitAmount(1001)];
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
[ERC20BridgeSource.MultiBridge],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
randomAddress(),
randomAddress(),
multiBridge,
),
);
expect(result).to.deep.equal([
[
{
source: 'MultiBridge',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
},
],
]);
});
it('getEth2DaiSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();

View File

@ -30,7 +30,7 @@ import { DexSample, ERC20BridgeSource, NativeFillData } from '../src/utils/marke
// tslint:disable: custom-no-magic-numbers
describe('MarketOperationUtils tests', () => {
const CHAIN_ID = 1;
const contractAddresses = getContractAddressesForChainOrThrow(CHAIN_ID);
const contractAddresses = { ...getContractAddressesForChainOrThrow(CHAIN_ID), multiBridge: NULL_ADDRESS };
const ETH2DAI_BRIDGE_ADDRESS = contractAddresses.eth2DaiBridge;
const KYBER_BRIDGE_ADDRESS = contractAddresses.kyberBridge;
const UNISWAP_BRIDGE_ADDRESS = contractAddresses.uniswapBridge;
@ -259,7 +259,9 @@ describe('MarketOperationUtils tests', () => {
const fn = (registryAddress: string, takerToken: string, makerToken: string): string => {
callArgs.makerToken = makerToken;
callArgs.takerToken = takerToken;
callArgs.registryAddress = registryAddress;
if (registryAddress !== constants.NULL_ADDRESS) {
callArgs.registryAddress = registryAddress;
}
return liquidityProviderAddress;
};
return [callArgs, fn];
@ -294,6 +296,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.CurveUsdcDaiUsdtBusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtSusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.MultiBridge]: _.times(NUM_SAMPLES, () => 0),
};
const DEFAULT_OPS = {

View File

@ -29,6 +29,13 @@ export type SampleSellsLPHandler = (
takerTokenAmounts: BigNumber[],
) => SampleResults;
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
export type SampleSellsMBHandler = (
multiBridgeAddress: string,
takerToken: string,
intermediateToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
) => SampleResults;
const DUMMY_PROVIDER = {
sendAsync: (...args: any[]): any => {
@ -41,6 +48,7 @@ interface Handlers {
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
sampleSellsFromKyberNetwork: SampleSellsHandler;
sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler;
sampleSellsFromMultiBridge: SampleSellsMBHandler;
sampleSellsFromEth2Dai: SampleSellsHandler;
sampleSellsFromUniswap: SampleSellsHandler;
sampleSellsFromUniswapV2: SampleSellsMultihopHandler;
@ -159,6 +167,24 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
);
}
public sampleSellsFromMultiBridge(
multiBridgeAddress: string,
takerToken: string,
intermediateToken: string,
makerToken: string,
takerAssetAmounts: BigNumber[],
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
return this._wrapCall(
super.sampleSellsFromMultiBridge,
this._handlers.sampleSellsFromMultiBridge,
multiBridgeAddress,
takerToken,
intermediateToken,
makerToken,
takerAssetAmounts,
);
}
public sampleBuysFromEth2Dai(
takerToken: string,
makerToken: string,

View File

@ -31,7 +31,8 @@
"chainlinkStopLimit": "0xeb27220f95f364e1d9531992c48613f231839f53",
"curveBridge": "0x6dc7950423ada9f56fb2c93a23edb787f1e29088",
"maximumGasPrice": "0xe2bfd35306495d11e3c9db0d8de390cda24563cf",
"dexForwarderBridge": "0x5591360f8c7640fea5771c9682d6b5ecb776e1f8"
"dexForwarderBridge": "0x5591360f8c7640fea5771c9682d6b5ecb776e1f8",
"multiBridge": "0xc03117a8c9bde203f70aa911cb64a7a0df5ba1e1"
},
"3": {
"erc20Proxy": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa",
@ -65,7 +66,8 @@
"chainlinkStopLimit": "0x67a094cf028221ffdd93fc658f963151d05e2a74",
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a",
"dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1"
"dexForwarderBridge": "0x3be8e59038d8c4e8d8776ca40ef2f024bad95ad1",
"multiBridge": "0x0000000000000000000000000000000000000000"
},
"4": {
"exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
@ -99,7 +101,8 @@
"chainlinkStopLimit": "0x407b4128e9ecad8769b2332312a9f655cb9f5f3a",
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x47697b44bd89051e93b4d5857ba8e024800a74ac",
"dexForwarderBridge": "0x0000000000000000000000000000000000000000"
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
"multiBridge": "0x0000000000000000000000000000000000000000"
},
"42": {
"erc20Proxy": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e",
@ -133,7 +136,8 @@
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
"curveBridge": "0x90c62c91a9f655f4f739e6cee85c84f9ccf47323",
"maximumGasPrice": "0x67a094cf028221ffdd93fc658f963151d05e2a74",
"dexForwarderBridge": "0x6cce442a48ab07635462a40594054f34f44195ff"
"dexForwarderBridge": "0x6cce442a48ab07635462a40594054f34f44195ff",
"multiBridge": "0x0000000000000000000000000000000000000000"
},
"1337": {
"erc20Proxy": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
@ -167,6 +171,7 @@
"chainlinkStopLimit": "0x0000000000000000000000000000000000000000",
"curveBridge": "0x0000000000000000000000000000000000000000",
"maximumGasPrice": "0x0000000000000000000000000000000000000000",
"dexForwarderBridge": "0x0000000000000000000000000000000000000000"
"dexForwarderBridge": "0x0000000000000000000000000000000000000000",
"multiBridge": "0x0000000000000000000000000000000000000000"
}
}

View File

@ -33,6 +33,7 @@ export interface ContractAddresses {
chainlinkStopLimit: string;
maximumGasPrice: string;
dexForwarderBridge: string;
multiBridge: string;
}
export enum ChainId {

View File

@ -323,6 +323,7 @@ export async function runMigrationsAsync(
chainlinkStopLimit: constants.NULL_ADDRESS,
maximumGasPrice: constants.NULL_ADDRESS,
dexForwarderBridge: constants.NULL_ADDRESS,
multiBridge: constants.NULL_ADDRESS,
};
return contractAddresses;
}