Merge pull request #2505 from 0xProject/feature/plp-integration

🔂 Liquidity Provider Asset Swapper integration
This commit is contained in:
Daniel Pyrathon
2020-03-06 12:34:45 -08:00
committed by GitHub
31 changed files with 3901 additions and 305 deletions

View File

@@ -0,0 +1,38 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
contract DummyLiquidityProvider
{
/// @dev Quotes the amount of `makerToken` that would be obtained by
/// selling `sellAmount` of `takerToken`.
/// @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)
{
makerTokenAmount = sellAmount - 1;
}
/// @dev Quotes the amount of `takerToken` that would need to be sold in
/// order to obtain `buyAmount` of `makerToken`.
/// @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)
{
takerTokenAmount = buyAmount + 1;
}
}

View File

@@ -0,0 +1,44 @@
pragma solidity ^0.5.9;
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 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 @@
"compile:truffle": "truffle compile"
},
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry",
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
"abis": "./test/generated-artifacts/@(DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IUniswapExchangeQuotes|TestERC20BridgeSampler).json"
},
"repository": {
"type": "git",

View File

@@ -5,6 +5,8 @@
*/
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 IERC20BridgeSampler from '../generated-artifacts/IERC20BridgeSampler.json';
import * as ILiquidityProvider from '../generated-artifacts/ILiquidityProvider.json';
@@ -14,4 +16,6 @@ export const artifacts = {
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
};

View File

@@ -3,6 +3,8 @@
* 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_erc20_bridge_sampler';
export * from '../generated-wrappers/i_liquidity_provider';

View File

@@ -5,6 +5,8 @@
*/
import { ContractArtifact } from 'ethereum-types';
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 ICurve from '../test/generated-artifacts/ICurve.json';
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
@@ -16,6 +18,8 @@ import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquid
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
export const artifacts = {
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
ICurve: ICurve as ContractArtifact,
IDevUtils: IDevUtils as ContractArtifact,

View File

@@ -11,7 +11,11 @@ import { BigNumber, hexUtils } from '@0x/utils';
import * as _ from 'lodash';
import { artifacts } from './artifacts';
import { TestERC20BridgeSamplerContract } from './wrappers';
import {
DummyLiquidityProviderContract,
DummyLiquidityProviderRegistryContract,
TestERC20BridgeSamplerContract,
} from './wrappers';
blockchainTests('erc20-bridge-sampler', env => {
let testContract: TestERC20BridgeSamplerContract;
@@ -716,6 +720,90 @@ blockchainTests('erc20-bridge-sampler', env => {
});
});
describe('getLiquidityProviderFromRegistry', () => {
const xAsset = randomAddress();
const yAsset = randomAddress();
const sampleAmounts = getSampleAmounts(yAsset);
let liquidityProvider: DummyLiquidityProviderContract;
let registryContract: DummyLiquidityProviderRegistryContract;
before(async () => {
liquidityProvider = await DummyLiquidityProviderContract.deployFrom0xArtifactAsync(
artifacts.DummyLiquidityProvider,
env.provider,
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 get the liquidity provider', async () => {
const xyLiquidityProvider = await testContract
.getLiquidityProviderFromRegistry(registryContract.address, xAsset, yAsset)
.callAsync();
const yxLiquidityProvider = await testContract
.getLiquidityProviderFromRegistry(registryContract.address, yAsset, xAsset)
.callAsync();
const unknownLiquidityProvider = await testContract
.getLiquidityProviderFromRegistry(registryContract.address, yAsset, randomAddress())
.callAsync();
expect(xyLiquidityProvider).to.eq(liquidityProvider.address);
expect(yxLiquidityProvider).to.eq(liquidityProvider.address);
expect(unknownLiquidityProvider).to.eq(constants.NULL_ADDRESS);
});
it('should be able to query sells from the liquidity provider', async () => {
const result = await testContract
.sampleSellsFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
.callAsync();
result.forEach((value, idx) => {
expect(value).is.bignumber.eql(sampleAmounts[idx].minus(1));
});
});
it('should be able to query buys from the liquidity provider', async () => {
const result = await testContract
.sampleBuysFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
.callAsync();
result.forEach((value, idx) => {
expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1));
});
});
it('should just return zeros if the liquidity provider cannot be found', async () => {
const result = await testContract
.sampleBuysFromLiquidityProviderRegistry(
registryContract.address,
yAsset,
randomAddress(),
sampleAmounts,
)
.callAsync();
result.forEach(value => {
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
});
});
it('should just return zeros if the registry does not exist', async () => {
const result = await testContract
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts)
.callAsync();
result.forEach(value => {
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
});
});
});
describe('batchCall()', () => {
it('can call one function', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);

View File

@@ -3,6 +3,8 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* -----------------------------------------------------------------------------
*/
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/i_curve';
export * from '../test/generated-wrappers/i_dev_utils';

View File

@@ -3,10 +3,14 @@
"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/IERC20BridgeSampler.json",
"generated-artifacts/ILiquidityProvider.json",
"generated-artifacts/ILiquidityProviderRegistry.json",
"test/generated-artifacts/DummyLiquidityProvider.json",
"test/generated-artifacts/DummyLiquidityProviderRegistry.json",
"test/generated-artifacts/ERC20BridgeSampler.json",
"test/generated-artifacts/ICurve.json",
"test/generated-artifacts/IDevUtils.json",

View File

@@ -21,8 +21,9 @@ import {
} from './types';
import { assert } from './utils/assert';
import { calculateLiquidity } from './utils/calculate_liquidity';
import { DexOrderSampler, MarketOperationUtils } from './utils/market_operation_utils';
import { MarketOperationUtils } from './utils/market_operation_utils';
import { dummyOrderUtils } from './utils/market_operation_utils/dummy_order_utils';
import { DexOrderSampler } from './utils/market_operation_utils/sampler';
import { orderPrunerUtils } from './utils/order_prune_utils';
import { OrderStateUtils } from './utils/order_state_utils';
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
@@ -144,11 +145,13 @@ export class SwapQuoter {
* @return An instance of SwapQuoter
*/
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
const { chainId, expiryBufferMs, permittedOrderFeeTypes, samplerGasLimit } = _.merge(
{},
constants.DEFAULT_SWAP_QUOTER_OPTS,
options,
);
const {
chainId,
expiryBufferMs,
permittedOrderFeeTypes,
samplerGasLimit,
liquidityProviderRegistryAddress,
} = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isValidOrderbook('orderbook', orderbook);
assert.isNumber('chainId', chainId);
@@ -167,10 +170,15 @@ export class SwapQuoter {
gas: samplerGasLimit,
}),
);
this._marketOperationUtils = new MarketOperationUtils(sampler, this._contractAddresses, {
chainId,
exchangeAddress: this._contractAddresses.exchange,
});
this._marketOperationUtils = new MarketOperationUtils(
sampler,
this._contractAddresses,
{
chainId,
exchangeAddress: this._contractAddresses.exchange,
},
liquidityProviderRegistryAddress,
);
this._swapQuoteCalculator = new SwapQuoteCalculator(this._protocolFeeUtils, this._marketOperationUtils);
}

