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

@@ -1,4 +1,17 @@
[
{
"version": "5.1.0",
"changes": [
{
"note": "Add support for LiquidityProvider feature in the swap quote consumer",
"pr": 16
},
{
"note": "Remove support for MultiBridge 😞",
"pr": 16
}
]
},
{
"timestamp": 1604620645,
"version": "5.0.3",

View File

@@ -20,8 +20,7 @@ pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
import "./interfaces/ILiquidityProvider.sol";
import "./interfaces/ILiquidityProviderRegistry.sol";
import "@0x/contracts-zero-ex/contracts/src/vendor/ILiquidityProvider.sol";
import "./ApproximateBuys.sol";
import "./SamplerUtils.sol";
@@ -34,37 +33,26 @@ contract LiquidityProviderSampler is
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param providerAddress Address of the liquidity provider.
/// @param takerToken Address of the taker token (what to sell).
/// @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 sampleSellsFromLiquidityProviderRegistry(
address registryAddress,
function sampleSellsFromLiquidityProvider(
address providerAddress,
address takerToken,
address makerToken,
uint256[] memory takerTokenAmounts
)
public
view
returns (uint256[] memory makerTokenAmounts, address providerAddress)
returns (uint256[] memory makerTokenAmounts)
{
// Initialize array of maker token amounts.
uint256 numSamples = takerTokenAmounts.length;
makerTokenAmounts = new uint256[](numSamples);
// Query registry for provider address.
providerAddress = _getLiquidityProviderFromRegistry(
registryAddress,
takerToken,
makerToken
);
// If provider doesn't exist, return all zeros.
if (providerAddress == address(0)) {
return (makerTokenAmounts, providerAddress);
}
for (uint256 i = 0; i < numSamples; i++) {
try
ILiquidityProvider(providerAddress).getSellQuote
@@ -81,68 +69,33 @@ contract LiquidityProviderSampler is
}
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
/// @param registryAddress Address of the liquidity provider registry contract.
/// @param providerAddress Address of the liquidity provider.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param makerTokenAmounts Maker token buy amount for each sample.
/// @return takerTokenAmounts Taker amounts sold at each maker token
/// amount.
function sampleBuysFromLiquidityProviderRegistry(
address registryAddress,
function sampleBuysFromLiquidityProvider(
address providerAddress,
address takerToken,
address makerToken,
uint256[] memory makerTokenAmounts
)
public
view
returns (uint256[] memory takerTokenAmounts, address providerAddress)
returns (uint256[] memory takerTokenAmounts)
{
providerAddress = _getLiquidityProviderFromRegistry(
registryAddress,
takerToken,
makerToken
);
takerTokenAmounts = _sampleApproximateBuys(
ApproximateBuyQuoteOpts({
makerTokenData: abi.encode(makerToken, registryAddress),
takerTokenData: abi.encode(takerToken, registryAddress),
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProviderRegistry
makerTokenData: abi.encode(makerToken, providerAddress),
takerTokenData: abi.encode(takerToken, providerAddress),
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProvider
}),
makerTokenAmounts
);
}
/// @dev Returns the address of a liquidity provider for the given market
/// (takerToken, makerToken), from a registry of liquidity providers.
/// Returns address(0) if no such provider exists in the registry.
/// @param takerToken Taker asset managed by liquidity provider.
/// @param makerToken Maker asset managed by liquidity provider.
/// @return providerAddress Address of the liquidity provider.
function _getLiquidityProviderFromRegistry(
address registryAddress,
address takerToken,
address makerToken
)
private
view
returns (address providerAddress)
{
if (registryAddress == address(0)) {
return address(0);
}
bytes memory callData = abi.encodeWithSelector(
ILiquidityProviderRegistry.getLiquidityProviderForMarket.selector,
takerToken,
makerToken
);
(bool didSucceed, bytes memory returnData) = registryAddress.staticcall(callData);
if (didSucceed && returnData.length == 32) {
return LibBytesV06.readAddress(returnData, 12);
}
}
function _sampleSellForApproximateBuyFromLiquidityProviderRegistry(
function _sampleSellForApproximateBuyFromLiquidityProvider(
bytes memory takerTokenData,
bytes memory makerTokenData,
uint256 sellAmount
@@ -151,15 +104,15 @@ contract LiquidityProviderSampler is
view
returns (uint256 buyAmount)
{
(address takerToken, address plpRegistryAddress) =
(address takerToken, address providerAddress) =
abi.decode(takerTokenData, (address, address));
(address makerToken) =
abi.decode(makerTokenData, (address));
try
this.sampleSellsFromLiquidityProviderRegistry
this.sampleSellsFromLiquidityProvider
{gas: DEFAULT_CALL_GAS}
(plpRegistryAddress, takerToken, makerToken, _toSingleValueArray(sellAmount))
returns (uint256[] memory amounts, address)
(providerAddress, takerToken, makerToken, _toSingleValueArray(sellAmount))
returns (uint256[] memory amounts)
{
return amounts[0];
} catch (bytes memory) {

View File

@@ -1,70 +0,0 @@
/*
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.6;
interface ILiquidityProvider {
/// @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 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 makerToken,
uint256 sellAmount
)
external
view
returns (uint256 makerTokenAmount);
/// @dev Quotes the amount of `takerToken` that would need to be sold in
/// order to obtain `buyAmount` of `makerToken`.
/// @param takerToken Address of the taker token (what to sell).
/// @param makerToken Address of the maker token (what to buy).
/// @param buyAmount Amount of `makerToken` to buy.
/// @return takerTokenAmount Amount of `takerToken` that would need to be sold.
function getBuyQuote(
address takerToken,
address makerToken,
uint256 buyAmount
)
external
view
returns (uint256 takerTokenAmount);
}

View File

@@ -1,36 +0,0 @@
/*
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.6;
interface ILiquidityProviderRegistry {
/// @dev Returns the address of a liquidity provider for the given market
/// (takerToken, makerToken), reverting if the pool does not exist.
/// @param takerToken Taker asset managed by liquidity provider.
/// @param makerToken Maker asset managed by liquidity provider.
/// @return providerAddress Address of the liquidity provider.
function getLiquidityProviderForMarket(
address takerToken,
address makerToken
)
external
view
returns (address providerAddress);
}

View File

@@ -1,44 +0,0 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
contract DummyLiquidityProviderRegistry
{
address private constant NULL_ADDRESS = address(0x0);
mapping (address => mapping (address => address)) internal _gAddressBook;
/// @dev Sets address of pool for a market given market (xAsset, yAsset).
/// @param xToken First asset managed by pool.
/// @param yToken Second asset managed by pool.
/// @param poolAddress Address of pool.
function setLiquidityProviderForMarket(
address xToken,
address yToken,
address poolAddress
)
external
{
_gAddressBook[xToken][yToken] = poolAddress;
_gAddressBook[yToken][xToken] = poolAddress;
}
/// @dev Returns the address of pool for a market given market (xAsset, yAsset), or reverts if pool does not exist.
/// @param xToken First asset managed by pool.
/// @param yToken Second asset managed by pool.
/// @return poolAddress Address of pool.
function getLiquidityProviderForMarket(
address xToken,
address yToken
)
external
view
returns (address poolAddress)
{
poolAddress = _gAddressBook[xToken][yToken];
require(
poolAddress != NULL_ADDRESS,
"Registry/MARKET_PAIR_NOT_SET"
);
}
}

View File

@@ -36,9 +36,9 @@
"publish:private": "yarn build && gitpkg publish"
},
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
"publicInterfaceContracts": "ERC20BridgeSampler",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DODOSampler|DeploymentConstants|DummyLiquidityProvider|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SushiSwapSampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json",
"postpublish": {
"assets": []
}
@@ -94,6 +94,7 @@
"@0x/contracts-gen": "^2.0.18",
"@0x/contracts-test-utils": "^5.3.10",
"@0x/contracts-utils": "^4.5.7",
"@0x/contracts-zero-ex": "^0.8.0",
"@0x/mesh-rpc-client": "^9.4.2",
"@0x/migrations": "^6.4.7",
"@0x/sol-compiler": "^4.2.7",

View File

@@ -5,15 +5,5 @@
*/
import { ContractArtifact } from 'ethereum-types';
import * as DummyLiquidityProvider from '../generated-artifacts/DummyLiquidityProvider.json';
import * as DummyLiquidityProviderRegistry from '../generated-artifacts/DummyLiquidityProviderRegistry.json';
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
import * as ILiquidityProvider from '../generated-artifacts/ILiquidityProvider.json';
import * as ILiquidityProviderRegistry from '../generated-artifacts/ILiquidityProviderRegistry.json';
export const artifacts = {
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
};
export const artifacts = { ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact };

View File

@@ -147,10 +147,10 @@ export {
GetMarketOrdersRfqtOpts,
KyberFillData,
LiquidityProviderFillData,
LiquidityProviderRegistry,
MarketDepth,
MarketDepthSide,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
NativeCollapsedFill,
NativeFillData,

View File

@@ -34,7 +34,7 @@ import { getSwapMinBuyAmount } from './utils';
// tslint:disable-next-line:custom-no-magic-numbers
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
const { NULL_ADDRESS, NULL_BYTES, ZERO_AMOUNT } = constants;
export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
public readonly provider: ZeroExProvider;
@@ -96,9 +96,16 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
const buyToken = getTokenFromAssetData(quote.makerAssetData);
const sellAmount = quote.worstCaseQuoteInfo.totalTakerAssetAmount;
let minBuyAmount = getSwapMinBuyAmount(quote);
let ethAmount = quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
if (isFromETH) {
ethAmount = ethAmount.plus(sellAmount);
}
const { buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
// VIP routes.
if (isDirectUniswapCompatible(quote, optsWithDefaults)) {
if (
isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap])
) {
const source = quote.orders[0].fills[0].source;
const fillData = quote.orders[0].fills[0].fillData as UniswapV2FillData;
return {
@@ -124,6 +131,26 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
};
}
if (isDirectSwapCompatible(quote, optsWithDefaults, [ERC20BridgeSource.LiquidityProvider])) {
const target = quote.orders[0].makerAddress;
return {
calldataHexString: this._exchangeProxy
.sellToLiquidityProvider(
isFromETH ? ETH_TOKEN_ADDRESS : sellToken,
isToETH ? ETH_TOKEN_ADDRESS : buyToken,
target,
NULL_ADDRESS,
sellAmount,
minBuyAmount,
NULL_BYTES,
)
.getABIEncodedTransactionData(),
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
toAddress: this._exchangeProxy.address,
allowanceTarget: this.contractAddresses.exchangeProxyAllowanceTarget,
};
}
// Build up the transforms.
const transforms = [];
if (isFromETH) {
@@ -198,8 +225,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
}
// This transformer pays affiliate fees.
const { buyTokenFeeAmount, sellTokenFeeAmount, recipient: feeRecipient } = affiliateFee;
if (buyTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
transforms.push({
deploymentNonce: this.transformerNonces.affiliateFeeTransformer,
@@ -216,7 +241,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
// Adjust the minimum buy amount by the fee.
minBuyAmount = BigNumber.max(0, minBuyAmount.minus(buyTokenFeeAmount));
}
if (sellTokenFeeAmount.isGreaterThan(0) && feeRecipient !== NULL_ADDRESS) {
throw new Error('Affiliate fees denominated in sell token are not yet supported');
}
@@ -240,11 +264,6 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
)
.getABIEncodedTransactionData();
let ethAmount = quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
if (isFromETH) {
ethAmount = ethAmount.plus(sellAmount);
}
return {
calldataHexString,
ethAmount,
@@ -266,7 +285,11 @@ function isBuyQuote(quote: SwapQuote): quote is MarketBuySwapQuote {
return quote.type === MarketOperation.Buy;
}
function isDirectUniswapCompatible(quote: SwapQuote, opts: ExchangeProxyContractOpts): boolean {
function isDirectSwapCompatible(
quote: SwapQuote,
opts: ExchangeProxyContractOpts,
directSources: ERC20BridgeSource[],
): boolean {
// Must not be a mtx.
if (opts.isMetaTransaction) {
return false;
@@ -285,8 +308,7 @@ function isDirectUniswapCompatible(quote: SwapQuote, opts: ExchangeProxyContract
return false;
}
const fill = order.fills[0];
// And that fill must be uniswap v2 or sushiswap.
if (![ERC20BridgeSource.UniswapV2, ERC20BridgeSource.SushiSwap].includes(fill.source)) {
if (!directSources.includes(fill.source)) {
return false;
}
return true;

View File

@@ -5,7 +5,7 @@ import { MarketOperation, SwapQuote } from '../types';
import { ERC20BridgeSource } from '../utils/market_operation_utils/types';
/**
* Compute the mminimum buy token amount for market operations by inferring
* Compute the minimum buy token amount for market operations by inferring
* the slippage from the orders in a quote. We cannot rely on
* `worstCaseQuoteInfo.makerAssetAmount` because that does not stop at
* maximum slippage.

View File

@@ -165,8 +165,9 @@ export class SwapQuoter {
expiryBufferMs,
permittedOrderFeeTypes,
samplerGasLimit,
liquidityProviderRegistryAddress,
rfqt,
tokenAdjacencyGraph,
liquidityProviderRegistry,
} = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isValidOrderbook('orderbook', orderbook);
@@ -208,13 +209,21 @@ export class SwapQuoter {
},
);
this._marketOperationUtils = new MarketOperationUtils(
new DexOrderSampler(samplerContract, samplerOverrides, provider),
new DexOrderSampler(
samplerContract,
samplerOverrides,
provider,
undefined,
undefined,
undefined,
tokenAdjacencyGraph,
liquidityProviderRegistry,
),
this._contractAddresses,
{
chainId,
exchangeAddress: this._contractAddresses.exchange,
},
liquidityProviderRegistryAddress,
);
this._swapQuoteCalculator = new SwapQuoteCalculator(this._marketOperationUtils);
}

View File

@@ -4,7 +4,13 @@ import { TakerRequestQueryParams } from '@0x/quote-server';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { ERC20BridgeSource, GetMarketOrdersOpts, OptimizedMarketOrder } from './utils/market_operation_utils/types';
import {
ERC20BridgeSource,
GetMarketOrdersOpts,
LiquidityProviderRegistry,
OptimizedMarketOrder,
TokenAdjacencyGraph,
} from './utils/market_operation_utils/types';
import { QuoteReport } from './utils/quote_report_generator';
/**
@@ -310,11 +316,12 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
ethereumRpcUrl?: string;
contractAddresses?: AssetSwapperContractAddresses;
samplerGasLimit?: number;
liquidityProviderRegistryAddress?: string;
multiBridgeAddress?: string;
ethGasStationUrl?: string;
rfqt?: SwapQuoterRfqtOpts;
samplerOverrides?: SamplerOverrides;
tokenAdjacencyGraph?: TokenAdjacencyGraph;
liquidityProviderRegistry?: LiquidityProviderRegistry;
}
/**

View File

@@ -13,6 +13,7 @@ import {
FeeSchedule,
FillData,
GetMarketOrdersOpts,
LiquidityProviderRegistry,
MultiHopFillData,
SnowSwapFillData,
SushiSwapFillData,
@@ -24,55 +25,51 @@ import {
/**
* Valid sources for market sell.
*/
export const SELL_SOURCE_FILTER = new SourceFilters(
[
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
// Bancor is sampled off-chain, but this list should only include on-chain sources (used in ERC20BridgeSampler)
// ERC20BridgeSource.Bancor,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SnowSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Shell,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
],
[ERC20BridgeSource.MultiBridge],
);
export const SELL_SOURCE_FILTER = new SourceFilters([
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
// Bancor is sampled off-chain, but this list should only include on-chain sources (used in ERC20BridgeSampler)
// ERC20BridgeSource.Bancor,
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SnowSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.Shell,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
ERC20BridgeSource.LiquidityProvider,
]);
/**
* Valid sources for market buy.
*/
export const BUY_SOURCE_FILTER = new SourceFilters(
[
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Shell,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SnowSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
],
[ERC20BridgeSource.MultiBridge],
);
export const BUY_SOURCE_FILTER = new SourceFilters([
ERC20BridgeSource.Native,
ERC20BridgeSource.Uniswap,
ERC20BridgeSource.UniswapV2,
ERC20BridgeSource.Eth2Dai,
ERC20BridgeSource.Kyber,
ERC20BridgeSource.Curve,
ERC20BridgeSource.Balancer,
// ERC20BridgeSource.Bancor, // FIXME: Disabled until Bancor SDK supports buy quotes
ERC20BridgeSource.MStable,
ERC20BridgeSource.Mooniswap,
ERC20BridgeSource.Shell,
ERC20BridgeSource.Swerve,
ERC20BridgeSource.SnowSwap,
ERC20BridgeSource.SushiSwap,
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.Dodo,
ERC20BridgeSource.Cream,
ERC20BridgeSource.LiquidityProvider,
]);
/**
* 0x Protocol Fee Multiplier
@@ -352,6 +349,8 @@ export const MAINNET_KYBER_TOKEN_RESERVE_IDS: { [token: string]: string } = {
'0xaa42414e44000000000000000000000000000000000000000000000000000000',
};
export const LIQUIDITY_PROVIDER_REGISTRY: LiquidityProviderRegistry = {};
export const MAINNET_SUSHI_SWAP_ROUTER = '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F';
export const MAINNET_SHELL_POOLS = {
@@ -544,5 +543,5 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
exchangeProxyOverhead: () => ZERO_AMOUNT,
allowFallback: true,
shouldGenerateQuoteReport: false,
tokenAdjacencyGraph: {},
tokenAdjacencyGraph: { default: [] },
};

View File

@@ -78,7 +78,6 @@ export async function getRfqtIndicativeQuotesAsync(
export class MarketOperationUtils {
private readonly _wethAddress: string;
private readonly _multiBridge: string;
private readonly _sellSources: SourceFilters;
private readonly _buySources: SourceFilters;
private readonly _feeSources = new SourceFilters(FEE_QUOTE_SOURCES);
@@ -108,19 +107,10 @@ export class MarketOperationUtils {
private readonly _sampler: DexOrderSampler,
private readonly contractAddresses: AssetSwapperContractAddresses,
private readonly _orderDomain: OrderDomain,
private readonly _liquidityProviderRegistry: string = NULL_ADDRESS,
) {
this._wethAddress = contractAddresses.etherToken.toLowerCase();
this._multiBridge = contractAddresses.multiBridge.toLowerCase();
const optionalQuoteSources = [];
if (this._liquidityProviderRegistry !== NULL_ADDRESS) {
optionalQuoteSources.push(ERC20BridgeSource.LiquidityProvider);
}
if (this._multiBridge !== NULL_ADDRESS) {
optionalQuoteSources.push(ERC20BridgeSource.MultiBridge);
}
this._buySources = BUY_SOURCE_FILTER.validate(optionalQuoteSources);
this._sellSources = SELL_SOURCE_FILTER.validate(optionalQuoteSources);
this._buySources = BUY_SOURCE_FILTER;
this._sellSources = SELL_SOURCE_FILTER;
}
/**
@@ -176,42 +166,21 @@ export class MarketOperationUtils {
// Get native order fillable amounts.
this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
// Get ETH -> maker token price.
this._sampler.getMedianSellRate(
feeSourceFilters.sources,
makerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
this._multiBridge,
),
this._sampler.getMedianSellRate(feeSourceFilters.sources, makerToken, this._wethAddress, ONE_ETHER),
// Get ETH -> taker token price.
this._sampler.getMedianSellRate(
feeSourceFilters.sources,
takerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
this._multiBridge,
),
this._sampler.getMedianSellRate(feeSourceFilters.sources, takerToken, this._wethAddress, ONE_ETHER),
// Get sell quotes for taker -> maker.
this._sampler.getSellQuotes(
quoteSourceFilters.exclude(offChainSources).sources,
makerToken,
takerToken,
sampleAmounts,
this._wethAddress,
_opts.tokenAdjacencyGraph,
this._liquidityProviderRegistry,
this._multiBridge,
),
this._sampler.getTwoHopSellQuotes(
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
makerToken,
takerToken,
takerAmount,
this._wethAddress,
_opts.tokenAdjacencyGraph,
this._liquidityProviderRegistry,
),
);
@@ -327,41 +296,21 @@ export class MarketOperationUtils {
// Get native order fillable amounts.
this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
// Get ETH -> makerToken token price.
this._sampler.getMedianSellRate(
feeSourceFilters.sources,
makerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
this._multiBridge,
),
this._sampler.getMedianSellRate(feeSourceFilters.sources, makerToken, this._wethAddress, ONE_ETHER),
// Get ETH -> taker token price.
this._sampler.getMedianSellRate(
feeSourceFilters.sources,
takerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
this._multiBridge,
),
this._sampler.getMedianSellRate(feeSourceFilters.sources, takerToken, this._wethAddress, ONE_ETHER),
// Get buy quotes for taker -> maker.
this._sampler.getBuyQuotes(
quoteSourceFilters.exclude(offChainSources).sources,
makerToken,
takerToken,
sampleAmounts,
this._wethAddress,
_opts.tokenAdjacencyGraph,
this._liquidityProviderRegistry,
),
this._sampler.getTwoHopBuyQuotes(
quoteSourceFilters.isAllowed(ERC20BridgeSource.MultiHop) ? quoteSourceFilters.sources : [],
makerToken,
takerToken,
makerAmount,
this._wethAddress,
_opts.tokenAdjacencyGraph,
this._liquidityProviderRegistry,
),
);
const isPriceAwareRfqEnabled =
@@ -391,10 +340,6 @@ export class MarketOperationUtils {
offChainBalancerQuotes,
offChainCreamQuotes,
] = await Promise.all([samplerPromise, rfqtPromise, offChainBalancerPromise, offChainCreamPromise]);
// Attach the MultiBridge address to the sample fillData
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach(
q => (q.fillData = { poolAddress: this._multiBridge }),
);
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
return {
side: MarketOperation.Buy,
@@ -482,7 +427,6 @@ export class MarketOperationUtils {
getNativeOrderTokens(orders[0])[1],
this._wethAddress,
ONE_ETHER,
this._wethAddress,
),
),
...batchNativeOrders.map((orders, i) =>
@@ -491,8 +435,6 @@ export class MarketOperationUtils {
getNativeOrderTokens(orders[0])[0],
getNativeOrderTokens(orders[0])[1],
[makerAmounts[i]],
this._wethAddress,
_opts.tokenAdjacencyGraph,
),
),
];

View File

@@ -0,0 +1,12 @@
import { LiquidityProviderRegistry } from './types';
// tslint:disable completed-docs
export function getLiquidityProvidersForPair(
registry: LiquidityProviderRegistry,
takerToken: string,
makerToken: string,
): string[] {
return Object.entries(registry)
.filter(([, tokens]) => [makerToken, takerToken].every(t => tokens.includes(t)))
.map(([providerAddress]) => providerAddress);
}

View File

@@ -1,13 +0,0 @@
import { NULL_ADDRESS, TOKENS } from './constants';
// tslint:disable completed-docs
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

@@ -21,19 +21,11 @@ export function getIntermediateTokens(
makerToken: string,
takerToken: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
wethAddress: string,
): string[] {
let intermediateTokens = [];
if (makerToken === wethAddress) {
intermediateTokens = _.get(tokenAdjacencyGraph, takerToken, [] as string[]);
} else if (takerToken === wethAddress) {
intermediateTokens = _.get(tokenAdjacencyGraph, makerToken, [] as string[]);
} else {
intermediateTokens = _.union(
_.intersection(_.get(tokenAdjacencyGraph, takerToken, []), _.get(tokenAdjacencyGraph, makerToken, [])),
[wethAddress],
);
}
const intermediateTokens = _.intersection(
_.get(tokenAdjacencyGraph, takerToken, tokenAdjacencyGraph.default),
_.get(tokenAdjacencyGraph, makerToken, tokenAdjacencyGraph.default),
);
return _.uniqBy(intermediateTokens, a => a.toLowerCase()).filter(
token => token.toLowerCase() !== makerToken.toLowerCase() && token.toLowerCase() !== takerToken.toLowerCase(),
);

View File

@@ -15,7 +15,6 @@ import {
WALLET_SIGNATURE,
ZERO_AMOUNT,
} from './constants';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
import {
AggregationError,
BalancerFillData,
@@ -28,7 +27,6 @@ import {
KyberFillData,
LiquidityProviderFillData,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
NativeCollapsedFill,
OptimizedMarketOrder,
@@ -193,8 +191,6 @@ function getBridgeAddressFromFill(fill: CollapsedFill, opts: CreateOrderFromPath
return opts.contractAddresses.creamBridge;
case ERC20BridgeSource.LiquidityProvider:
return (fill.fillData as LiquidityProviderFillData).poolAddress;
case ERC20BridgeSource.MultiBridge:
return (fill.fillData as MultiBridgeFillData).poolAddress;
case ERC20BridgeSource.MStable:
return opts.contractAddresses.mStableBridge;
case ERC20BridgeSource.Mooniswap:
@@ -301,13 +297,6 @@ export function createBridgeOrder(
createSushiSwapBridgeData(sushiSwapFillData.tokenAddressPath, sushiSwapFillData.router),
);
break;
case ERC20BridgeSource.MultiBridge:
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
makerToken,
bridgeAddress,
createMultiBridgeData(takerToken, makerToken),
);
break;
case ERC20BridgeSource.Kyber:
const kyberFillData = (fill as CollapsedFill<KyberFillData>).fillData!; // tslint:disable-line:no-non-null-assertion
makerAssetData = assetDataUtils.encodeERC20BridgeAssetData(
@@ -372,15 +361,6 @@ 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 createBalancerBridgeData(takerToken: string, poolAddress: string): string {
const encoder = AbiEncoder.create([
{ name: 'takerToken', type: 'address' },

View File

@@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
import { MarketOperation } from '../../types';
import { POSITIVE_INF, SOURCE_FLAGS, ZERO_AMOUNT } from './constants';
import { POSITIVE_INF, ZERO_AMOUNT } from './constants';
import { createBridgeOrder, createNativeOrder, CreateOrderFromPathOpts, getMakerTakerTokens } from './orders';
import { getCompleteRate, getRate } from './rate_utils';
import {
@@ -202,7 +202,7 @@ export class Path {
}
}
}
return doSourcesConflict(this.sourceFlags);
return true;
}
public isValidNextFill(fill: Fill): boolean {
@@ -215,7 +215,7 @@ export class Path {
if (fill.parent) {
return false;
}
return doSourcesConflict(this.sourceFlags | fill.flags);
return true;
}
private _collapseFills(): ReadonlyArray<CollapsedFill> {
@@ -268,9 +268,3 @@ export interface CollapsedPath extends Path {
readonly collapsedFills: ReadonlyArray<CollapsedFill>;
readonly orders: OptimizedMarketOrder[];
}
const MULTIBRIDGE_SOURCES = SOURCE_FLAGS.LiquidityProvider | SOURCE_FLAGS.Uniswap;
export function doSourcesConflict(flags: number): boolean {
const multiBridgeConflict = flags & SOURCE_FLAGS.MultiBridge && flags & MULTIBRIDGE_SOURCES;
return !multiBridgeConflict;
}

View File

@@ -8,7 +8,7 @@ import { BalancerPoolsCache } from './balancer_utils';
import { BancorService } from './bancor_service';
import { CreamPoolsCache } from './cream_utils';
import { SamplerOperations } from './sampler_operations';
import { BatchedOperation } from './types';
import { BatchedOperation, LiquidityProviderRegistry, TokenAdjacencyGraph } from './types';
/**
* Generate sample amounts up to `maxFillAmount`.
@@ -40,8 +40,18 @@ export class DexOrderSampler extends SamplerOperations {
balancerPoolsCache?: BalancerPoolsCache,
creamPoolsCache?: CreamPoolsCache,
getBancorServiceFn?: () => BancorService,
tokenAdjacencyGraph?: TokenAdjacencyGraph,
liquidityProviderRegistry?: LiquidityProviderRegistry,
) {
super(_samplerContract, provider, balancerPoolsCache, creamPoolsCache, getBancorServiceFn);
super(
_samplerContract,
provider,
balancerPoolsCache,
creamPoolsCache,
getBancorServiceFn,
tokenAdjacencyGraph,
liquidityProviderRegistry,
);
}
/* Type overloads for `executeAsync()`. Could skip this if we would upgrade TS. */

View File

@@ -1,17 +1,17 @@
import { SupportedProvider } from '@0x/dev-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { ERC20BridgeSamplerContract } from '../../wrappers';
import { BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } from './balancer_utils';
import { BancorService } from './bancor_service';
import { MAINNET_SUSHI_SWAP_ROUTER, MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants';
import { LIQUIDITY_PROVIDER_REGISTRY, MAINNET_SUSHI_SWAP_ROUTER, MAX_UINT256, ZERO_AMOUNT } from './constants';
import { CreamPoolsCache } from './cream_utils';
import { getCurveInfosForPair, getSnowSwapInfosForPair, getSwerveInfosForPair } from './curve_utils';
import { getKyberReserveIdsForPair } from './kyber_utils';
import { getMultiBridgeIntermediateToken } from './multibridge_utils';
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
import { getIntermediateTokens } from './multihop_utils';
import { SamplerContractOperation } from './sampler_contract_operation';
import { getShellsForPair } from './shell_utils';
@@ -28,8 +28,8 @@ import {
HopInfo,
KyberFillData,
LiquidityProviderFillData,
LiquidityProviderRegistry,
MooniswapFillData,
MultiBridgeFillData,
MultiHopFillData,
ShellFillData,
SnowSwapFillData,
@@ -47,7 +47,6 @@ import {
*/
export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([
ERC20BridgeSource.MultiHop,
ERC20BridgeSource.MultiBridge,
ERC20BridgeSource.Native,
]);
/**
@@ -80,6 +79,8 @@ export class SamplerOperations {
public readonly balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
public readonly creamPoolsCache: CreamPoolsCache = new CreamPoolsCache(),
protected readonly getBancorServiceFn?: () => BancorService, // for dependency injection in tests
protected readonly tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [] },
public readonly liquidityProviderRegistry: LiquidityProviderRegistry = LIQUIDITY_PROVIDER_REGISTRY,
) {}
public async getBancorServiceAsync(): Promise<BancorService> {
@@ -221,64 +222,32 @@ export class SamplerOperations {
}
public getLiquidityProviderSellQuotes(
registryAddress: string,
providerAddress: string,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<LiquidityProviderFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.LiquidityProvider,
fillData: {} as LiquidityProviderFillData, // tslint:disable-line:no-object-literal-type-assertion
fillData: { poolAddress: providerAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromLiquidityProviderRegistry,
params: [registryAddress, takerToken, makerToken, takerFillAmounts],
callback: (callResults: string, fillData: LiquidityProviderFillData): BigNumber[] => {
const [samples, poolAddress] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], string]>(
'sampleSellsFromLiquidityProviderRegistry',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
function: this._samplerContract.sampleSellsFromLiquidityProvider,
params: [providerAddress, takerToken, makerToken, takerFillAmounts],
});
}
public getLiquidityProviderBuyQuotes(
registryAddress: string,
providerAddress: string,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): SourceQuoteOperation<LiquidityProviderFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.LiquidityProvider,
fillData: {} as LiquidityProviderFillData, // tslint:disable-line:no-object-literal-type-assertion
fillData: { poolAddress: providerAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromLiquidityProviderRegistry,
params: [registryAddress, takerToken, makerToken, makerFillAmounts],
callback: (callResults: string, fillData: LiquidityProviderFillData): BigNumber[] => {
const [samples, poolAddress] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], string]>(
'sampleBuysFromLiquidityProviderRegistry',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
});
}
public getMultiBridgeSellQuotes(
multiBridgeAddress: string,
makerToken: string,
intermediateToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): SourceQuoteOperation<MultiBridgeFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.MultiBridge,
fillData: { poolAddress: multiBridgeAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromMultiBridge,
params: [multiBridgeAddress, takerToken, intermediateToken, makerToken, takerFillAmounts],
function: this._samplerContract.sampleBuysFromLiquidityProvider,
params: [providerAddress, takerToken, makerToken, makerFillAmounts],
});
}
@@ -669,34 +638,15 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
sellAmount: BigNumber,
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
if (_sources.length === 0) {
return SamplerOperations.constant([]);
}
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph);
const subOps = intermediateTokens.map(intermediateToken => {
const firstHopOps = this._getSellQuoteOperations(
_sources,
intermediateToken,
takerToken,
[ZERO_AMOUNT],
wethAddress,
tokenAdjacencyGraph,
liquidityProviderRegistryAddress,
);
const secondHopOps = this._getSellQuoteOperations(
_sources,
makerToken,
intermediateToken,
[ZERO_AMOUNT],
wethAddress,
tokenAdjacencyGraph,
liquidityProviderRegistryAddress,
);
const firstHopOps = this._getSellQuoteOperations(_sources, intermediateToken, takerToken, [ZERO_AMOUNT]);
const secondHopOps = this._getSellQuoteOperations(_sources, makerToken, intermediateToken, [ZERO_AMOUNT]);
return new SamplerContractOperation({
contract: this._samplerContract,
source: ERC20BridgeSource.MultiHop,
@@ -747,34 +697,19 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
buyAmount: BigNumber,
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
): BatchedOperation<Array<DexSample<MultiHopFillData>>> {
const _sources = TWO_HOP_SOURCE_FILTERS.getAllowed(sources);
if (_sources.length === 0) {
return SamplerOperations.constant([]);
}
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph);
const subOps = intermediateTokens.map(intermediateToken => {
const firstHopOps = this._getBuyQuoteOperations(
_sources,
intermediateToken,
takerToken,
[new BigNumber(0)],
wethAddress,
tokenAdjacencyGraph, // TODO is this a bad idea?
liquidityProviderRegistryAddress,
);
const secondHopOps = this._getBuyQuoteOperations(
_sources,
makerToken,
intermediateToken,
[new BigNumber(0)],
wethAddress,
tokenAdjacencyGraph,
liquidityProviderRegistryAddress,
);
const firstHopOps = this._getBuyQuoteOperations(_sources, intermediateToken, takerToken, [
new BigNumber(0),
]);
const secondHopOps = this._getBuyQuoteOperations(_sources, makerToken, intermediateToken, [
new BigNumber(0),
]);
return new SamplerContractOperation({
contract: this._samplerContract,
source: ERC20BridgeSource.MultiHop,
@@ -853,17 +788,10 @@ export class SamplerOperations {
): SourceQuoteOperation<ShellFillData> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Shell,
fillData: { poolAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleSellsFromShell,
params: [poolAddress, takerToken, makerToken, takerFillAmounts],
callback: (callResults: string, fillData: ShellFillData): BigNumber[] => {
const samples = this._samplerContract.getABIDecodedReturnData<BigNumber[]>(
'sampleSellsFromShell',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
});
}
@@ -875,17 +803,10 @@ export class SamplerOperations {
): SourceQuoteOperation {
return new SamplerContractOperation({
source: ERC20BridgeSource.Shell,
fillData: { poolAddress },
contract: this._samplerContract,
function: this._samplerContract.sampleBuysFromShell,
params: [poolAddress, takerToken, makerToken, makerFillAmounts],
callback: (callResults: string, fillData: ShellFillData): BigNumber[] => {
const samples = this._samplerContract.getABIDecodedReturnData<BigNumber[]>(
'sampleBuysFromShell',
callResults,
);
fillData.poolAddress = poolAddress;
return samples;
},
});
}
@@ -936,52 +857,36 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
takerFillAmount: BigNumber,
liquidityProviderRegistryAddress?: string,
multiBridgeAddress?: string,
): BatchedOperation<BigNumber> {
if (makerToken.toLowerCase() === takerToken.toLowerCase()) {
return SamplerOperations.constant(new BigNumber(1));
}
const getSellQuotes = this.getSellQuotes(
sources,
makerToken,
takerToken,
[takerFillAmount],
NULL_ADDRESS, // weth address
{}, // token adjacency
liquidityProviderRegistryAddress,
multiBridgeAddress,
);
const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, [takerFillAmount], {
default: [],
});
return {
encodeCall: () => {
const encodedCall = getSellQuotes.encodeCall();
// All soures were excluded
if (encodedCall === NULL_BYTES) {
return NULL_BYTES;
}
return this._samplerContract.batchCall([encodedCall]).getABIEncodedTransactionData();
const subCalls = subOps.map(op => op.encodeCall());
return this._samplerContract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResults: callResults => {
if (callResults === NULL_BYTES) {
return ZERO_AMOUNT;
}
const rawSubCallResults = this._samplerContract.getABIDecodedReturnData<string[]>(
'batchCall',
callResults,
);
const samples = getSellQuotes.handleCallResults(rawSubCallResults[0]);
const samples = subOps.map((op, i) => op.handleCallResults(rawSubCallResults[i]));
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));
.filter(v => !v.isZero())
.sort((a, b) => a.comparedTo(b));
if (flatSortedSamples.length === 0) {
return ZERO_AMOUNT;
}
const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
return medianSample.output.div(medianSample.input);
return medianSample.div(takerFillAmount);
},
};
}
@@ -991,21 +896,8 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
multiBridgeAddress?: string,
): BatchedOperation<DexSample[][]> {
const subOps = this._getSellQuoteOperations(
sources,
makerToken,
takerToken,
takerFillAmounts,
wethAddress,
tokenAdjacencyGraph,
liquidityProviderRegistryAddress,
multiBridgeAddress,
);
const subOps = this._getSellQuoteOperations(sources, makerToken, takerToken, takerFillAmounts);
return {
encodeCall: () => {
const subCalls = subOps.map(op => op.encodeCall());
@@ -1034,19 +926,8 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
): BatchedOperation<DexSample[][]> {
const subOps = this._getBuyQuoteOperations(
sources,
makerToken,
takerToken,
makerFillAmounts,
wethAddress,
tokenAdjacencyGraph,
liquidityProviderRegistryAddress,
);
const subOps = this._getBuyQuoteOperations(sources, makerToken, takerToken, makerFillAmounts);
return {
encodeCall: () => {
const subCalls = subOps.map(op => op.encodeCall());
@@ -1075,21 +956,12 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
multiBridgeAddress?: string,
tokenAdjacencyGraph: TokenAdjacencyGraph = this.tokenAdjacencyGraph,
): SourceQuoteOperation[] {
const _sources = BATCH_SOURCE_FILTERS.exclude(
liquidityProviderRegistryAddress ? [] : [ERC20BridgeSource.LiquidityProvider],
)
.exclude(multiBridgeAddress || multiBridgeAddress === NULL_ADDRESS ? [] : [ERC20BridgeSource.MultiBridge])
.getAllowed(sources);
// Find the adjacent tokens in the provided tooken adjacency graph,
// e.g if this is DAI->USDC we may check for DAI->WETH->USDC
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph);
const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
return _.flatten(
_sources.map(
(source): SourceQuoteOperation | SourceQuoteOperation[] => {
@@ -1144,30 +1016,12 @@ export class SamplerOperations {
),
);
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(
'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.',
);
}
return this.getLiquidityProviderSellQuotes(
liquidityProviderRegistryAddress,
makerToken,
return getLiquidityProvidersForPair(
this.liquidityProviderRegistry,
takerToken,
takerFillAmounts,
);
case ERC20BridgeSource.MultiBridge:
if (multiBridgeAddress === undefined) {
throw new Error(
'Cannot sample liquidity from MultiBridge if an address is not provided.',
);
}
const intermediateToken = getMultiBridgeIntermediateToken(takerToken, makerToken);
return this.getMultiBridgeSellQuotes(
multiBridgeAddress,
makerToken,
intermediateToken,
takerToken,
takerFillAmounts,
).map(pool =>
this.getLiquidityProviderSellQuotes(pool, makerToken, takerToken, takerFillAmounts),
);
case ERC20BridgeSource.MStable:
return this.getMStableSellQuotes(makerToken, takerToken, takerFillAmounts);
@@ -1216,18 +1070,11 @@ export class SamplerOperations {
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
wethAddress: string,
tokenAdjacencyGraph: TokenAdjacencyGraph,
liquidityProviderRegistryAddress?: string,
): SourceQuoteOperation[] {
const _sources = BATCH_SOURCE_FILTERS.exclude(
liquidityProviderRegistryAddress ? [] : [ERC20BridgeSource.LiquidityProvider],
).getAllowed(sources);
// Find the adjacent tokens in the provided tooken adjacency graph,
// e.g if this is DAI->USDC we may check for DAI->WETH->USDC
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, tokenAdjacencyGraph, wethAddress);
const intermediateTokens = getIntermediateTokens(makerToken, takerToken, this.tokenAdjacencyGraph);
const _sources = BATCH_SOURCE_FILTERS.getAllowed(sources);
return _.flatten(
_sources.map(
(source): SourceQuoteOperation | SourceQuoteOperation[] => {
@@ -1282,16 +1129,12 @@ export class SamplerOperations {
),
);
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(
'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.',
);
}
return this.getLiquidityProviderBuyQuotes(
liquidityProviderRegistryAddress,
makerToken,
return getLiquidityProvidersForPair(
this.liquidityProviderRegistry,
takerToken,
makerFillAmounts,
makerToken,
).map(pool =>
this.getLiquidityProviderBuyQuotes(pool, makerToken, takerToken, makerFillAmounts),
);
case ERC20BridgeSource.MStable:
return this.getMStableBuyQuotes(makerToken, takerToken, makerFillAmounts);

View File

@@ -132,10 +132,6 @@ export interface LiquidityProviderFillData extends FillData {
poolAddress: string;
}
export interface MultiBridgeFillData extends FillData {
poolAddress: string;
}
export interface BancorFillData extends FillData {
path: string[];
networkAddress: string;
@@ -375,6 +371,11 @@ export interface MarketSideLiquidity {
export interface TokenAdjacencyGraph {
[token: string]: string[];
default: string[];
}
export interface LiquidityProviderRegistry {
[address: string]: [string, string];
}
export interface GenerateOptimizedOrdersOpts {

View File

@@ -3,8 +3,4 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/dummy_liquidity_provider';
export * from '../generated-wrappers/dummy_liquidity_provider_registry';
export * from '../generated-wrappers/erc20_bridge_sampler';
export * from '../generated-wrappers/i_liquidity_provider';
export * from '../generated-wrappers/i_liquidity_provider_registry';

View File

@@ -11,15 +11,12 @@ import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json';
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
import * as DummyLiquidityProviderRegistry from '../test/generated-artifacts/DummyLiquidityProviderRegistry.json';
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json';
import * as IBalancer from '../test/generated-artifacts/IBalancer.json';
import * as ICurve from '../test/generated-artifacts/ICurve.json';
import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json';
import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json';
import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json';
import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquidityProviderRegistry.json';
import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json';
import * as IMStable from '../test/generated-artifacts/IMStable.json';
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
@@ -64,8 +61,6 @@ export const artifacts = {
ICurve: ICurve as ContractArtifact,
IEth2Dai: IEth2Dai as ContractArtifact,
IKyberNetwork: IKyberNetwork as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
IMStable: IMStable as ContractArtifact,
IMooniswap: IMooniswap as ContractArtifact,
IMultiBridge: IMultiBridge as ContractArtifact,
@@ -73,7 +68,6 @@ export const artifacts = {
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
TestNativeOrderSampler: TestNativeOrderSampler as ContractArtifact,
};

View File

@@ -11,11 +11,7 @@ import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import {
DummyLiquidityProviderContract,
DummyLiquidityProviderRegistryContract,
TestERC20BridgeSamplerContract,
} from '../wrappers';
import { DummyLiquidityProviderContract, TestERC20BridgeSamplerContract } from '../wrappers';
// tslint:disable: custom-no-magic-numbers
@@ -817,7 +813,6 @@ blockchainTests('erc20-bridge-sampler', env => {
const yAsset = randomAddress();
const sampleAmounts = getSampleAmounts(yAsset);
let liquidityProvider: DummyLiquidityProviderContract;
let registryContract: DummyLiquidityProviderRegistryContract;
before(async () => {
liquidityProvider = await DummyLiquidityProviderContract.deployFrom0xArtifactAsync(
@@ -826,61 +821,33 @@ blockchainTests('erc20-bridge-sampler', env => {
env.txDefaults,
{},
);
registryContract = await DummyLiquidityProviderRegistryContract.deployFrom0xArtifactAsync(
artifacts.DummyLiquidityProviderRegistry,
env.provider,
env.txDefaults,
{},
);
await registryContract
.setLiquidityProviderForMarket(xAsset, yAsset, liquidityProvider.address)
.awaitTransactionSuccessAsync();
});
it('should be able to query sells from the liquidity provider', async () => {
const [quotes, providerAddress] = await testContract
.sampleSellsFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
const quotes = await testContract
.sampleSellsFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts)
.callAsync();
quotes.forEach((value, idx) => {
expect(value).is.bignumber.eql(sampleAmounts[idx].minus(1));
});
expect(providerAddress).to.equal(liquidityProvider.address);
});
it('should be able to query buys from the liquidity provider', async () => {
const [quotes, providerAddress] = await testContract
.sampleBuysFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
const quotes = await testContract
.sampleBuysFromLiquidityProvider(liquidityProvider.address, yAsset, xAsset, sampleAmounts)
.callAsync();
quotes.forEach((value, idx) => {
expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1));
});
expect(providerAddress).to.equal(liquidityProvider.address);
});
it('should just return zeros if the liquidity provider cannot be found', async () => {
const [quotes, providerAddress] = await testContract
.sampleBuysFromLiquidityProviderRegistry(
registryContract.address,
yAsset,
randomAddress(),
sampleAmounts,
)
it('should just return zeros if the liquidity provider does not exist', async () => {
const quotes = await testContract
.sampleBuysFromLiquidityProvider(randomAddress(), yAsset, xAsset, sampleAmounts)
.callAsync();
quotes.forEach(value => {
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
});
expect(providerAddress).to.equal(constants.NULL_ADDRESS);
});
it('should just return zeros if the registry does not exist', async () => {
const [quotes, providerAddress] = await testContract
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts)
.callAsync();
quotes.forEach(value => {
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
});
expect(providerAddress).to.equal(constants.NULL_ADDRESS);
});
});

View File

@@ -36,7 +36,7 @@ describe('DexSampler tests', () => {
const wethAddress = getContractAddressesForChainOrThrow(CHAIN_ID).etherToken;
const exchangeAddress = getContractAddressesForChainOrThrow(CHAIN_ID).exchange;
const tokenAdjacencyGraph: TokenAdjacencyGraph = {};
const tokenAdjacencyGraph: TokenAdjacencyGraph = { default: [wethAddress] };
describe('getSampleAmounts()', () => {
const FILL_AMOUNT = getRandomInteger(1, 1e18);
@@ -159,26 +159,31 @@ describe('DexSampler tests', () => {
it('getLiquidityProviderSellQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const poolAddress = randomAddress();
const sampler = new MockSamplerContract({
sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
expect(registryAddress).to.eq(registry);
sampleSellsFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
expect(providerAddress).to.eq(poolAddress);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [[toBaseUnitAmount(1001)], poolAddress];
return [toBaseUnitAmount(1001)];
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
{ [poolAddress]: [expectedMakerToken, expectedTakerToken] },
);
const [result] = await dexOrderSampler.executeAsync(
dexOrderSampler.getSellQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
wethAddress,
tokenAdjacencyGraph,
registry,
),
);
expect(result).to.deep.equal([
@@ -196,26 +201,31 @@ describe('DexSampler tests', () => {
it('getLiquidityProviderBuyQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const poolAddress = randomAddress();
const sampler = new MockSamplerContract({
sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, _fillAmounts) => {
expect(registryAddress).to.eq(registry);
sampleBuysFromLiquidityProvider: (providerAddress, takerToken, makerToken, _fillAmounts) => {
expect(providerAddress).to.eq(poolAddress);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [[toBaseUnitAmount(999)], poolAddress];
return [toBaseUnitAmount(999)];
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
{ [poolAddress]: [expectedMakerToken, expectedTakerToken] },
);
const [result] = await dexOrderSampler.executeAsync(
dexOrderSampler.getBuyQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
wethAddress,
tokenAdjacencyGraph,
registry,
),
);
expect(result).to.deep.equal([
@@ -230,50 +240,6 @@ 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.getSellQuotes(
[ERC20BridgeSource.MultiBridge],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
wethAddress,
tokenAdjacencyGraph,
randomAddress(),
multiBridge,
),
);
expect(result).to.deep.equal([
[
{
source: 'MultiBridge',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
fillData: { poolAddress: multiBridge },
},
],
]);
});
it('getEth2DaiSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();
@@ -416,15 +382,21 @@ describe('DexSampler tests', () => {
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
tokenAdjacencyGraph,
);
const [quotes] = await dexOrderSampler.executeAsync(
dexOrderSampler.getSellQuotes(
sources,
expectedMakerToken,
expectedTakerToken,
expectedTakerFillAmounts,
wethAddress,
tokenAdjacencyGraph,
),
);
const expectedQuotes = sources.map(s =>
@@ -561,16 +533,17 @@ describe('DexSampler tests', () => {
return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.UniswapV2]).integerValue());
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const dexOrderSampler = new DexOrderSampler(
sampler,
undefined,
undefined,
undefined,
undefined,
undefined,
tokenAdjacencyGraph,
);
const [quotes] = await dexOrderSampler.executeAsync(
dexOrderSampler.getBuyQuotes(
sources,
expectedMakerToken,
expectedTakerToken,
expectedMakerFillAmounts,
wethAddress,
tokenAdjacencyGraph,
),
dexOrderSampler.getBuyQuotes(sources, expectedMakerToken, expectedTakerToken, expectedMakerFillAmounts),
);
const expectedQuotes = sources.map(s =>
expectedMakerFillAmounts.map(a => ({

View File

@@ -20,7 +20,7 @@ import { constants } from '../src/constants';
import { ExchangeProxySwapQuoteConsumer } from '../src/quote_consumers/exchange_proxy_swap_quote_consumer';
import { getSwapMinBuyAmount } from '../src/quote_consumers/utils';
import { MarketBuySwapQuote, MarketOperation, MarketSellSwapQuote } from '../src/types';
import { OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';
import { ERC20BridgeSource, OptimizedMarketOrder } from '../src/utils/market_operation_utils/types';
import { chaiSetup } from './utils/chai_setup';
@@ -161,7 +161,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
);
}
const callDataEncoder = AbiEncoder.createMethod('transformERC20', [
const transformERC20Encoder = AbiEncoder.createMethod('transformERC20', [
{ type: 'address', name: 'inputToken' },
{ type: 'address', name: 'outputToken' },
{ type: 'uint256', name: 'inputTokenAmount' },
@@ -173,7 +173,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
},
]);
interface CallArgs {
interface TransformERC20Args {
inputToken: string;
outputToken: string;
inputTokenAmount: BigNumber;
@@ -184,11 +184,31 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
}>;
}
const liquidityProviderEncoder = AbiEncoder.createMethod('sellToLiquidityProvider', [
{ type: 'address', name: 'inputToken' },
{ type: 'address', name: 'outputToken' },
{ type: 'address', name: 'target' },
{ type: 'address', name: 'recipient' },
{ type: 'uint256', name: 'sellAmount' },
{ type: 'uint256', name: 'minBuyAmount' },
{ type: 'bytes', name: 'auxiliaryData' },
]);
interface LiquidityProviderArgs {
inputToken: string;
outputToken: string;
target: string;
recipient: string;
sellAmount: BigNumber;
minBuyAmount: BigNumber;
auxiliaryData: string;
}
describe('getCalldataOrThrow()', () => {
it('can produce a sell quote', async () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
@@ -217,7 +237,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
it('can produce a buy quote', async () => {
const quote = getRandomBuyQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
@@ -246,7 +266,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
it('ERC20 -> ERC20 does not have a WETH transformer', async () => {
const quote = getRandomSellQuote();
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
const nonces = callArgs.transformations.map(t => t.deploymentNonce);
expect(nonces).to.not.include(consumer.transformerNonces.wethTransformer);
});
@@ -256,7 +276,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { isFromETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.transformations[0].deploymentNonce.toNumber()).to.eq(
consumer.transformerNonces.wethTransformer,
);
@@ -270,7 +290,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { isToETH: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq(
consumer.transformerNonces.wethTransformer,
);
@@ -278,7 +298,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
expect(wethTransformerData.amount).to.bignumber.eq(MAX_UINT256);
expect(wethTransformerData.token).to.eq(contractAddresses.etherToken);
});
it('Appends an affiliate fee transformer after the fill if a buy token affiliate fee is provided', async () => {
it('Appends an affiliate fee transformer after the fill if a buy token affiliate fee is provided', async () => {
const quote = getRandomSellQuote();
const affiliateFee = {
recipient: randomAddress(),
@@ -288,7 +308,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { affiliateFee },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.transformations[1].deploymentNonce.toNumber()).to.eq(
consumer.transformerNonces.affiliateFeeTransformer,
);
@@ -315,7 +335,7 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
const callInfo = await consumer.getCalldataOrThrowAsync(quote, {
extensionContractOpts: { isTwoHop: true },
});
const callArgs = callDataEncoder.decode(callInfo.calldataHexString) as CallArgs;
const callArgs = transformERC20Encoder.decode(callInfo.calldataHexString) as TransformERC20Args;
expect(callArgs.inputToken).to.eq(TAKER_TOKEN);
expect(callArgs.outputToken).to.eq(MAKER_TOKEN);
expect(callArgs.inputTokenAmount).to.bignumber.eq(quote.worstCaseQuoteInfo.totalTakerAssetAmount);
@@ -357,5 +377,35 @@ describe('ExchangeProxySwapQuoteConsumer', () => {
INTERMEDIATE_TOKEN,
]);
});
it('Uses the `LiquidityProviderFeature` if given a single LiquidityProvider order', async () => {
const quote = {
...getRandomSellQuote(),
orders: [
{
...getRandomOrder(),
fills: [
{
source: ERC20BridgeSource.LiquidityProvider,
sourcePathId: '',
input: constants.ZERO_AMOUNT,
output: constants.ZERO_AMOUNT,
subFills: [],
},
],
},
],
};
const callInfo = await consumer.getCalldataOrThrowAsync(quote);
const callArgs = liquidityProviderEncoder.decode(callInfo.calldataHexString) as LiquidityProviderArgs;
expect(callArgs).to.deep.equal({
inputToken: TAKER_TOKEN,
outputToken: MAKER_TOKEN,
target: quote.orders[0].makerAddress,
recipient: constants.NULL_ADDRESS,
sellAmount: quote.worstCaseQuoteInfo.totalTakerAssetAmount,
minBuyAmount: getSwapMinBuyAmount(quote),
auxiliaryData: constants.NULL_BYTES,
});
});
});
});

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,
},
);

View File

@@ -37,19 +37,12 @@ export type SampleBuysKyberHandler = (
) => [string, SampleResults];
export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
export type SampleSellsLPHandler = (
registryAddress: string,
providerAddress: string,
takerToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
) => [SampleResults, string];
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
export type SampleSellsMBHandler = (
multiBridgeAddress: string,
takerToken: string,
intermediateToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
) => SampleResults;
export type SampleSellsMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults;
const DUMMY_PROVIDER = {
sendAsync: (..._args: any[]): any => {
@@ -61,15 +54,14 @@ interface Handlers {
getOrderFillableMakerAssetAmounts: GetOrderFillableAssetAmountHandler;
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
sampleSellsFromKyberNetwork: SampleSellsKyberHandler;
sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler;
sampleSellsFromMultiBridge: SampleSellsMBHandler;
sampleSellsFromLiquidityProvider: SampleSellsLPHandler;
sampleSellsFromEth2Dai: SampleSellsHandler;
sampleSellsFromUniswap: SampleSellsHandler;
sampleSellsFromUniswapV2: SampleSellsMultihopHandler;
sampleBuysFromEth2Dai: SampleBuysHandler;
sampleBuysFromUniswap: SampleBuysHandler;
sampleBuysFromUniswapV2: SampleBuysMultihopHandler;
sampleBuysFromLiquidityProviderRegistry: SampleSellsLPHandler;
sampleBuysFromLiquidityProvider: SampleSellsLPHandler;
}
// tslint:disable: no-unbound-method
@@ -171,35 +163,17 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract {
);
}
public sampleSellsFromLiquidityProviderRegistry(
registryAddress: string,
public sampleSellsFromLiquidityProvider(
providerAddress: string,
takerToken: string,
makerToken: string,
takerAssetAmounts: BigNumber[],
): ContractTxFunctionObj<[BigNumber[], string]> {
return this._wrapCall(
super.sampleSellsFromLiquidityProviderRegistry,
this._handlers.sampleSellsFromLiquidityProviderRegistry,
registryAddress,
takerToken,
makerToken,
takerAssetAmounts,
);
}
public sampleSellsFromMultiBridge(
multiBridgeAddress: string,
takerToken: string,
intermediateToken: string,
makerToken: string,
takerAssetAmounts: BigNumber[],
): ContractTxFunctionObj<BigNumber[]> {
return this._wrapCall(
super.sampleSellsFromMultiBridge,
this._handlers.sampleSellsFromMultiBridge,
multiBridgeAddress,
super.sampleSellsFromLiquidityProvider,
this._handlers.sampleSellsFromLiquidityProvider,
providerAddress,
takerToken,
intermediateToken,
makerToken,
takerAssetAmounts,
);

View File

@@ -9,15 +9,12 @@ export * from '../test/generated-wrappers/curve_sampler';
export * from '../test/generated-wrappers/d_o_d_o_sampler';
export * from '../test/generated-wrappers/deployment_constants';
export * from '../test/generated-wrappers/dummy_liquidity_provider';
export * from '../test/generated-wrappers/dummy_liquidity_provider_registry';
export * from '../test/generated-wrappers/erc20_bridge_sampler';
export * from '../test/generated-wrappers/eth2_dai_sampler';
export * from '../test/generated-wrappers/i_balancer';
export * from '../test/generated-wrappers/i_curve';
export * from '../test/generated-wrappers/i_eth2_dai';
export * from '../test/generated-wrappers/i_kyber_network';
export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_registry';
export * from '../test/generated-wrappers/i_m_stable';
export * from '../test/generated-wrappers/i_mooniswap';
export * from '../test/generated-wrappers/i_multi_bridge';

View File

@@ -3,26 +3,19 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/DummyLiquidityProvider.json",
"generated-artifacts/DummyLiquidityProviderRegistry.json",
"generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/ILiquidityProvider.json",
"generated-artifacts/ILiquidityProviderRegistry.json",
"test/generated-artifacts/ApproximateBuys.json",
"test/generated-artifacts/BalancerSampler.json",
"test/generated-artifacts/CurveSampler.json",
"test/generated-artifacts/DODOSampler.json",
"test/generated-artifacts/DeploymentConstants.json",
"test/generated-artifacts/DummyLiquidityProvider.json",
"test/generated-artifacts/DummyLiquidityProviderRegistry.json",
"test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/Eth2DaiSampler.json",
"test/generated-artifacts/IBalancer.json",
"test/generated-artifacts/ICurve.json",
"test/generated-artifacts/IEth2Dai.json",
"test/generated-artifacts/IKyberNetwork.json",
"test/generated-artifacts/ILiquidityProvider.json",
"test/generated-artifacts/ILiquidityProviderRegistry.json",
"test/generated-artifacts/IMStable.json",
"test/generated-artifacts/IMooniswap.json",
"test/generated-artifacts/IMultiBridge.json",

View File

@@ -1,4 +1,13 @@
[
{
"version": "5.3.0",
"changes": [
{
"note": "Add `exchangeProxyLiquidityProviderSandbox` addresses",
"pr": 16
}
]
},
{
"version": "5.2.0",
"changes": [

View File

@@ -32,6 +32,7 @@
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"exchangeProxyLiquidityProviderSandbox": "0xdb971b18ea5075734cec1241732cc1b41031dfc9",
"transformers": {
"wethTransformer": "0x68c0bb685099dc7cb5c5ce2b26185945b357383e",
"payTakerTransformer": "0x49b9df2c58491764cf40cb052dd4243df63622c7",
@@ -72,6 +73,7 @@
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"exchangeProxyLiquidityProviderSandbox": "0xb8afda68a9834969a69ebd4aab201feff814d170",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@@ -112,6 +114,7 @@
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x1c9a27658dd303a31205a3b245e8993b92d4d502",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"exchangeProxyLiquidityProviderSandbox": "0xb8afda68a9834969a69ebd4aab201feff814d170",
"transformers": {
"wethTransformer": "0x8d822fe2b42f60531203e288f5f357fa79474437",
"payTakerTransformer": "0x150652244723102faeaefa4c79597d097ffa26c6",
@@ -152,6 +155,7 @@
"exchangeProxyAllowanceTarget": "0xf740b67da229f2f10bcbd38a7979992fcc71b8eb",
"exchangeProxyTransformerDeployer": "0x1b62de2dbb5e7aa519e9c442721ecef75702807f",
"exchangeProxyFlashWallet": "0x22f9dcf4647084d6c31b2765f6910cd85c178c18",
"exchangeProxyLiquidityProviderSandbox": "0x598d7a659d1f163d94abe3628674f8a2569ff344",
"transformers": {
"wethTransformer": "0x9ce35b5ee9e710535e3988e3f8731d9ca9dba17d",
"payTakerTransformer": "0x5a53e7b02a83aa9f60ccf4e424f0442c255bc977",
@@ -192,6 +196,7 @@
"exchangeProxyAllowanceTarget": "0x8362c3ebd90041b30ec45908332e592721642637",
"exchangeProxyTransformerDeployer": "0x5409ed021d9299bf6814279a6a1411a7e866a631",
"exchangeProxyFlashWallet": "0xb9682a8e7920b431f1d412b8510f0077410c8faa",
"exchangeProxyLiquidityProviderSandbox": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",

View File

@@ -33,6 +33,7 @@ export interface ContractAddresses {
exchangeProxyAllowanceTarget: string;
exchangeProxyTransformerDeployer: string;
exchangeProxyFlashWallet: string;
exchangeProxyLiquidityProviderSandbox: string;
transformers: {
wethTransformer: string;
payTakerTransformer: string;

View File

@@ -1,4 +1,13 @@
[
{
"version": "3.9.0",
"changes": [
{
"note": "Update IZeroEx artifact and remove some unused artifacts",
"pr": 16
}
]
},
{
"timestamp": 1604376968,
"version": "3.8.2",

View File

@@ -1,81 +0,0 @@
{
"schemaVersion": "2.0.0",
"contractName": "DummyLiquidityProvider",
"compilerOutput": {
"abi": [
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "uint256", "name": "buyAmount", "type": "uint256" }
],
"name": "getBuyQuote",
"outputs": [{ "internalType": "uint256", "name": "takerTokenAmount", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" }
],
"name": "getSellQuote",
"outputs": [{ "internalType": "uint256", "name": "makerTokenAmount", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"devdoc": {
"methods": {
"getBuyQuote(address,address,uint256)": {
"details": "Quotes the amount of `takerToken` that would need to be sold in order to obtain `buyAmount` of `makerToken`.",
"params": { "buyAmount": "Amount of `makerToken` to buy." },
"return": "takerTokenAmount Amount of `takerToken` that would need to be sold."
},
"getSellQuote(address,address,uint256)": {
"details": "Quotes the amount of `makerToken` that would be obtained by selling `sellAmount` of `takerToken`.",
"params": { "sellAmount": "Amount of `takerToken` to sell." },
"return": "makerTokenAmount Amount of `makerToken` that would be obtained."
}
}
},
"evm": {
"bytecode": {
"object": "0x608060405234801561001057600080fd5b50610159806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063343fbcdd1461003b57806345060eb014610064575b600080fd5b61004e6100493660046100a8565b610077565b60405161005b91906100e8565b60405180910390f35b61004e6100723660046100a8565b61009f565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0192915050565b60010192915050565b6000806000606084860312156100bc578283fd5b83356100c7816100f1565b925060208401356100d7816100f1565b929592945050506040919091013590565b90815260200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461011357600080fd5b5056fea365627a7a72315820981135e6e25d9062a0a9bcf7e08e326cde449b18310db7488d1db4e79ef0f6f36c6578706572696d656e74616cf564736f6c63430005100040"
},
"deployedBytecode": {
"object": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063343fbcdd1461003b57806345060eb014610064575b600080fd5b61004e6100493660046100a8565b610077565b60405161005b91906100e8565b60405180910390f35b61004e6100723660046100a8565b61009f565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0192915050565b60010192915050565b6000806000606084860312156100bc578283fd5b83356100c7816100f1565b925060208401356100d7816100f1565b929592945050506040919091013590565b90815260200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461011357600080fd5b5056fea365627a7a72315820981135e6e25d9062a0a9bcf7e08e326cde449b18310db7488d1db4e79ef0f6f36c6578706572696d656e74616cf564736f6c63430005100040"
}
}
},
"compiler": {
"name": "solc",
"version": "soljson-v0.5.16+commit.9c3226ce.js",
"settings": {
"optimizer": {
"enabled": true,
"runs": 1000000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
},
"evmVersion": "istanbul"
}
},
"chains": {}
}

View File

@@ -1,83 +0,0 @@
{
"schemaVersion": "2.0.0",
"contractName": "DummyLiquidityProviderRegistry",
"compilerOutput": {
"abi": [
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "xToken", "type": "address" },
{ "internalType": "address", "name": "yToken", "type": "address" }
],
"name": "getLiquidityProviderForMarket",
"outputs": [{ "internalType": "address", "name": "poolAddress", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "xToken", "type": "address" },
{ "internalType": "address", "name": "yToken", "type": "address" },
{ "internalType": "address", "name": "poolAddress", "type": "address" }
],
"name": "setLiquidityProviderForMarket",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
],
"devdoc": {
"methods": {
"getLiquidityProviderForMarket(address,address)": {
"details": "Returns the address of pool for a market given market (xAsset, yAsset), or reverts if pool does not exist.",
"params": { "xToken": "First asset managed by pool.", "yToken": "Second asset managed by pool." },
"return": "Address of pool."
},
"setLiquidityProviderForMarket(address,address,address)": {
"details": "Sets address of pool for a market given market (xAsset, yAsset).",
"params": {
"poolAddress": "Address of pool.",
"xToken": "First asset managed by pool.",
"yToken": "Second asset managed by pool."
}
}
}
},
"evm": {
"bytecode": {
"object": "0x608060405234801561001057600080fd5b506102a6806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063153f59971461003b57806384da8d1e14610064575b600080fd5b61004e610049366004610192565b610079565b60405161005b919061020b565b60405180910390f35b6100776100723660046101c6565b6100f2565b005b73ffffffffffffffffffffffffffffffffffffffff808316600090815260208181526040808320858516845290915290205416806100ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100e39061022c565b60405180910390fd5b92915050565b73ffffffffffffffffffffffffffffffffffffffff92831660008181526020818152604080832095871683529481528482208054969094167fffffffffffffffffffffffff0000000000000000000000000000000000000000968716811790945581815284822092825291909152919091208054909216179055565b803573ffffffffffffffffffffffffffffffffffffffff811681146100ec57600080fd5b600080604083850312156101a4578182fd5b6101ae848461016e565b91506101bd846020850161016e565b90509250929050565b6000806000606084860312156101da578081fd5b6101e4858561016e565b92506101f3856020860161016e565b9150610202856040860161016e565b90509250925092565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6020808252601c908201527f52656769737472792f4d41524b45545f504149525f4e4f545f5345540000000060408201526060019056fea365627a7a723158200b589233a17eab806bfb7e334f40bc1ba4502479e55b2aa562c069bc440ceb476c6578706572696d656e74616cf564736f6c63430005100040"
},
"deployedBytecode": {
"object": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063153f59971461003b57806384da8d1e14610064575b600080fd5b61004e610049366004610192565b610079565b60405161005b919061020b565b60405180910390f35b6100776100723660046101c6565b6100f2565b005b73ffffffffffffffffffffffffffffffffffffffff808316600090815260208181526040808320858516845290915290205416806100ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100e39061022c565b60405180910390fd5b92915050565b73ffffffffffffffffffffffffffffffffffffffff92831660008181526020818152604080832095871683529481528482208054969094167fffffffffffffffffffffffff0000000000000000000000000000000000000000968716811790945581815284822092825291909152919091208054909216179055565b803573ffffffffffffffffffffffffffffffffffffffff811681146100ec57600080fd5b600080604083850312156101a4578182fd5b6101ae848461016e565b91506101bd846020850161016e565b90509250929050565b6000806000606084860312156101da578081fd5b6101e4858561016e565b92506101f3856020860161016e565b9150610202856040860161016e565b90509250925092565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6020808252601c908201527f52656769737472792f4d41524b45545f504149525f4e4f545f5345540000000060408201526060019056fea365627a7a723158200b589233a17eab806bfb7e334f40bc1ba4502479e55b2aa562c069bc440ceb476c6578706572696d656e74616cf564736f6c63430005100040"
}
}
},
"compiler": {
"name": "solc",
"version": "soljson-v0.5.16+commit.9c3226ce.js",
"settings": {
"optimizer": {
"enabled": true,
"runs": 1000000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
},
"evmVersion": "istanbul"
}
},
"chains": {}
}

View File

@@ -1,58 +0,0 @@
{
"schemaVersion": "2.0.0",
"contractName": "ILiquidityProviderRegistry",
"compilerOutput": {
"abi": [
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "type": "address" }
],
"name": "getLiquidityProviderForMarket",
"outputs": [{ "internalType": "address", "name": "providerAddress", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"devdoc": {
"methods": {
"getLiquidityProviderForMarket(address,address)": {
"details": "Returns the address of a liquidity provider for the given market (takerToken, makerToken), reverting if the pool does not exist.",
"params": {
"makerToken": "Maker asset managed by liquidity provider.",
"takerToken": "Taker asset managed by liquidity provider."
},
"return": "Address of the liquidity provider."
}
}
},
"evm": { "bytecode": { "object": "0x" }, "deployedBytecode": { "object": "0x" } }
},
"compiler": {
"name": "solc",
"version": "soljson-v0.5.16+commit.9c3226ce.js",
"settings": {
"optimizer": {
"enabled": true,
"runs": 1000000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
},
"evmVersion": "istanbul"
}
},
"chains": {}
}

View File

@@ -3,16 +3,6 @@
"contractName": "IZeroEx",
"compilerOutput": {
"abi": [
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "xAsset", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "yAsset", "type": "address" },
{ "indexed": false, "internalType": "address", "name": "providerAddress", "type": "address" }
],
"name": "LiquidityProviderForMarketUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
@@ -232,16 +222,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "xAsset", "type": "address" },
{ "internalType": "address", "name": "yAsset", "type": "address" }
],
"name": "getLiquidityProviderForMarket",
"outputs": [{ "internalType": "address", "name": "providerAddress", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
@@ -390,9 +370,11 @@
"inputs": [
{ "internalType": "address", "name": "makerToken", "type": "address" },
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address payable", "name": "recipient", "type": "address" },
{ "internalType": "address payable", "name": "target", "type": "address" },
{ "internalType": "address", "name": "recipient", "type": "address" },
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
{ "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" }
{ "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" },
{ "internalType": "bytes", "name": "auxiliaryData", "type": "bytes" }
],
"name": "sellToLiquidityProvider",
"outputs": [{ "internalType": "uint256", "name": "boughtAmount", "type": "uint256" }],
@@ -411,17 +393,6 @@
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "xAsset", "type": "address" },
{ "internalType": "address", "name": "yAsset", "type": "address" },
{ "internalType": "address", "name": "providerAddress", "type": "address" }
],
"name": "setLiquidityProviderForMarket",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "quoteSigner", "type": "address" }],
"name": "setQuoteSigner",
@@ -536,14 +507,6 @@
"params": { "selector": "The function selector." },
"returns": { "impl": "The implementation contract address." }
},
"getLiquidityProviderForMarket(address,address)": {
"details": "Returns the address of the liquidity provider for a market given (xAsset, yAsset), or reverts if pool does not exist.",
"params": {
"xAsset": "First asset managed by the liquidity provider.",
"yAsset": "Second asset managed by the liquidity provider."
},
"returns": { "providerAddress": "Address of the liquidity provider." }
},
"getMetaTransactionExecutedBlock((address,address,uint256,uint256,uint256,uint256,bytes,uint256,address,uint256))": {
"details": "Get the block at which a meta-transaction has been executed.",
"params": { "mtx": "The meta-transaction." },
@@ -616,6 +579,19 @@
"targetImpl": "The address of an older implementation of the function."
}
},
"sellToLiquidityProvider(address,address,address,address,uint256,uint256,bytes)": {
"details": "Sells `sellAmount` of `takerToken` to the liquidity provider at the given `target`.",
"params": {
"auxiliaryData": "Auxiliary data supplied to the `target` contract.",
"makerToken": "The token being bought.",
"minBuyAmount": "The minimum acceptable amount of `makerToken` to buy. Reverts if this amount is not satisfied.",
"recipient": "The recipient of the bought tokens. If equal to address(0), `msg.sender` is assumed to be the recipient.",
"sellAmount": "The amount of `takerToken` to sell.",
"takerToken": "The token being sold.",
"target": "The address of the on-chain liquidity provider to trade with."
},
"returns": { "boughtAmount": "The amount of `makerToken` bought." }
},
"sellToUniswap(address[],uint256,uint256,bool)": {
"details": "Efficiently sell directly to uniswap/sushiswap.",
"params": {
@@ -626,14 +602,6 @@
},
"returns": { "buyAmount": "Amount of `tokens[-1]` bought." }
},
"setLiquidityProviderForMarket(address,address,address)": {
"details": "Sets address of the liquidity provider for a market given (xAsset, yAsset).",
"params": {
"providerAddress": "Address of the liquidity provider.",
"xAsset": "First asset managed by the liquidity provider.",
"yAsset": "Second asset managed by the liquidity provider."
}
},
"setQuoteSigner(address)": {
"details": "Replace the optional signer for `transformERC20()` calldata. Only callable by the owner.",
"params": { "quoteSigner": "The address of the new calldata signer." }

View File

@@ -11,7 +11,7 @@
},
"scripts": {
"artifacts_copy": "node lib/src/copy.js",
"artifacts_transform": "node lib/src/transform.js ./artifacts && prettier --write ./artifacts/*.json && cp -r ./artifacts/ ../../python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/",
"artifacts_transform": "node lib/src/transform.js ./artifacts && prettier --write ./artifacts/*.json",
"artifacts_update": "yarn artifacts_copy && yarn artifacts_transform && yarn build",
"build": "yarn tsc -b",
"build:ci": "yarn build",

View File

@@ -1,4 +1,13 @@
[
{
"version": "13.10.0",
"changes": [
{
"note": "Update IZeroEx wrapper and remove ILiquidityProviderRegistry wrapper",
"pr": 16
}
]
},
{
"timestamp": 1604385937,
"version": "13.9.5",

View File

@@ -1,327 +0,0 @@
// tslint:disable:no-consecutive-blank-lines ordered-imports align trailing-comma enum-naming
// tslint:disable:whitespace no-unbound-method no-trailing-whitespace
// tslint:disable:no-unused-variable
import {
AwaitTransactionSuccessOpts,
ContractFunctionObj,
ContractTxFunctionObj,
SendTransactionOpts,
BaseContract,
PromiseWithTransactionHash,
methodAbiToFunctionSignature,
linkLibrariesInBytecode,
} from '@0x/base-contract';
import { schemas } from '@0x/json-schemas';
import {
BlockParam,
BlockParamLiteral,
BlockRange,
CallData,
ContractAbi,
ContractArtifact,
DecodedLogArgs,
MethodAbi,
TransactionReceiptWithDecodedLogs,
TxData,
TxDataPayable,
SupportedProvider,
} from 'ethereum-types';
import { BigNumber, classUtils, hexUtils, logUtils, providerUtils } from '@0x/utils';
import { EventCallback, IndexedFilterValues, SimpleContractArtifact } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { assert } from '@0x/assert';
import * as ethers from 'ethers';
// tslint:enable:no-unused-variable
/* istanbul ignore next */
// tslint:disable:array-type
// tslint:disable:no-parameter-reassignment
// tslint:disable-next-line:class-name
export class ILiquidityProviderRegistryContract extends BaseContract {
/**
* @ignore
*/
public static deployedBytecode: string | undefined;
public static contractName = 'ILiquidityProviderRegistry';
private readonly _methodABIIndex: { [name: string]: number } = {};
public static async deployFrom0xArtifactAsync(
artifact: ContractArtifact | SimpleContractArtifact,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact },
): Promise<ILiquidityProviderRegistryContract> {
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
if (artifact.compilerOutput === undefined) {
throw new Error('Compiler output not found in the artifact file');
}
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const bytecode = artifact.compilerOutput.evm.bytecode.object;
const abi = artifact.compilerOutput.abi;
const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {};
if (Object.keys(logDecodeDependencies) !== undefined) {
for (const key of Object.keys(logDecodeDependencies)) {
logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi;
}
}
return ILiquidityProviderRegistryContract.deployAsync(
bytecode,
abi,
provider,
txDefaults,
logDecodeDependenciesAbiOnly,
);
}
public static async deployWithLibrariesFrom0xArtifactAsync(
artifact: ContractArtifact,
libraryArtifacts: { [libraryName: string]: ContractArtifact },
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact },
): Promise<ILiquidityProviderRegistryContract> {
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
if (artifact.compilerOutput === undefined) {
throw new Error('Compiler output not found in the artifact file');
}
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const abi = artifact.compilerOutput.abi;
const logDecodeDependenciesAbiOnly: { [contractName: string]: ContractAbi } = {};
if (Object.keys(logDecodeDependencies) !== undefined) {
for (const key of Object.keys(logDecodeDependencies)) {
logDecodeDependenciesAbiOnly[key] = logDecodeDependencies[key].compilerOutput.abi;
}
}
const libraryAddresses = await ILiquidityProviderRegistryContract._deployLibrariesAsync(
artifact,
libraryArtifacts,
new Web3Wrapper(provider),
txDefaults,
);
const bytecode = linkLibrariesInBytecode(artifact, libraryAddresses);
return ILiquidityProviderRegistryContract.deployAsync(
bytecode,
abi,
provider,
txDefaults,
logDecodeDependenciesAbiOnly,
);
}
public static async deployAsync(
bytecode: string,
abi: ContractAbi,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractAbi },
): Promise<ILiquidityProviderRegistryContract> {
assert.isHexString('bytecode', bytecode);
assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [
schemas.addressSchema,
schemas.numberSchema,
schemas.jsNumber,
]);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
const constructorAbi = BaseContract._lookupConstructorAbi(abi);
[] = BaseContract._formatABIDataItemList(constructorAbi.inputs, [], BaseContract._bigNumberToString);
const iface = new ethers.utils.Interface(abi);
const deployInfo = iface.deployFunction;
const txData = deployInfo.encode(bytecode, []);
const web3Wrapper = new Web3Wrapper(provider);
const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync(
{
data: txData,
...txDefaults,
},
web3Wrapper.estimateGasAsync.bind(web3Wrapper),
);
const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults);
logUtils.log(`transactionHash: ${txHash}`);
const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
logUtils.log(`ILiquidityProviderRegistry successfully deployed at ${txReceipt.contractAddress}`);
const contractInstance = new ILiquidityProviderRegistryContract(
txReceipt.contractAddress as string,
provider,
txDefaults,
logDecodeDependencies,
);
contractInstance.constructorArgs = [];
return contractInstance;
}
/**
* @returns The contract ABI
*/
public static ABI(): ContractAbi {
const abi = [
{
constant: true,
inputs: [
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
],
name: 'getLiquidityProviderForMarket',
outputs: [
{
name: 'providerAddress',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
] as ContractAbi;
return abi;
}
protected static async _deployLibrariesAsync(
artifact: ContractArtifact,
libraryArtifacts: { [libraryName: string]: ContractArtifact },
web3Wrapper: Web3Wrapper,
txDefaults: Partial<TxData>,
libraryAddresses: { [libraryName: string]: string } = {},
): Promise<{ [libraryName: string]: string }> {
const links = artifact.compilerOutput.evm.bytecode.linkReferences;
// Go through all linked libraries, recursively deploying them if necessary.
for (const link of Object.values(links)) {
for (const libraryName of Object.keys(link)) {
if (!libraryAddresses[libraryName]) {
// Library not yet deployed.
const libraryArtifact = libraryArtifacts[libraryName];
if (!libraryArtifact) {
throw new Error(`Missing artifact for linked library "${libraryName}"`);
}
// Deploy any dependent libraries used by this library.
await ILiquidityProviderRegistryContract._deployLibrariesAsync(
libraryArtifact,
libraryArtifacts,
web3Wrapper,
txDefaults,
libraryAddresses,
);
// Deploy this library.
const linkedLibraryBytecode = linkLibrariesInBytecode(libraryArtifact, libraryAddresses);
const txDataWithDefaults = await BaseContract._applyDefaultsToContractTxDataAsync(
{
data: linkedLibraryBytecode,
...txDefaults,
},
web3Wrapper.estimateGasAsync.bind(web3Wrapper),
);
const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults);
logUtils.log(`transactionHash: ${txHash}`);
const { contractAddress } = await web3Wrapper.awaitTransactionSuccessAsync(txHash);
logUtils.log(`${libraryArtifact.contractName} successfully deployed at ${contractAddress}`);
libraryAddresses[libraryArtifact.contractName] = contractAddress as string;
}
}
}
return libraryAddresses;
}
public getFunctionSignature(methodName: string): string {
const index = this._methodABIIndex[methodName];
const methodAbi = ILiquidityProviderRegistryContract.ABI()[index] as MethodAbi; // tslint:disable-line:no-unnecessary-type-assertion
const functionSignature = methodAbiToFunctionSignature(methodAbi);
return functionSignature;
}
public getABIDecodedTransactionData<T>(methodName: string, callData: string): T {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ILiquidityProviderRegistryContract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
const abiDecodedCallData = abiEncoder.strictDecode<T>(callData);
return abiDecodedCallData;
}
public getABIDecodedReturnData<T>(methodName: string, callData: string): T {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ILiquidityProviderRegistryContract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
const abiDecodedCallData = abiEncoder.strictDecodeReturnValue<T>(callData);
return abiDecodedCallData;
}
public getSelector(methodName: string): string {
const functionSignature = this.getFunctionSignature(methodName);
const self = (this as any) as ILiquidityProviderRegistryContract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
return abiEncoder.getSelector();
}
/**
* Returns the address of a liquidity provider for the given market
* (takerToken, makerToken), reverting if the pool does not exist.
* @param takerToken Taker asset managed by liquidity provider.
* @param makerToken Maker asset managed by liquidity provider.
* @returns Address of the liquidity provider.
*/
public getLiquidityProviderForMarket(takerToken: string, makerToken: string): ContractFunctionObj<string> {
const self = (this as any) as ILiquidityProviderRegistryContract;
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
const functionSignature = 'getLiquidityProviderForMarket(address,address)';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
takerToken.toLowerCase(),
makerToken.toLowerCase(),
]);
},
};
}
constructor(
address: string,
supportedProvider: SupportedProvider,
txDefaults?: Partial<TxData>,
logDecodeDependencies?: { [contractName: string]: ContractAbi },
deployedBytecode: string | undefined = ILiquidityProviderRegistryContract.deployedBytecode,
) {
super(
'ILiquidityProviderRegistry',
ILiquidityProviderRegistryContract.ABI(),
address,
supportedProvider,
txDefaults,
logDecodeDependencies,
deployedBytecode,
);
classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', '_web3Wrapper']);
ILiquidityProviderRegistryContract.ABI().forEach((item, index) => {
if (item.type === 'function') {
const methodAbi = item as MethodAbi;
this._methodABIIndex[methodAbi.name] = index;
}
});
}
}
// tslint:disable:max-file-line-count
// tslint:enable:no-unbound-method no-parameter-reassignment no-consecutive-blank-lines ordered-imports align
// tslint:enable:trailing-comma whitespace no-trailing-whitespace

View File

@@ -36,7 +36,6 @@ import * as ethers from 'ethers';
// tslint:enable:no-unused-variable
export type IZeroExEventArgs =
| IZeroExLiquidityProviderForMarketUpdatedEventArgs
| IZeroExMetaTransactionExecutedEventArgs
| IZeroExMigratedEventArgs
| IZeroExOwnershipTransferredEventArgs
@@ -46,7 +45,6 @@ export type IZeroExEventArgs =
| IZeroExTransformerDeployerUpdatedEventArgs;
export enum IZeroExEvents {
LiquidityProviderForMarketUpdated = 'LiquidityProviderForMarketUpdated',
MetaTransactionExecuted = 'MetaTransactionExecuted',
Migrated = 'Migrated',
OwnershipTransferred = 'OwnershipTransferred',
@@ -56,12 +54,6 @@ export enum IZeroExEvents {
TransformerDeployerUpdated = 'TransformerDeployerUpdated',
}
export interface IZeroExLiquidityProviderForMarketUpdatedEventArgs extends DecodedLogArgs {
xAsset: string;
yAsset: string;
providerAddress: string;
}
export interface IZeroExMetaTransactionExecutedEventArgs extends DecodedLogArgs {
hash: string;
selector: string;
@@ -219,29 +211,6 @@ export class IZeroExContract extends BaseContract {
*/
public static ABI(): ContractAbi {
const abi = [
{
anonymous: false,
inputs: [
{
name: 'xAsset',
type: 'address',
indexed: true,
},
{
name: 'yAsset',
type: 'address',
indexed: true,
},
{
name: 'providerAddress',
type: 'address',
indexed: false,
},
],
name: 'LiquidityProviderForMarketUpdated',
outputs: [],
type: 'event',
},
{
anonymous: false,
inputs: [
@@ -728,27 +697,6 @@ export class IZeroExContract extends BaseContract {
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
name: 'xAsset',
type: 'address',
},
{
name: 'yAsset',
type: 'address',
},
],
name: 'getLiquidityProviderForMarket',
outputs: [
{
name: 'providerAddress',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
@@ -1062,6 +1010,10 @@ export class IZeroExContract extends BaseContract {
name: 'takerToken',
type: 'address',
},
{
name: 'target',
type: 'address',
},
{
name: 'recipient',
type: 'address',
@@ -1074,6 +1026,10 @@ export class IZeroExContract extends BaseContract {
name: 'minBuyAmount',
type: 'uint256',
},
{
name: 'auxiliaryData',
type: 'bytes',
},
],
name: 'sellToLiquidityProvider',
outputs: [
@@ -1114,26 +1070,6 @@ export class IZeroExContract extends BaseContract {
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
name: 'xAsset',
type: 'address',
},
{
name: 'yAsset',
type: 'address',
},
{
name: 'providerAddress',
type: 'address',
},
],
name: 'setLiquidityProviderForMarket',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
@@ -1848,60 +1784,6 @@ export class IZeroExContract extends BaseContract {
},
};
}
/**
* Returns the address of the liquidity provider for a market given
* (xAsset, yAsset), or reverts if pool does not exist.
* @param xAsset First asset managed by the liquidity provider.
* @param yAsset Second asset managed by the liquidity provider.
*/
public getLiquidityProviderForMarket(xAsset: string, yAsset: string): ContractTxFunctionObj<string> {
const self = (this as any) as IZeroExContract;
assert.isString('xAsset', xAsset);
assert.isString('yAsset', yAsset);
const functionSignature = 'getLiquidityProviderForMarket(address,address)';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
data: this.getABIEncodedTransactionData(),
...txData,
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [xAsset.toLowerCase(), yAsset.toLowerCase()]);
},
};
}
/**
* Get the block at which a meta-transaction has been executed.
* @param mtx The meta-transaction.
@@ -2606,20 +2488,38 @@ export class IZeroExContract extends BaseContract {
},
};
}
/**
* Sells `sellAmount` of `takerToken` to the liquidity provider
* at the given `target`.
* @param makerToken The token being bought.
* @param takerToken The token being sold.
* @param target The address of the on-chain liquidity provider to trade
* with.
* @param recipient The recipient of the bought tokens. If equal to
* address(0), `msg.sender` is assumed to be the recipient.
* @param sellAmount The amount of `takerToken` to sell.
* @param minBuyAmount The minimum acceptable amount of `makerToken` to
* buy. Reverts if this amount is not satisfied.
* @param auxiliaryData Auxiliary data supplied to the `target` contract.
*/
public sellToLiquidityProvider(
makerToken: string,
takerToken: string,
target: string,
recipient: string,
sellAmount: BigNumber,
minBuyAmount: BigNumber,
auxiliaryData: string,
): ContractTxFunctionObj<BigNumber> {
const self = (this as any) as IZeroExContract;
assert.isString('makerToken', makerToken);
assert.isString('takerToken', takerToken);
assert.isString('target', target);
assert.isString('recipient', recipient);
assert.isBigNumber('sellAmount', sellAmount);
assert.isBigNumber('minBuyAmount', minBuyAmount);
const functionSignature = 'sellToLiquidityProvider(address,address,address,uint256,uint256)';
assert.isString('auxiliaryData', auxiliaryData);
const functionSignature = 'sellToLiquidityProvider(address,address,address,address,uint256,uint256,bytes)';
return {
async sendTransactionAsync(
@@ -2662,9 +2562,11 @@ export class IZeroExContract extends BaseContract {
return self._strictEncodeArguments(functionSignature, [
makerToken.toLowerCase(),
takerToken.toLowerCase(),
target.toLowerCase(),
recipient.toLowerCase(),
sellAmount,
minBuyAmount,
auxiliaryData,
]);
},
};
@@ -2731,70 +2633,6 @@ export class IZeroExContract extends BaseContract {
},
};
}
/**
* Sets address of the liquidity provider for a market given
* (xAsset, yAsset).
* @param xAsset First asset managed by the liquidity provider.
* @param yAsset Second asset managed by the liquidity provider.
* @param providerAddress Address of the liquidity provider.
*/
public setLiquidityProviderForMarket(
xAsset: string,
yAsset: string,
providerAddress: string,
): ContractTxFunctionObj<void> {
const self = (this as any) as IZeroExContract;
assert.isString('xAsset', xAsset);
assert.isString('yAsset', yAsset);
assert.isString('providerAddress', providerAddress);
const functionSignature = 'setLiquidityProviderForMarket(address,address,address)';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ data: this.getABIEncodedTransactionData(), ...txData },
this.estimateGasAsync.bind(this),
);
if (opts.shouldValidate !== false) {
await this.callAsync(txDataWithDefaults);
}
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
},
awaitTransactionSuccessAsync(
txData?: Partial<TxData>,
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
},
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
data: this.getABIEncodedTransactionData(),
...txData,
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<void> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ data: this.getABIEncodedTransactionData(), ...callData },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<void>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
xAsset.toLowerCase(),
yAsset.toLowerCase(),
providerAddress.toLowerCase(),
]);
},
};
}
/**
* Replace the optional signer for `transformERC20()` calldata.
* Only callable by the owner.

View File

@@ -125,7 +125,6 @@ export {
IZeroExContract,
IZeroExEventArgs,
IZeroExEvents,
IZeroExLiquidityProviderForMarketUpdatedEventArgs,
IZeroExMetaTransactionExecutedEventArgs,
IZeroExMigratedEventArgs,
IZeroExOwnershipTransferredEventArgs,

View File

@@ -1,4 +1,13 @@
[
{
"version": "6.5.0",
"changes": [
{
"note": "Add `exchangeProxyLiquidityProviderSandbox` address",
"pr": 16
}
]
},
{
"timestamp": 1604385937,
"version": "6.4.7",

View File

@@ -402,6 +402,7 @@ export async function runMigrationsAsync(
exchangeProxyAllowanceTarget: exchangeProxyAllowanceTargetAddress,
exchangeProxyTransformerDeployer: txDefaults.from,
exchangeProxyFlashWallet: exchangeProxyFlashWalletAddress,
exchangeProxyLiquidityProviderSandbox: NULL_ADDRESS,
transformers: {
wethTransformer: wethTransformer.address,
payTakerTransformer: payTakerTransformer.address,