View File

@@ -212,6 +212,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
expiryBufferMs: number;
contractAddresses?: ContractAddresses;
samplerGasLimit?: number;
liquidityProviderRegistryAddress?: string;
}
/**

View File

@@ -1,3 +1,4 @@
import { assert } from '@0x/assert';
import { ContractAddresses } from '@0x/contract-addresses';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
@@ -48,6 +49,7 @@ export class CreateOrderUtils {
outputToken: string,
path: CollapsedFill[],
bridgeSlippage: number,
liquidityProviderAddress?: string,
): OptimizedMarketOrder[] {
const orders: OptimizedMarketOrder[] = [];
for (const fill of path) {
@@ -58,7 +60,7 @@ export class CreateOrderUtils {
createBridgeOrder(
orderDomain,
fill,
this._getBridgeAddressFromSource(fill.source),
this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress),
outputToken,
inputToken,
bridgeSlippage,
@@ -76,6 +78,7 @@ export class CreateOrderUtils {
outputToken: string,
path: CollapsedFill[],
bridgeSlippage: number,
liquidityProviderAddress?: string,
): OptimizedMarketOrder[] {
const orders: OptimizedMarketOrder[] = [];
for (const fill of path) {
@@ -86,7 +89,7 @@ export class CreateOrderUtils {
createBridgeOrder(
orderDomain,
fill,
this._getBridgeAddressFromSource(fill.source),
this._getBridgeAddressFromSource(fill.source, liquidityProviderAddress),
inputToken,
outputToken,
bridgeSlippage,
@@ -98,7 +101,7 @@ export class CreateOrderUtils {
return orders;
}
private _getBridgeAddressFromSource(source: ERC20BridgeSource): string {
private _getBridgeAddressFromSource(source: ERC20BridgeSource, liquidityProviderAddress?: string): string {
switch (source) {
case ERC20BridgeSource.Eth2Dai:
return this._contractAddress.eth2DaiBridge;
@@ -111,6 +114,14 @@ export class CreateOrderUtils {
case ERC20BridgeSource.CurveUsdcDaiUsdtTusd:
case ERC20BridgeSource.CurveUsdcDaiUsdtBusd:
return this._contractAddress.curveBridge;
case ERC20BridgeSource.LiquidityProvider:
if (liquidityProviderAddress === undefined) {
throw new Error(
'Cannot create a LiquidityProvider order without a LiquidityProvider pool address.',
);
}
assert.isETHAddressHex('liquidityProviderAddress', liquidityProviderAddress);
return liquidityProviderAddress;
default:
break;
}

View File

@@ -1,7 +1,7 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { assetDataUtils, ERC20AssetData, orderCalculationUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
import { constants } from '../../constants';
import { MarketOperation, SignedOrderWithFillableAmounts } from '../../types';
@@ -46,6 +46,7 @@ export class MarketOperationUtils {
private readonly _sampler: DexOrderSampler,
contractAddresses: ContractAddresses,
private readonly _orderDomain: OrderDomain,
private readonly _liquidityProviderRegistry: string = NULL_ADDRESS,
) {
this._createOrderUtils = new CreateOrderUtils(contractAddresses);
this._wethAddress = contractAddresses.etherToken;
@@ -72,23 +73,40 @@ export class MarketOperationUtils {
...opts,
};
const [makerToken, takerToken] = getOrderTokens(nativeOrders[0]);
const [fillableAmounts, ethToMakerAssetRate, dexQuotes] = await this._sampler.executeAsync(
const [
fillableAmounts,
liquidityProviderAddress,
ethToMakerAssetRate,
dexQuotes,
] = await this._sampler.executeAsync(
DexOrderSampler.ops.getOrderFillableTakerAmounts(nativeOrders),
DexOrderSampler.ops.getLiquidityProviderFromRegistry(
this._liquidityProviderRegistry,
takerToken,
makerToken,
),
makerToken.toLowerCase() === this._wethAddress.toLowerCase()
? DexOrderSampler.ops.constant(new BigNumber(1))
: DexOrderSampler.ops.getMedianSellRate(
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
makerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
),
DexOrderSampler.ops.getSellQuotes(
difference(SELL_SOURCES, _opts.excludedSources),
difference(SELL_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
makerToken,
takerToken,
getSampleAmounts(takerAmount, _opts.numSamples, _opts.sampleDistributionBase),
this._liquidityProviderRegistry,
),
);
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
nativeOrders,
fillableAmounts,
@@ -130,6 +148,7 @@ export class MarketOperationUtils {
makerToken,
collapsePath(optimalPath, false),
_opts.bridgeSlippage,
liquidityProviderAddress,
);
}
@@ -154,21 +173,37 @@ export class MarketOperationUtils {
...opts,
};
const [makerToken, takerToken] = getOrderTokens(nativeOrders[0]);
const [fillableAmounts, ethToTakerAssetRate, dexQuotes] = await this._sampler.executeAsync(
const [
fillableAmounts,
liquidityProviderAddress,
ethToTakerAssetRate,
dexQuotes,
] = await this._sampler.executeAsync(
DexOrderSampler.ops.getOrderFillableMakerAmounts(nativeOrders),
DexOrderSampler.ops.getLiquidityProviderFromRegistry(
this._liquidityProviderRegistry,
takerToken,
makerToken,
),
takerToken.toLowerCase() === this._wethAddress.toLowerCase()
? DexOrderSampler.ops.constant(new BigNumber(1))
: DexOrderSampler.ops.getMedianSellRate(
difference(FEE_QUOTE_SOURCES, _opts.excludedSources),
difference(FEE_QUOTE_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
takerToken,
this._wethAddress,
ONE_ETHER,
this._liquidityProviderRegistry,
),
DexOrderSampler.ops.getBuyQuotes(
difference(BUY_SOURCES, _opts.excludedSources),
difference(BUY_SOURCES, _opts.excludedSources).concat(
this._liquidityProviderSourceIfAvailable(_opts.excludedSources),
),
makerToken,
takerToken,
getSampleAmounts(makerAmount, _opts.numSamples, _opts.sampleDistributionBase),
this._liquidityProviderRegistry,
),
);
const signedOrderWithFillableAmounts = this._createBuyOrdersPathFromSamplerResultIfExists(
@@ -178,6 +213,7 @@ export class MarketOperationUtils {
dexQuotes,
ethToTakerAssetRate,
_opts,
liquidityProviderAddress,
);
if (!signedOrderWithFillableAmounts) {
throw new Error(AggregationError.NoOptimalPath);
@@ -188,6 +224,9 @@ export class MarketOperationUtils {
/**
* gets the orders required for a batch of market buy operations by (potentially) merging native orders with
* generated bridge orders.
*
* NOTE: Currently `getBatchMarketBuyOrdersAsync()` does not support external liquidity providers.
*
* @param batchNativeOrders Batch of Native orders.
* @param makerAmounts Array amount of maker asset to buy for each batch.
* @param opts Options object.
@@ -240,6 +279,13 @@ export class MarketOperationUtils {
);
}
private _liquidityProviderSourceIfAvailable(excludedSources: ERC20BridgeSource[]): ERC20BridgeSource[] {
return this._liquidityProviderRegistry !== NULL_ADDRESS &&
!excludedSources.includes(ERC20BridgeSource.LiquidityProvider)
? [ERC20BridgeSource.LiquidityProvider]
: [];
}
private _createBuyOrdersPathFromSamplerResultIfExists(
nativeOrders: SignedOrder[],
makerAmount: BigNumber,
@@ -247,6 +293,7 @@ export class MarketOperationUtils {
dexQuotes: DexSample[][],
ethToTakerAssetRate: BigNumber,
opts: GetMarketOrdersOpts,
liquidityProviderAddress?: string,
): OptimizedMarketOrder[] | undefined {
const nativeOrdersWithFillableAmounts = createSignedOrdersWithFillableAmounts(
nativeOrders,
@@ -289,6 +336,7 @@ export class MarketOperationUtils {
outputToken,
collapsePath(optimalPath, true),
opts.bridgeSlippage,
liquidityProviderAddress,
);
}
}
@@ -470,6 +518,9 @@ function sourceToFillFlags(source: ERC20BridgeSource): number {
if (source === ERC20BridgeSource.Uniswap) {
return FillFlags.SourceUniswap;
}
if (source === ERC20BridgeSource.LiquidityProvider) {
return FillFlags.SourceLiquidityPool;
}
return FillFlags.SourceNative;
}

View File

@@ -1,283 +1,8 @@
import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { constants } from '../../constants';
import { DexSample, ERC20BridgeSource } from './types';
/**
* A composable operation the be run in `DexOrderSampler.executeAsync()`.
*/
export interface BatchedOperation<TResult> {
encodeCall(contract: IERC20BridgeSamplerContract): string;
handleCallResultsAsync(contract: IERC20BridgeSamplerContract, callResults: string): Promise<TResult>;
}
/**
* Composable operations that can be batched in a single transaction,
* for use with `DexOrderSampler.executeAsync()`.
*/
const samplerOperations = {
getOrderFillableTakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.getOrderFillableTakerAssetAmounts(orders, orders.map(o => o.signature))
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableTakerAssetAmounts', callResults);
},
};
},
getOrderFillableMakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.getOrderFillableMakerAssetAmounts(orders, orders.map(o => o.signature))
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableMakerAssetAmounts', callResults);
},
};
},
getKyberSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromKyberNetwork(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromKyberNetwork', callResults);
},
};
},
getUniswapSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromUniswap(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromUniswap', callResults);
},
};
},
getEth2DaiSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromEth2Dai(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromEth2Dai', callResults);
},
};
},
getCurveSellQuotes(
curveAddress: string,
fromTokenIdx: number,
toTokenIdx: number,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromCurve(
curveAddress,
new BigNumber(fromTokenIdx),
new BigNumber(toTokenIdx),
takerFillAmounts,
)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromCurve', callResults);
},
};
},
getUniswapBuyQuotes(
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleBuysFromUniswap(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromUniswap', callResults);
},
};
},
getEth2DaiBuyQuotes(
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleBuysFromEth2Dai(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromEth2Dai', callResults);
},
};
},
getMedianSellRate(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
takerFillAmount: BigNumber,
): BatchedOperation<BigNumber> {
const getSellQuotes = samplerOperations.getSellQuotes(sources, makerToken, takerToken, [takerFillAmount]);
return {
encodeCall: contract => {
const subCalls = [getSellQuotes.encodeCall(contract)];
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await getSellQuotes.handleCallResultsAsync(contract, rawSubCallResults[0]);
if (samples.length === 0) {
return new BigNumber(0);
}
const flatSortedSamples = samples
.reduce((acc, v) => acc.concat(...v))
.sort((a, b) => a.output.comparedTo(b.output));
if (flatSortedSamples.length === 0) {
return new BigNumber(0);
}
const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
return medianSample.output.div(medianSample.input);
},
};
},
constant<T>(result: T): BatchedOperation<T> {
return {
encodeCall: contract => {
return '0x';
},
handleCallResultsAsync: async (contract, callResults) => {
return result;
},
};
},
getSellQuotes(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<DexSample[][]> {
const subOps = sources
.map(source => {
let batchedOperation;
if (source === ERC20BridgeSource.Eth2Dai) {
batchedOperation = samplerOperations.getEth2DaiSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (source === ERC20BridgeSource.Uniswap) {
batchedOperation = samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (source === ERC20BridgeSource.Kyber) {
batchedOperation = samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(source)) {
const { curveAddress, tokens } = constants.DEFAULT_CURVE_OPTS[source];
const fromTokenIdx = tokens.indexOf(takerToken);
const toTokenIdx = tokens.indexOf(makerToken);
if (fromTokenIdx !== -1 && toTokenIdx !== -1) {
batchedOperation = samplerOperations.getCurveSellQuotes(
curveAddress,
fromTokenIdx,
toTokenIdx,
takerFillAmounts,
);
}
} else {
throw new Error(`Unsupported sell sample source: ${source}`);
}
return { batchedOperation, source };
})
.filter(op => op.batchedOperation) as Array<{
batchedOperation: BatchedOperation<BigNumber[]>;
source: ERC20BridgeSource;
}>;
return {
encodeCall: contract => {
const subCalls = subOps.map(op => op.batchedOperation.encodeCall(contract));
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await Promise.all(
subOps.map(async (op, i) =>
op.batchedOperation.handleCallResultsAsync(contract, rawSubCallResults[i]),
),
);
return subOps.map((op, i) => {
return samples[i].map((output, j) => ({
source: op.source,
output,
input: takerFillAmounts[j],
}));
});
},
};
},
getBuyQuotes(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<DexSample[][]> {
const subOps = sources.map(source => {
if (source === ERC20BridgeSource.Eth2Dai) {
return samplerOperations.getEth2DaiBuyQuotes(makerToken, takerToken, makerFillAmounts);
} else if (source === ERC20BridgeSource.Uniswap) {
return samplerOperations.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts);
} else {
throw new Error(`Unsupported buy sample source: ${source}`);
}
});
return {
encodeCall: contract => {
const subCalls = subOps.map(op => op.encodeCall(contract));
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await Promise.all(
subOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])),
);
return sources.map((source, i) => {
return samples[i].map((output, j) => ({
source,
output,
input: makerFillAmounts[j],
}));
});
},
};
},
};
import { samplerOperations } from './sampler_operations';
import { BatchedOperation } from './types';
/**
* Generate sample amounts up to `maxFillAmount`.

View File

@@ -0,0 +1,368 @@
import { BigNumber, ERC20BridgeSource, SignedOrder } from '../..';
import { constants } from '../../constants';
import { BatchedOperation, DexSample } from './types';
/**
* Composable operations that can be batched in a single transaction,
* for use with `DexOrderSampler.executeAsync()`.
*/
export const samplerOperations = {
getOrderFillableTakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.getOrderFillableTakerAssetAmounts(orders, orders.map(o => o.signature))
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableTakerAssetAmounts', callResults);
},
};
},
getOrderFillableMakerAmounts(orders: SignedOrder[]): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.getOrderFillableMakerAssetAmounts(orders, orders.map(o => o.signature))
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('getOrderFillableMakerAssetAmounts', callResults);
},
};
},
getKyberSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromKyberNetwork(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromKyberNetwork', callResults);
},
};
},
getUniswapSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromUniswap(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromUniswap', callResults);
},
};
},
getLiquidityProviderSellQuotes(
liquidityProviderRegistryAddress: string,
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromLiquidityProviderRegistry(
liquidityProviderRegistryAddress,
takerToken,
makerToken,
takerFillAmounts,
)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>(
'sampleSellsFromLiquidityProviderRegistry',
callResults,
);
},
};
},
getLiquidityProviderBuyQuotes(
liquidityProviderRegistryAddress: string,
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleBuysFromLiquidityProviderRegistry(
liquidityProviderRegistryAddress,
takerToken,
makerToken,
makerFillAmounts,
)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>(
'sampleBuysFromLiquidityProviderRegistry',
callResults,
);
},
};
},
getEth2DaiSellQuotes(
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromEth2Dai(takerToken, makerToken, takerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromEth2Dai', callResults);
},
};
},
getCurveSellQuotes(
curveAddress: string,
fromTokenIdx: number,
toTokenIdx: number,
takerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleSellsFromCurve(
curveAddress,
new BigNumber(fromTokenIdx),
new BigNumber(toTokenIdx),
takerFillAmounts,
)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleSellsFromCurve', callResults);
},
};
},
getUniswapBuyQuotes(
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleBuysFromUniswap(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromUniswap', callResults);
},
};
},
getEth2DaiBuyQuotes(
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
): BatchedOperation<BigNumber[]> {
return {
encodeCall: contract => {
return contract
.sampleBuysFromEth2Dai(takerToken, makerToken, makerFillAmounts)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<BigNumber[]>('sampleBuysFromEth2Dai', callResults);
},
};
},
getMedianSellRate(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
takerFillAmount: BigNumber,
liquidityProviderRegistryAddress?: string | undefined,
): BatchedOperation<BigNumber> {
const getSellQuotes = samplerOperations.getSellQuotes(
sources,
makerToken,
takerToken,
[takerFillAmount],
liquidityProviderRegistryAddress,
);
return {
encodeCall: contract => {
const subCalls = [getSellQuotes.encodeCall(contract)];
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await getSellQuotes.handleCallResultsAsync(contract, rawSubCallResults[0]);
if (samples.length === 0) {
return new BigNumber(0);
}
const flatSortedSamples = samples
.reduce((acc, v) => acc.concat(...v))
.sort((a, b) => a.output.comparedTo(b.output));
if (flatSortedSamples.length === 0) {
return new BigNumber(0);
}
const medianSample = flatSortedSamples[Math.floor(flatSortedSamples.length / 2)];
return medianSample.output.div(medianSample.input);
},
};
},
constant<T>(result: T): BatchedOperation<T> {
return {
encodeCall: contract => {
return '0x';
},
handleCallResultsAsync: async (contract, callResults) => {
return result;
},
};
},
getLiquidityProviderFromRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
): BatchedOperation<string> {
return {
encodeCall: contract => {
return contract
.getLiquidityProviderFromRegistry(registryAddress, takerToken, makerToken)
.getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
return contract.getABIDecodedReturnData<string>('getLiquidityProviderFromRegistry', callResults);
},
};
},
getSellQuotes(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
takerFillAmounts: BigNumber[],
liquidityProviderRegistryAddress?: string | undefined,
): BatchedOperation<DexSample[][]> {
const subOps = sources
.map(source => {
let batchedOperation;
if (source === ERC20BridgeSource.Eth2Dai) {
batchedOperation = samplerOperations.getEth2DaiSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (source === ERC20BridgeSource.Uniswap) {
batchedOperation = samplerOperations.getUniswapSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (source === ERC20BridgeSource.Kyber) {
batchedOperation = samplerOperations.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts);
} else if (Object.keys(constants.DEFAULT_CURVE_OPTS).includes(source)) {
const { curveAddress, tokens } = constants.DEFAULT_CURVE_OPTS[source];
const fromTokenIdx = tokens.indexOf(takerToken);
const toTokenIdx = tokens.indexOf(makerToken);
if (fromTokenIdx !== -1 && toTokenIdx !== -1) {
batchedOperation = samplerOperations.getCurveSellQuotes(
curveAddress,
fromTokenIdx,
toTokenIdx,
takerFillAmounts,
);
}
} else if (source === ERC20BridgeSource.LiquidityProvider) {
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(
'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.',
);
}
batchedOperation = samplerOperations.getLiquidityProviderSellQuotes(
liquidityProviderRegistryAddress,
makerToken,
takerToken,
takerFillAmounts,
);
} else {
throw new Error(`Unsupported sell sample source: ${source}`);
}
return { batchedOperation, source };
})
.filter(op => op.batchedOperation) as Array<{
batchedOperation: BatchedOperation<BigNumber[]>;
source: ERC20BridgeSource;
}>;
return {
encodeCall: contract => {
const subCalls = subOps.map(op => op.batchedOperation.encodeCall(contract));
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await Promise.all(
subOps.map(async (op, i) =>
op.batchedOperation.handleCallResultsAsync(contract, rawSubCallResults[i]),
),
);
return subOps.map((op, i) => {
return samples[i].map((output, j) => ({
source: op.source,
output,
input: takerFillAmounts[j],
}));
});
},
};
},
getBuyQuotes(
sources: ERC20BridgeSource[],
makerToken: string,
takerToken: string,
makerFillAmounts: BigNumber[],
liquidityProviderRegistryAddress?: string | undefined,
): BatchedOperation<DexSample[][]> {
const subOps = sources.map(source => {
if (source === ERC20BridgeSource.Eth2Dai) {
return samplerOperations.getEth2DaiBuyQuotes(makerToken, takerToken, makerFillAmounts);
} else if (source === ERC20BridgeSource.Uniswap) {
return samplerOperations.getUniswapBuyQuotes(makerToken, takerToken, makerFillAmounts);
} else if (source === ERC20BridgeSource.LiquidityProvider) {
if (liquidityProviderRegistryAddress === undefined) {
throw new Error(
'Cannot sample liquidity from a LiquidityProvider liquidity pool, if a registry is not provided.',
);
}
return samplerOperations.getLiquidityProviderBuyQuotes(
liquidityProviderRegistryAddress,
makerToken,
takerToken,
makerFillAmounts,
);
} else {
throw new Error(`Unsupported buy sample source: ${source}`);
}
});
return {
encodeCall: contract => {
const subCalls = subOps.map(op => op.encodeCall(contract));
return contract.batchCall(subCalls).getABIEncodedTransactionData();
},
handleCallResultsAsync: async (contract, callResults) => {
const rawSubCallResults = contract.getABIDecodedReturnData<string[]>('batchCall', callResults);
const samples = await Promise.all(
subOps.map(async (op, i) => op.handleCallResultsAsync(contract, rawSubCallResults[i])),
);
return sources.map((source, i) => {
return samples[i].map((output, j) => ({
source,
output,
input: makerFillAmounts[j],
}));
});
},
};
},
};

View File

@@ -1,3 +1,4 @@
import { IERC20BridgeSamplerContract } from '@0x/contract-wrappers';
import { BigNumber } from '@0x/utils';
import { SignedOrderWithFillableAmounts } from '../../types';
@@ -31,6 +32,7 @@ export enum ERC20BridgeSource {
CurveUsdcDai = 'Curve_USDC_DAI',
CurveUsdcDaiUsdt = 'Curve_USDC_DAI_USDT',
CurveUsdcDaiUsdtTusd = 'Curve_USDC_DAI_USDT_TUSD',
LiquidityProvider = 'LiquidityProvider',
CurveUsdcDaiUsdtBusd = 'Curve_USDC_DAI_USDT_BUSD',
}
@@ -61,6 +63,7 @@ export enum FillFlags {
SourceUniswap = 0x2,
SourceEth2Dai = 0x4,
SourceKyber = 0x8,
SourceLiquidityPool = 0x10,
}
/**
@@ -175,3 +178,11 @@ export interface GetMarketOrdersOpts {
*/
fees: { [source: string]: BigNumber };
}
/**
* A composable operation the be run in `DexOrderSampler.executeAsync()`.
*/
export interface BatchedOperation<TResult> {
encodeCall(contract: IERC20BridgeSamplerContract): string;
handleCallResultsAsync(contract: IERC20BridgeSamplerContract, callResults: string): Promise<TResult>;
}

View File

@@ -1,4 +1,11 @@
import { constants, expect, getRandomFloat, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
import {
constants,
expect,
getRandomFloat,
getRandomInteger,
randomAddress,
toBaseUnitAmount,
} from '@0x/contracts-test-utils';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils';
@@ -134,6 +141,72 @@ describe('DexSampler tests', () => {
expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts);
});
it('getLiquidityProviderSellQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const sampler = new MockSamplerContract({
sampleSellsFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => {
expect(registryAddress).to.eq(registry);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [toBaseUnitAmount(1001)];
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getSellQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
registry,
),
);
expect(result).to.deep.equal([
[
{
source: 'LiquidityProvider',
output: toBaseUnitAmount(1001),
input: toBaseUnitAmount(1000),
},
],
]);
});
it('getLiquidityProviderBuyQuotes()', async () => {
const expectedMakerToken = randomAddress();
const expectedTakerToken = randomAddress();
const registry = randomAddress();
const sampler = new MockSamplerContract({
sampleBuysFromLiquidityProviderRegistry: (registryAddress, takerToken, makerToken, fillAmounts) => {
expect(registryAddress).to.eq(registry);
expect(takerToken).to.eq(expectedTakerToken);
expect(makerToken).to.eq(expectedMakerToken);
return [toBaseUnitAmount(999)];
},
});
const dexOrderSampler = new DexOrderSampler(sampler);
const [result] = await dexOrderSampler.executeAsync(
DexOrderSampler.ops.getBuyQuotes(
[ERC20BridgeSource.LiquidityProvider],
expectedMakerToken,
expectedTakerToken,
[toBaseUnitAmount(1000)],
registry,
),
);
expect(result).to.deep.equal([
[
{
source: 'LiquidityProvider',
output: toBaseUnitAmount(999),
input: toBaseUnitAmount(1000),
},
],
]);
});
it('getEth2DaiSellQuotes()', async () => {
const expectedTakerToken = randomAddress();
const expectedMakerToken = randomAddress();

View File

@@ -8,10 +8,10 @@ import {
Numberish,
randomAddress,
} from '@0x/contracts-test-utils';
import { Web3Wrapper } from '@0x/dev-utils';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber, hexUtils } from '@0x/utils';
import { AssetProxyId, ERC20BridgeAssetData, SignedOrder } from '@0x/types';
import { BigNumber, fromTokenUnitAmount, hexUtils, NULL_ADDRESS } from '@0x/utils';
import * as _ from 'lodash';
import { constants as assetSwapperConstants } from '../src/constants';
@@ -141,6 +141,7 @@ describe('MarketOperationUtils tests', () => {
makerToken: string,
takerToken: string,
fillAmounts: BigNumber[],
liquidityProviderAddress?: string,
) => DexSample[][];
function createGetMultipleSellQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
@@ -155,6 +156,28 @@ 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[],
liquidityProviderAddress?: string,
) => {
liquidityPoolParams.liquidityProviderAddress = liquidityProviderAddress;
liquidityPoolParams.sources = sources;
return tradeOperation(rates)(sources, makerToken, takerToken, fillAmounts, liquidityProviderAddress);
};
return [liquidityPoolParams, fn];
}
function createGetMultipleBuyQuotesOperationFromRates(rates: RatesBySource): GetMultipleQuotesOperation {
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
return sources.map(s =>
@@ -172,14 +195,47 @@ describe('MarketOperationUtils tests', () => {
makerToken: string,
takerToken: string,
fillAmounts: BigNumber[],
liquidityProviderAddress?: string,
) => BigNumber;
type GetLiquidityProviderFromRegistryOperation = (
registryAddress: string,
takerToken: string,
makerToken: string,
) => string;
function createGetMedianSellRate(rate: Numberish): GetMedianRateOperation {
return (sources: ERC20BridgeSource[], makerToken: string, takerToken: string, fillAmounts: BigNumber[]) => {
return new BigNumber(rate);
};
}
function getLiquidityProviderFromRegistry(): GetLiquidityProviderFromRegistryOperation {
return (registryAddress: string, takerToken: string, makerToken: string): string => {
return NULL_ADDRESS;
};
}
function getLiquidityProviderFromRegistryAndReturnCallParameters(
liquidityProviderAddress: string = NULL_ADDRESS,
): [
{ registryAddress?: string; takerToken?: string; makerToken?: string },
GetLiquidityProviderFromRegistryOperation
] {
const callArgs: { registryAddress?: string; takerToken?: string; makerToken?: string } = {
registryAddress: undefined,
takerToken: undefined,
makerToken: undefined,
};
const fn = (registryAddress: string, takerToken: string, makerToken: string): string => {
callArgs.makerToken = makerToken;
callArgs.takerToken = takerToken;
callArgs.registryAddress = registryAddress;
return liquidityProviderAddress;
};
return [callArgs, fn];
}
function createDecreasingRates(count: number): BigNumber[] {
const rates: BigNumber[] = [];
const initialRate = getRandomFloat(1e-3, 1e2);
@@ -205,6 +261,7 @@ describe('MarketOperationUtils tests', () => {
[ERC20BridgeSource.CurveUsdcDaiUsdt]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtTusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.CurveUsdcDaiUsdtBusd]: _.times(NUM_SAMPLES, () => 0),
[ERC20BridgeSource.LiquidityProvider]: _.times(NUM_SAMPLES, () => 0),
};
function findSourceWithMaxOutput(rates: RatesBySource): ERC20BridgeSource {
@@ -239,6 +296,7 @@ describe('MarketOperationUtils tests', () => {
getSellQuotes: createGetMultipleSellQuotesOperationFromRates(DEFAULT_RATES),
getBuyQuotes: createGetMultipleBuyQuotesOperationFromRates(DEFAULT_RATES),
getMedianSellRate: createGetMedianSellRate(1),
getLiquidityProviderFromRegistry: getLiquidityProviderFromRegistry(),
};
function replaceSamplerOps(ops: Partial<typeof DEFAULT_OPS> = {}): void {
@@ -313,6 +371,31 @@ describe('MarketOperationUtils tests', () => {
expect(sourcesPolled.sort()).to.deep.eq(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,
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
await newMarketOperationUtils.getMarketSellOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
excludedSources: [],
});
expect(args.sources.sort()).to.deep.eq(
SELL_SOURCES.concat([ERC20BridgeSource.LiquidityProvider]).sort(),
);
expect(args.liquidityProviderAddress).to.eql(registryAddress);
});
it('does not poll DEXes in `excludedSources`', async () => {
const excludedSources = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
let sourcesPolled: ERC20BridgeSource[] = [];
@@ -530,6 +613,62 @@ describe('MarketOperationUtils tests', () => {
const expectedSources = [ERC20BridgeSource.Native, ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Kyber];
expect(orderSources).to.deep.eq(expectedSources);
});
it('is able to create a order from LiquidityProvider', async () => {
const registryAddress = randomAddress();
const liquidityProviderAddress = randomAddress();
const xAsset = randomAddress();
const yAsset = randomAddress();
const toSell = fromTokenUnitAmount(10);
const [getSellQuotesParams, getSellQuotesFn] = callTradeOperationAndRetainLiquidityProviderParams(
createGetMultipleSellQuotesOperationFromRates,
{
[ERC20BridgeSource.LiquidityProvider]: createDecreasingRates(5),
},
);
const [
getLiquidityProviderParams,
getLiquidityProviderFn,
] = getLiquidityProviderFromRegistryAndReturnCallParameters(liquidityProviderAddress);
replaceSamplerOps({
getOrderFillableTakerAmounts: () => [constants.ZERO_AMOUNT],
getSellQuotes: getSellQuotesFn,
getLiquidityProviderFromRegistry: getLiquidityProviderFn,
});
const sampler = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
const result = await sampler.getMarketSellOrdersAsync(
[
createOrder({
makerAssetData: assetDataUtils.encodeERC20AssetData(xAsset),
takerAssetData: assetDataUtils.encodeERC20AssetData(yAsset),
}),
],
Web3Wrapper.toBaseUnitAmount(10, 18),
{ excludedSources: SELL_SOURCES, numSamples: 4 },
);
expect(result.length).to.eql(1);
expect(result[0].makerAddress).to.eql(liquidityProviderAddress);
// tslint:disable-next-line:no-unnecessary-type-assertion
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(
result[0].makerAssetData,
) 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(getLiquidityProviderParams.registryAddress).is.eql(registryAddress);
expect(getLiquidityProviderParams.makerToken).is.eql(xAsset);
expect(getLiquidityProviderParams.takerToken).is.eql(yAsset);
});
});
describe('getMarketBuyOrdersAsync()', () => {
@@ -580,6 +719,31 @@ describe('MarketOperationUtils tests', () => {
expect(sourcesPolled).to.deep.eq(BUY_SOURCES);
});
it('polls the liquidity provider when the registry is provided in the arguments', async () => {
const [args, fn] = callTradeOperationAndRetainLiquidityProviderParams(
createGetMultipleBuyQuotesOperationFromRates,
DEFAULT_RATES,
);
replaceSamplerOps({
getBuyQuotes: fn,
});
const registryAddress = randomAddress();
const newMarketOperationUtils = new MarketOperationUtils(
MOCK_SAMPLER,
contractAddresses,
ORDER_DOMAIN,
registryAddress,
);
await newMarketOperationUtils.getMarketBuyOrdersAsync(ORDERS, FILL_AMOUNT, {
...DEFAULT_OPTS,
excludedSources: [],
});
expect(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 = _.sampleSize(SELL_SOURCES, _.random(1, SELL_SOURCES.length));
let sourcesPolled: ERC20BridgeSource[] = [];

View File

@@ -21,6 +21,12 @@ export type SampleBuysHandler = (
makerToken: string,
makerTokenAmounts: BigNumber[],
) => SampleResults;
export type SampleSellsLPHandler = (
registryAddress: string,
takerToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
) => SampleResults;
const DUMMY_PROVIDER = {
sendAsync: (...args: any[]): any => {
@@ -32,10 +38,12 @@ interface Handlers {
getOrderFillableMakerAssetAmounts: GetOrderFillableAssetAmountHandler;
getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler;
sampleSellsFromKyberNetwork: SampleSellsHandler;
sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler;
sampleSellsFromEth2Dai: SampleSellsHandler;
sampleSellsFromUniswap: SampleSellsHandler;
sampleBuysFromEth2Dai: SampleBuysHandler;
sampleBuysFromUniswap: SampleBuysHandler;
sampleBuysFromLiquidityProviderRegistry: SampleSellsLPHandler;
}
export class MockSamplerContract extends IERC20BridgeSamplerContract {
@@ -119,6 +127,22 @@ export class MockSamplerContract extends IERC20BridgeSamplerContract {
);
}
public sampleSellsFromLiquidityProviderRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
takerAssetAmounts: BigNumber[],
): ContractFunctionObj<GetOrderFillableAssetAmountResult> {
return this._wrapCall(
super.sampleSellsFromLiquidityProviderRegistry,
this._handlers.sampleSellsFromLiquidityProviderRegistry,
registryAddress,
takerToken,
makerToken,
takerAssetAmounts,
);
}
public sampleBuysFromEth2Dai(
takerToken: string,
makerToken: string,

View File

@@ -0,0 +1,81 @@
{
"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

@@ -0,0 +1,83 @@
{
"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": {}
}

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,19 @@
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "registryAddress", "type": "address" },
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "type": "address" }
],
"name": "getLiquidityProviderFromRegistry",
"outputs": [{ "internalType": "address", "name": "providerAddress", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
@@ -93,6 +106,20 @@
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "registryAddress", "type": "address" },
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "type": "address" },
{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }
],
"name": "sampleBuysFromLiquidityProviderRegistry",
"outputs": [{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
@@ -146,6 +173,20 @@
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "registryAddress", "type": "address" },
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "type": "address" },
{ "internalType": "uint256[]", "name": "takerTokenAmounts", "type": "uint256[]" }
],
"name": "sampleSellsFromLiquidityProviderRegistry",
"outputs": [{ "internalType": "uint256[]", "name": "makerTokenAmounts", "type": "uint256[]" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
@@ -167,6 +208,14 @@
"params": { "callDatas": "ABI-encoded call data for each function call." },
"return": "callResults ABI-encoded results data for each call."
},
"getLiquidityProviderFromRegistry(address,address,address)": {
"details": "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.",
"params": {
"makerToken": "Maker asset managed by liquidity provider.",
"takerToken": "Taker asset managed by liquidity provider."
},
"return": "providerAddress Address of the liquidity provider."
},
"getOrderFillableMakerAssetAmounts((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[])": {
"details": "Queries the fillable maker asset amounts of native orders.",
"params": {
@@ -192,6 +241,16 @@
},
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
},
"sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[])": {
"details": "Sample buy quotes from an arbitrary on-chain liquidity provider.",
"params": {
"makerToken": "Address of the maker token (what to buy).",
"makerTokenAmounts": "Maker token buy amount for each sample.",
"registryAddress": "Address of the liquidity provider registry contract.",
"takerToken": "Address of the taker token (what to sell)."
},
"return": "takerTokenAmounts Taker amounts sold at each maker token amount."
},
"sampleBuysFromUniswap(address,address,uint256[])": {
"details": "Sample buy quotes from Uniswap.",
"params": {
@@ -229,6 +288,16 @@
},
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
},
"sampleSellsFromLiquidityProviderRegistry(address,address,address,uint256[])": {
"details": "Sample sell quotes from an arbitrary on-chain liquidity provider.",
"params": {
"makerToken": "Address of the maker token (what to buy).",
"registryAddress": "Address of the liquidity provider registry contract.",
"takerToken": "Address of the taker token (what to sell).",
"takerTokenAmounts": "Taker token sell amount for each sample."
},
"return": "makerTokenAmounts Maker amounts bought at each taker token amount."
},
"sampleSellsFromUniswap(address,address,uint256[])": {
"details": "Sample sell quotes from Uniswap.",
"params": {

View File

@@ -0,0 +1,108 @@
{
"schemaVersion": "2.0.0",
"contractName": "ILiquidityProvider",
"compilerOutput": {
"abi": [
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "tokenAddress", "type": "address" },
{ "internalType": "address", "name": "from", "type": "address" },
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" },
{ "internalType": "bytes", "name": "bridgeData", "type": "bytes" }
],
"name": "bridgeTransferFrom",
"outputs": [{ "internalType": "bytes4", "name": "success", "type": "bytes4" }],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "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": "takerToken", "type": "address" },
{ "internalType": "address", "name": "makerToken", "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": {
"bridgeTransferFrom(address,address,address,uint256,bytes)": {
"details": "Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.",
"params": {
"amount": "Amount of asset to transfer.",
"bridgeData": "Arbitrary asset data needed by the bridge contract.",
"from": "Address to transfer asset from.",
"to": "Address to transfer asset to.",
"tokenAddress": "The address of the ERC20 token to transfer."
},
"return": "success The magic bytes `0xdc1600f3` if successful."
},
"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.",
"makerToken": "Address of the maker token (what to buy).",
"takerToken": "Address of the taker token (what to sell)."
},
"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": {
"makerToken": "Address of the maker token (what to buy).",
"sellAmount": "Amount of `takerToken` to sell.",
"takerToken": "Address of the taker token (what to sell)."
},
"return": "makerTokenAmount Amount of `makerToken` that would be obtained."
}
}
},
"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

@@ -0,0 +1,58 @@
{
"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

@@ -31,7 +31,7 @@
"wrappers:generate": "abi-gen --abis ${npm_package_config_abis} --output src/generated-wrappers --backend ethers"
},
"config": {
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|GodsUnchainedValidator|Broker|MaximumGasPrice).json"
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler|ERC20BridgeSampler|GodsUnchainedValidator|Broker|ILiquidityProvider|ILiquidityProviderRegistry|MaximumGasPrice).json"
},
"repository": {
"type": "git",

File diff suppressed because it is too large Load Diff

View File

@@ -180,6 +180,33 @@ export class IERC20BridgeSamplerContract extends BaseContract {
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'registryAddress',
type: 'address',
},
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
],
name: 'getLiquidityProviderFromRegistry',
outputs: [
{
name: 'providerAddress',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
@@ -369,6 +396,37 @@ export class IERC20BridgeSamplerContract extends BaseContract {
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'registryAddress',
type: 'address',
},
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
{
name: 'makerTokenAmounts',
type: 'uint256[]',
},
],
name: 'sampleBuysFromLiquidityProviderRegistry',
outputs: [
{
name: 'takerTokenAmounts',
type: 'uint256[]',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
@@ -481,6 +539,37 @@ export class IERC20BridgeSamplerContract extends BaseContract {
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'registryAddress',
type: 'address',
},
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
{
name: 'takerTokenAmounts',
type: 'uint256[]',
},
],
name: 'sampleSellsFromLiquidityProviderRegistry',
outputs: [
{
name: 'makerTokenAmounts',
type: 'uint256[]',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
@@ -613,6 +702,45 @@ export class IERC20BridgeSamplerContract extends BaseContract {
},
};
}
/**
* 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.
* @returns providerAddress Address of the liquidity provider.
*/
public getLiquidityProviderFromRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
): ContractFunctionObj<string> {
const self = (this as any) as IERC20BridgeSamplerContract;
assert.isString('registryAddress', registryAddress);
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
const functionSignature = 'getLiquidityProviderFromRegistry(address,address,address)';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
registryAddress.toLowerCase(),
takerToken.toLowerCase(),
makerToken.toLowerCase(),
]);
},
};
}
/**
* Queries the fillable maker asset amounts of native orders.
* @param orders Native orders to query.
@@ -744,6 +872,48 @@ export class IERC20BridgeSamplerContract extends BaseContract {
},
};
}
/**
* Sample buy quotes from an arbitrary on-chain liquidity provider.
* @param registryAddress Address of the liquidity provider registry contract.
* @param takerToken Address of the taker token (what to sell).
* @param makerToken Address of the maker token (what to buy).
* @param makerTokenAmounts Maker token buy amount for each sample.
* @returns takerTokenAmounts Taker amounts sold at each maker token amount.
*/
public sampleBuysFromLiquidityProviderRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
makerTokenAmounts: BigNumber[],
): ContractFunctionObj<BigNumber[]> {
const self = (this as any) as IERC20BridgeSamplerContract;
assert.isString('registryAddress', registryAddress);
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
assert.isArray('makerTokenAmounts', makerTokenAmounts);
const functionSignature = 'sampleBuysFromLiquidityProviderRegistry(address,address,address,uint256[])';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber[]> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber[]>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
registryAddress.toLowerCase(),
takerToken.toLowerCase(),
makerToken.toLowerCase(),
makerTokenAmounts,
]);
},
};
}
/**
* Sample buy quotes from Uniswap.
* @param takerToken Address of the taker token (what to sell).
@@ -900,6 +1070,48 @@ export class IERC20BridgeSamplerContract extends BaseContract {
},
};
}
/**
* Sample sell quotes from an arbitrary on-chain liquidity provider.
* @param registryAddress Address of the liquidity provider registry contract.
* @param takerToken Address of the taker token (what to sell).
* @param makerToken Address of the maker token (what to buy).
* @param takerTokenAmounts Taker token sell amount for each sample.
* @returns makerTokenAmounts Maker amounts bought at each taker token amount.
*/
public sampleSellsFromLiquidityProviderRegistry(
registryAddress: string,
takerToken: string,
makerToken: string,
takerTokenAmounts: BigNumber[],
): ContractFunctionObj<BigNumber[]> {
const self = (this as any) as IERC20BridgeSamplerContract;
assert.isString('registryAddress', registryAddress);
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
assert.isArray('takerTokenAmounts', takerTokenAmounts);
const functionSignature = 'sampleSellsFromLiquidityProviderRegistry(address,address,address,uint256[])';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber[]> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber[]>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
registryAddress.toLowerCase(),
takerToken.toLowerCase(),
makerToken.toLowerCase(),
takerTokenAmounts,
]);
},
};
}
/**
* Sample sell quotes from Uniswap.
* @param takerToken Address of the taker token (what to sell).

View File

@@ -0,0 +1,503 @@
// 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 ILiquidityProviderContract extends BaseContract {
/**
* @ignore
*/
public static deployedBytecode: string | undefined;
public static contractName = 'ILiquidityProvider';
private readonly _methodABIIndex: { [name: string]: number } = {};
public static async deployFrom0xArtifactAsync(
artifact: ContractArtifact | SimpleContractArtifact,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractArtifact | SimpleContractArtifact },
): Promise<ILiquidityProviderContract> {
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 ILiquidityProviderContract.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<ILiquidityProviderContract> {
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 ILiquidityProviderContract._deployLibrariesAsync(
artifact,
libraryArtifacts,
new Web3Wrapper(provider),
txDefaults,
);
const bytecode = linkLibrariesInBytecode(artifact, libraryAddresses);
return ILiquidityProviderContract.deployAsync(
bytecode,
abi,
provider,
txDefaults,
logDecodeDependenciesAbiOnly,
);
}
public static async deployAsync(
bytecode: string,
abi: ContractAbi,
supportedProvider: SupportedProvider,
txDefaults: Partial<TxData>,
logDecodeDependencies: { [contractName: string]: ContractAbi },
): Promise<ILiquidityProviderContract> {
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(`ILiquidityProvider successfully deployed at ${txReceipt.contractAddress}`);
const contractInstance = new ILiquidityProviderContract(
txReceipt.contractAddress as string,
provider,
txDefaults,
logDecodeDependencies,
);
contractInstance.constructorArgs = [];
return contractInstance;
}
/**
* @returns The contract ABI
*/
public static ABI(): ContractAbi {
const abi = [
{
constant: false,
inputs: [
{
name: 'tokenAddress',
type: 'address',
},
{
name: 'from',
type: 'address',
},
{
name: 'to',
type: 'address',
},
{
name: 'amount',
type: 'uint256',
},
{
name: 'bridgeData',
type: 'bytes',
},
],
name: 'bridgeTransferFrom',
outputs: [
{
name: 'success',
type: 'bytes4',
},
],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
{
name: 'buyAmount',
type: 'uint256',
},
],
name: 'getBuyQuote',
outputs: [
{
name: 'takerTokenAmount',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: 'takerToken',
type: 'address',
},
{
name: 'makerToken',
type: 'address',
},
{
name: 'sellAmount',
type: 'uint256',
},
],
name: 'getSellQuote',
outputs: [
{
name: 'makerTokenAmount',
type: 'uint256',
},
],
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 ILiquidityProviderContract._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 = ILiquidityProviderContract.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 ILiquidityProviderContract;
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 ILiquidityProviderContract;
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 ILiquidityProviderContract;
const abiEncoder = self._lookupAbiEncoder(functionSignature);
return abiEncoder.getSelector();
}
/**
* 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.
* @returns success The magic bytes &#x60;0xdc1600f3&#x60; if successful.
*/
public bridgeTransferFrom(
tokenAddress: string,
from: string,
to: string,
amount: BigNumber,
bridgeData: string,
): ContractTxFunctionObj<string> {
const self = (this as any) as ILiquidityProviderContract;
assert.isString('tokenAddress', tokenAddress);
assert.isString('from', from);
assert.isString('to', to);
assert.isBigNumber('amount', amount);
assert.isString('bridgeData', bridgeData);
const functionSignature = 'bridgeTransferFrom(address,address,address,uint256,bytes)';
return {
async sendTransactionAsync(
txData?: Partial<TxData> | undefined,
opts: SendTransactionOpts = { shouldValidate: true },
): Promise<string> {
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
{ ...txData, data: this.getABIEncodedTransactionData() },
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({
...txData,
data: this.getABIEncodedTransactionData(),
});
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
},
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<string> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<string>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
tokenAddress.toLowerCase(),
from.toLowerCase(),
to.toLowerCase(),
amount,
bridgeData,
]);
},
};
}
/**
* 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.
* @returns takerTokenAmount Amount of &#x60;takerToken&#x60; that would need to be sold.
*/
public getBuyQuote(takerToken: string, makerToken: string, buyAmount: BigNumber): ContractFunctionObj<BigNumber> {
const self = (this as any) as ILiquidityProviderContract;
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
assert.isBigNumber('buyAmount', buyAmount);
const functionSignature = 'getBuyQuote(address,address,uint256)';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
takerToken.toLowerCase(),
makerToken.toLowerCase(),
buyAmount,
]);
},
};
}
/**
* 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.
* @returns makerTokenAmount Amount of &#x60;makerToken&#x60; that would be obtained.
*/
public getSellQuote(takerToken: string, makerToken: string, sellAmount: BigNumber): ContractFunctionObj<BigNumber> {
const self = (this as any) as ILiquidityProviderContract;
assert.isString('takerToken', takerToken);
assert.isString('makerToken', makerToken);
assert.isBigNumber('sellAmount', sellAmount);
const functionSignature = 'getSellQuote(address,address,uint256)';
return {
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
BaseContract._assertCallParams(callData, defaultBlock);
const rawCallResult = await self._performCallAsync(
{ ...callData, data: this.getABIEncodedTransactionData() },
defaultBlock,
);
const abiEncoder = self._lookupAbiEncoder(functionSignature);
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
},
getABIEncodedTransactionData(): string {
return self._strictEncodeArguments(functionSignature, [
takerToken.toLowerCase(),
makerToken.toLowerCase(),
sellAmount,
]);
},
};
}
constructor(
address: string,
supportedProvider: SupportedProvider,
txDefaults?: Partial<TxData>,
logDecodeDependencies?: { [contractName: string]: ContractAbi },
deployedBytecode: string | undefined = ILiquidityProviderContract.deployedBytecode,
) {
super(
'ILiquidityProvider',
ILiquidityProviderContract.ABI(),
address,
supportedProvider,
txDefaults,
logDecodeDependencies,
deployedBytecode,
);
classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', '_web3Wrapper']);
ILiquidityProviderContract.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

@@ -0,0 +1,327 @@
// 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(
{ ...callData, data: this.getABIEncodedTransactionData() },
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

@@ -1,3 +1,5 @@
export { ERC20BridgeSamplerContract } from './generated-wrappers/erc20_bridge_sampler';
export { ContractAddresses } from '@0x/contract-addresses';
export { ContractWrappers } from './contract_wrappers';