@0x/asset-swapper
: Rebase against development and pay protocol fees.
This commit is contained in:
parent
32258ef666
commit
439c98a6e5
@ -1,4 +1,17 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "2.1.0-beta.3",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Refactor of logic for marketBuy/marketSell order pruning and selecting, introduced protocol fees, and refactored types used by the package",
|
||||||
|
"pr": 2272
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Incorporate paying protocol fees.",
|
||||||
|
"pr": 2350
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2.1.0-beta.2",
|
"version": "2.1.0-beta.2",
|
||||||
"changes": [
|
"changes": [
|
||||||
@ -7,7 +20,7 @@
|
|||||||
"pr": 2342
|
"pr": 2342
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timestamp": 1574030254
|
"timestamp": 1573159180
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "2.1.0-beta.1",
|
"version": "2.1.0-beta.1",
|
||||||
|
@ -5,10 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
## v2.1.0-beta.2 - _November 17, 2019_
|
|
||||||
|
|
||||||
* Update BigNumber version to ~9.0.0 (#2342)
|
|
||||||
|
|
||||||
## v2.1.0-beta.1 - _November 7, 2019_
|
## v2.1.0-beta.1 - _November 7, 2019_
|
||||||
|
|
||||||
* All references to network ID have been removed, and references to chain ID have been introduced instead (#2313)
|
* All references to network ID have been removed, and references to chain ID have been introduced instead (#2313)
|
||||||
|
File diff suppressed because it is too large
Load Diff
1839
packages/asset-swapper/package-lock.json
generated
Normal file
1839
packages/asset-swapper/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/asset-swapper",
|
"name": "@0x/asset-swapper",
|
||||||
"version": "2.1.0-beta.2",
|
"version": "2.1.0-beta.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@ -40,28 +40,30 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://0x.org/asset-swapper",
|
"homepage": "https://0x.org/asset-swapper",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0x/assert": "^2.2.0-beta.2",
|
"@0x/assert": "^2.2.0-beta.1",
|
||||||
"@0x/connect": "^5.1.0-beta.2",
|
"@0x/contracts-dev-utils": "^0.1.0-beta.1",
|
||||||
"@0x/contract-addresses": "^3.3.0-beta.3",
|
"@0x/contracts-erc20": "^2.3.0-beta.1",
|
||||||
"@0x/contract-wrappers": "^12.2.0-beta.2",
|
"@0x/contracts-exchange": "^2.2.0-beta.1",
|
||||||
"@0x/dev-utils": "^2.4.0-beta.2",
|
"@0x/contracts-exchange-forwarder": "^3.1.0-beta.1",
|
||||||
"@0x/json-schemas": "^4.1.0-beta.2",
|
"@0x/json-schemas": "^4.1.0-beta.1",
|
||||||
"@0x/migrations": "^4.4.0-beta.2",
|
"@0x/order-utils": "^8.5.0-beta.1",
|
||||||
"@0x/order-utils": "^8.5.0-beta.2",
|
"@0x/orderbook": "^0.1.0-beta.1",
|
||||||
"@0x/orderbook": "^0.1.0-beta.2",
|
"@0x/types": "^2.5.0-beta.1",
|
||||||
"@0x/subproviders": "^5.1.0-beta.2",
|
"@0x/utils": "^4.6.0-beta.1",
|
||||||
"@0x/types": "^2.5.0-beta.2",
|
"@0x/web3-wrapper": "^6.1.0-beta.1",
|
||||||
"@0x/typescript-typings": "^4.4.0-beta.2",
|
"ethereum-types": "^2.2.0-beta.1",
|
||||||
"@0x/utils": "^4.6.0-beta.2",
|
|
||||||
"@0x/web3-wrapper": "^6.1.0-beta.2",
|
|
||||||
"ethereum-types": "^2.2.0-beta.2",
|
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@0x/contract-addresses": "^3.3.0-beta.2",
|
||||||
"@0x/contracts-test-utils": "^3.2.0-beta.2",
|
"@0x/contracts-test-utils": "^3.2.0-beta.2",
|
||||||
|
"@0x/dev-utils": "^2.4.0-beta.1",
|
||||||
"@0x/mesh-rpc-client": "^7.0.4-beta-0xv3",
|
"@0x/mesh-rpc-client": "^7.0.4-beta-0xv3",
|
||||||
|
"@0x/migrations": "^4.4.0-beta.1",
|
||||||
|
"@0x/subproviders": "^5.1.0-beta.1",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
"@0x/tslint-config": "^3.1.0-beta.2",
|
"@0x/tslint-config": "^3.1.0-beta.1",
|
||||||
|
"@0x/typescript-typings": "^4.4.0-beta.1",
|
||||||
"@types/lodash": "4.14.104",
|
"@types/lodash": "4.14.104",
|
||||||
"@types/mocha": "^5.2.7",
|
"@types/mocha": "^5.2.7",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
@ -1,55 +1,68 @@
|
|||||||
import { SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ForwarderSwapQuoteExecutionOpts,
|
ForwarderExtensionContractOpts,
|
||||||
ForwarderSwapQuoteGetOutputOpts,
|
OrderPrunerOpts,
|
||||||
OrdersAndFillableAmounts,
|
OrderPrunerPermittedFeeTypes,
|
||||||
|
SwapQuoteExecutionOpts,
|
||||||
|
SwapQuoteGetOutputOpts,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterOpts,
|
SwapQuoterOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
|
||||||
const NULL_BYTES = '0x';
|
const NULL_BYTES = '0x';
|
||||||
|
const NULL_ERC20_ASSET_DATA = '0xf47261b00000000000000000000000000000000000000000000000000000000000000000';
|
||||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
const MAINNET_CHAIN_ID = 1;
|
const MAINNET_CHAIN_ID = 1;
|
||||||
const ONE_SECOND_MS = 1000;
|
const ONE_SECOND_MS = 1000;
|
||||||
const DEFAULT_PER_PAGE = 1000;
|
const DEFAULT_PER_PAGE = 1000;
|
||||||
|
const PROTOCOL_FEE_MULTIPLIER = 150000;
|
||||||
|
|
||||||
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
|
||||||
chainId: MAINNET_CHAIN_ID,
|
|
||||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
|
||||||
expiryBufferMs: 120000, // 2 minutes
|
expiryBufferMs: 120000, // 2 minutes
|
||||||
|
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([
|
||||||
|
OrderPrunerPermittedFeeTypes.NoFees,
|
||||||
|
OrderPrunerPermittedFeeTypes.MakerDenominatedTakerFee,
|
||||||
|
]), // Default asset-swapper for CFL oriented fee types
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: ForwarderSwapQuoteGetOutputOpts = {
|
const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
||||||
|
...{
|
||||||
|
chainId: MAINNET_CHAIN_ID,
|
||||||
|
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||||
|
},
|
||||||
|
...DEFAULT_ORDER_PRUNER_OPTS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts = {
|
||||||
feePercentage: 0,
|
feePercentage: 0,
|
||||||
feeRecipient: NULL_ADDRESS,
|
feeRecipient: NULL_ADDRESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: ForwarderSwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
|
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts &
|
||||||
|
ForwarderExtensionContractOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
|
||||||
|
|
||||||
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
|
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
|
||||||
shouldDisableRequestingFeeOrders: false,
|
|
||||||
slippagePercentage: 0.2, // 20% slippage protection,
|
slippagePercentage: 0.2, // 20% slippage protection,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EMPTY_ORDERS_AND_FILLABLE_AMOUNTS: OrdersAndFillableAmounts = {
|
|
||||||
orders: [] as SignedOrder[],
|
|
||||||
remainingFillableMakerAssetAmounts: [] as BigNumber[],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const constants = {
|
export const constants = {
|
||||||
|
ETH_GAS_STATION_API_BASE_URL,
|
||||||
NULL_BYTES,
|
NULL_BYTES,
|
||||||
ZERO_AMOUNT: new BigNumber(0),
|
ZERO_AMOUNT: new BigNumber(0),
|
||||||
NULL_ADDRESS,
|
NULL_ADDRESS,
|
||||||
MAINNET_CHAIN_ID,
|
MAINNET_CHAIN_ID,
|
||||||
|
DEFAULT_ORDER_PRUNER_OPTS,
|
||||||
ETHER_TOKEN_DECIMALS: 18,
|
ETHER_TOKEN_DECIMALS: 18,
|
||||||
ONE_AMOUNT: new BigNumber(1),
|
ONE_AMOUNT: new BigNumber(1),
|
||||||
|
MAX_AFFILIATE_FEE_PERCENTAGE: 0.05,
|
||||||
ONE_SECOND_MS,
|
ONE_SECOND_MS,
|
||||||
DEFAULT_SWAP_QUOTER_OPTS,
|
DEFAULT_SWAP_QUOTER_OPTS,
|
||||||
DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
|
DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
|
||||||
DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
|
DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
|
||||||
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
||||||
EMPTY_ORDERS_AND_FILLABLE_AMOUNTS,
|
|
||||||
DEFAULT_PER_PAGE,
|
DEFAULT_PER_PAGE,
|
||||||
|
PROTOCOL_FEE_MULTIPLIER,
|
||||||
|
NULL_ERC20_ASSET_DATA,
|
||||||
};
|
};
|
||||||
|
@ -20,11 +20,13 @@ export {
|
|||||||
ConstructorStateMutability,
|
ConstructorStateMutability,
|
||||||
} from 'ethereum-types';
|
} from 'ethereum-types';
|
||||||
|
|
||||||
export { SignedOrder, AssetPairsItem, APIOrder, Asset } from '@0x/types';
|
export { SignedOrder, AssetPairsItem, APIOrder, Asset, Order } from '@0x/types';
|
||||||
export { BigNumber } from '@0x/utils';
|
export { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
|
||||||
export { SwapQuoter } from './swap_quoter';
|
export { SwapQuoter } from './swap_quoter';
|
||||||
|
export { protocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
|
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
|
||||||
export { InsufficientAssetLiquidityError } from './errors';
|
export { InsufficientAssetLiquidityError } from './errors';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -34,21 +36,16 @@ export {
|
|||||||
SwapQuoteConsumerOpts,
|
SwapQuoteConsumerOpts,
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
ExtensionContractType,
|
ExtensionContractType,
|
||||||
|
SwapQuoteConsumingOpts,
|
||||||
|
LiquidityForTakerMakerAssetDataPair,
|
||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
|
PrunedSignedOrder,
|
||||||
SwapQuoteExecutionOpts,
|
SwapQuoteExecutionOpts,
|
||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
GetExtensionContractTypeOpts,
|
GetExtensionContractTypeOpts,
|
||||||
SwapQuoteExecutionOptsBase,
|
|
||||||
SwapQuoteGetOutputOptsBase,
|
|
||||||
ForwarderSwapQuoteExecutionOpts,
|
|
||||||
ForwarderSwapQuoteGetOutputOpts,
|
|
||||||
SmartContractParamsInfo,
|
SmartContractParamsInfo,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
MarketBuySwapQuoteWithAffiliateFee,
|
|
||||||
MarketSellSwapQuoteWithAffiliateFee,
|
|
||||||
LiquidityForAssetData,
|
|
||||||
OrdersAndFillableAmounts,
|
|
||||||
SwapQuoteConsumerBase,
|
SwapQuoteConsumerBase,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ContractError, ContractWrappers, ForwarderError } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
import { MarketOperation } from '@0x/types';
|
import { ExchangeContract } from '@0x/contracts-exchange';
|
||||||
import { AbiEncoder, providerUtils } from '@0x/utils';
|
import { AbiEncoder, providerUtils } from '@0x/utils';
|
||||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||||
import { MethodAbi } from 'ethereum-types';
|
import { MethodAbi } from 'ethereum-types';
|
||||||
@ -9,13 +9,13 @@ import { constants } from '../constants';
|
|||||||
import {
|
import {
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
ExchangeSmartContractParams,
|
ExchangeSmartContractParams,
|
||||||
|
MarketOperation,
|
||||||
SmartContractParamsInfo,
|
SmartContractParamsInfo,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteConsumerBase,
|
SwapQuoteConsumerBase,
|
||||||
SwapQuoteConsumerError,
|
|
||||||
SwapQuoteConsumerOpts,
|
SwapQuoteConsumerOpts,
|
||||||
SwapQuoteExecutionOptsBase,
|
SwapQuoteExecutionOpts,
|
||||||
SwapQuoteGetOutputOptsBase,
|
SwapQuoteGetOutputOpts,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { assert } from '../utils/assert';
|
import { assert } from '../utils/assert';
|
||||||
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
|
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
|
||||||
@ -25,23 +25,24 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
|
|||||||
public readonly provider: ZeroExProvider;
|
public readonly provider: ZeroExProvider;
|
||||||
public readonly chainId: number;
|
public readonly chainId: number;
|
||||||
|
|
||||||
private readonly _contractWrappers: ContractWrappers;
|
private readonly _exchangeContract: ExchangeContract;
|
||||||
|
|
||||||
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
constructor(
|
||||||
|
supportedProvider: SupportedProvider,
|
||||||
|
contractAddresses: ContractAddresses,
|
||||||
|
options: Partial<SwapQuoteConsumerOpts> = {},
|
||||||
|
) {
|
||||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||||
assert.isNumber('chainId', chainId);
|
assert.isNumber('chainId', chainId);
|
||||||
|
|
||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
this._exchangeContract = new ExchangeContract(contractAddresses.exchange, supportedProvider);
|
||||||
chainId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCalldataOrThrowAsync(
|
public async getCalldataOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteGetOutputOptsBase>,
|
opts: Partial<SwapQuoteGetOutputOpts>,
|
||||||
): Promise<CalldataInfo> {
|
): Promise<CalldataInfo> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
|
|||||||
|
|
||||||
public async getSmartContractParamsOrThrowAsync(
|
public async getSmartContractParamsOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
_opts: Partial<SwapQuoteGetOutputOptsBase>,
|
_opts: Partial<SwapQuoteGetOutputOpts> = {},
|
||||||
): Promise<SmartContractParamsInfo<ExchangeSmartContractParams>> {
|
): Promise<SmartContractParamsInfo<ExchangeSmartContractParams>> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
|
|
||||||
@ -77,8 +78,6 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
|
|||||||
|
|
||||||
const signatures = _.map(orders, o => o.signature);
|
const signatures = _.map(orders, o => o.signature);
|
||||||
|
|
||||||
const optimizedOrders = swapQuoteConsumerUtils.optimizeOrdersForMarketExchangeOperation(orders, quote.type);
|
|
||||||
|
|
||||||
let params: ExchangeSmartContractParams;
|
let params: ExchangeSmartContractParams;
|
||||||
let methodName: string;
|
let methodName: string;
|
||||||
|
|
||||||
@ -86,45 +85,43 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
|
|||||||
const { makerAssetFillAmount } = quote;
|
const { makerAssetFillAmount } = quote;
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
orders: optimizedOrders,
|
orders,
|
||||||
signatures,
|
signatures,
|
||||||
makerAssetFillAmount,
|
makerAssetFillAmount,
|
||||||
type: MarketOperation.Buy,
|
type: MarketOperation.Buy,
|
||||||
};
|
};
|
||||||
|
|
||||||
methodName = 'marketBuyOrders';
|
methodName = 'marketBuyOrdersFillOrKill';
|
||||||
} else {
|
} else {
|
||||||
const { takerAssetFillAmount } = quote;
|
const { takerAssetFillAmount } = quote;
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
orders: optimizedOrders,
|
orders,
|
||||||
signatures,
|
signatures,
|
||||||
takerAssetFillAmount,
|
takerAssetFillAmount,
|
||||||
type: MarketOperation.Sell,
|
type: MarketOperation.Sell,
|
||||||
};
|
};
|
||||||
|
|
||||||
methodName = 'marketSellOrders';
|
methodName = 'marketSellOrdersFillOrKill';
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodAbi = utils.getMethodAbiFromContractAbi(
|
const methodAbi = utils.getMethodAbiFromContractAbi(this._exchangeContract.abi, methodName) as MethodAbi;
|
||||||
this._contractWrappers.exchange.abi,
|
|
||||||
methodName,
|
|
||||||
) as MethodAbi;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params,
|
params,
|
||||||
toAddress: this._contractWrappers.exchange.address,
|
toAddress: this._exchangeContract.address,
|
||||||
methodAbi,
|
methodAbi,
|
||||||
|
ethAmount: quote.worstCaseQuoteInfo.protocolFeeInEthAmount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async executeSwapQuoteOrThrowAsync(
|
public async executeSwapQuoteOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteExecutionOptsBase>,
|
opts: Partial<SwapQuoteExecutionOpts>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
|
|
||||||
const { takerAddress, gasLimit, gasPrice } = opts;
|
const { takerAddress, gasLimit, gasPrice, ethAmount } = opts;
|
||||||
|
|
||||||
if (takerAddress !== undefined) {
|
if (takerAddress !== undefined) {
|
||||||
assert.isETHAddressHex('takerAddress', takerAddress);
|
assert.isETHAddressHex('takerAddress', takerAddress);
|
||||||
@ -135,41 +132,38 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
|
|||||||
if (gasPrice !== undefined) {
|
if (gasPrice !== undefined) {
|
||||||
assert.isBigNumber('gasPrice', gasPrice);
|
assert.isBigNumber('gasPrice', gasPrice);
|
||||||
}
|
}
|
||||||
|
if (ethAmount !== undefined) {
|
||||||
|
assert.isBigNumber('ethAmount', ethAmount);
|
||||||
|
}
|
||||||
const { orders } = quote;
|
const { orders } = quote;
|
||||||
|
|
||||||
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
|
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
|
||||||
|
const value = ethAmount || quote.worstCaseQuoteInfo.protocolFeeInEthAmount;
|
||||||
try {
|
let txHash: string;
|
||||||
let txHash: string;
|
if (quote.type === MarketOperation.Buy) {
|
||||||
if (quote.type === MarketOperation.Buy) {
|
const { makerAssetFillAmount } = quote;
|
||||||
const { makerAssetFillAmount } = quote;
|
txHash = await this._exchangeContract
|
||||||
txHash = await this._contractWrappers.exchange
|
.marketBuyOrdersFillOrKill(orders, makerAssetFillAmount, orders.map(o => o.signature))
|
||||||
.marketBuyOrdersNoThrow(orders, makerAssetFillAmount, orders.map(o => o.signature))
|
.sendTransactionAsync({
|
||||||
.sendTransactionAsync({
|
from: finalTakerAddress,
|
||||||
from: finalTakerAddress,
|
gas: gasLimit,
|
||||||
gas: gasLimit,
|
gasPrice,
|
||||||
gasPrice,
|
value,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { takerAssetFillAmount } = quote;
|
const { takerAssetFillAmount } = quote;
|
||||||
txHash = await this._contractWrappers.exchange
|
txHash = await this._exchangeContract
|
||||||
.marketSellOrdersNoThrow(orders, takerAssetFillAmount, orders.map(o => o.signature))
|
.marketSellOrdersFillOrKill(orders, takerAssetFillAmount, orders.map(o => o.signature))
|
||||||
.sendTransactionAsync({
|
.sendTransactionAsync({
|
||||||
from: finalTakerAddress,
|
from: finalTakerAddress,
|
||||||
gas: gasLimit,
|
gas: gasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
});
|
value,
|
||||||
}
|
});
|
||||||
return txHash;
|
|
||||||
} catch (err) {
|
|
||||||
if (_.includes(err.message, ContractError.SignatureRequestDenied)) {
|
|
||||||
throw new Error(SwapQuoteConsumerError.SignatureRequestDenied);
|
|
||||||
} else if (_.includes(err.message, ForwarderError.CompleteFillFailed)) {
|
|
||||||
throw new Error(SwapQuoteConsumerError.TransactionValueTooLow);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// TODO(dorothy-zbornak): Handle signature request denied
|
||||||
|
// (see contract-wrappers/decorators)
|
||||||
|
// and ExchangeRevertErrors.IncompleteFillError.
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ContractError, ContractWrappers, ForwarderError } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
import { MarketOperation } from '@0x/types';
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
|
||||||
import { AbiEncoder, providerUtils } from '@0x/utils';
|
import { AbiEncoder, providerUtils } from '@0x/utils';
|
||||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||||
import { MethodAbi } from 'ethereum-types';
|
import { MethodAbi } from 'ethereum-types';
|
||||||
@ -8,14 +9,15 @@ import * as _ from 'lodash';
|
|||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import {
|
import {
|
||||||
CalldataInfo,
|
CalldataInfo,
|
||||||
|
ForwarderExtensionContractOpts,
|
||||||
ForwarderSmartContractParams,
|
ForwarderSmartContractParams,
|
||||||
ForwarderSwapQuoteExecutionOpts,
|
MarketOperation,
|
||||||
ForwarderSwapQuoteGetOutputOpts,
|
|
||||||
SmartContractParamsInfo,
|
SmartContractParamsInfo,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteConsumerBase,
|
SwapQuoteConsumerBase,
|
||||||
SwapQuoteConsumerError,
|
|
||||||
SwapQuoteConsumerOpts,
|
SwapQuoteConsumerOpts,
|
||||||
|
SwapQuoteExecutionOpts,
|
||||||
|
SwapQuoteGetOutputOpts,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { affiliateFeeUtils } from '../utils/affiliate_fee_utils';
|
import { affiliateFeeUtils } from '../utils/affiliate_fee_utils';
|
||||||
import { assert } from '../utils/assert';
|
import { assert } from '../utils/assert';
|
||||||
@ -26,18 +28,23 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
public readonly provider: ZeroExProvider;
|
public readonly provider: ZeroExProvider;
|
||||||
public readonly chainId: number;
|
public readonly chainId: number;
|
||||||
|
|
||||||
private readonly _contractWrappers: ContractWrappers;
|
private readonly _contractAddresses: ContractAddresses;
|
||||||
|
private readonly _forwarder: ForwarderContract;
|
||||||
|
private readonly _devUtils: DevUtilsContract;
|
||||||
|
|
||||||
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
constructor(
|
||||||
|
supportedProvider: SupportedProvider,
|
||||||
|
contractAddresses: ContractAddresses,
|
||||||
|
options: Partial<SwapQuoteConsumerOpts> = {},
|
||||||
|
) {
|
||||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||||
assert.isNumber('chainId', chainId);
|
assert.isNumber('chainId', chainId);
|
||||||
|
|
||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
this._contractAddresses = contractAddresses;
|
||||||
chainId,
|
this._forwarder = new ForwarderContract(contractAddresses.forwarder, supportedProvider);
|
||||||
});
|
this._devUtils = new DevUtilsContract(contractAddresses.devUtils, supportedProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,21 +54,21 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
*/
|
*/
|
||||||
public async getCalldataOrThrowAsync(
|
public async getCalldataOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<ForwarderSwapQuoteGetOutputOpts>,
|
opts: Partial<SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts> = {},
|
||||||
): Promise<CalldataInfo> {
|
): Promise<CalldataInfo> {
|
||||||
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
||||||
|
|
||||||
const { toAddress, methodAbi, ethAmount, params } = await this.getSmartContractParamsOrThrowAsync(quote, opts);
|
const { toAddress, methodAbi, ethAmount, params } = await this.getSmartContractParamsOrThrowAsync(quote, opts);
|
||||||
|
|
||||||
const abiEncoder = new AbiEncoder.Method(methodAbi);
|
const abiEncoder = new AbiEncoder.Method(methodAbi);
|
||||||
const { orders, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient } = params;
|
const { orders, signatures, feePercentage, feeRecipient } = params;
|
||||||
|
|
||||||
let args: any[];
|
let args: any[];
|
||||||
if (params.type === MarketOperation.Buy) {
|
if (params.type === MarketOperation.Buy) {
|
||||||
const { makerAssetFillAmount } = params;
|
const { makerAssetFillAmount } = params;
|
||||||
args = [orders, makerAssetFillAmount, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient];
|
args = [orders, makerAssetFillAmount, signatures, feePercentage, feeRecipient];
|
||||||
} else {
|
} else {
|
||||||
args = [orders, signatures, feeOrders, feeSignatures, feePercentage, feeRecipient];
|
args = [orders, signatures, feePercentage, feeRecipient];
|
||||||
}
|
}
|
||||||
const calldataHexString = abiEncoder.encode(args, { shouldOptimize: true });
|
const calldataHexString = abiEncoder.encode(args, { shouldOptimize: true });
|
||||||
return {
|
return {
|
||||||
@ -79,47 +86,42 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
*/
|
*/
|
||||||
public async getSmartContractParamsOrThrowAsync(
|
public async getSmartContractParamsOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<ForwarderSwapQuoteGetOutputOpts>,
|
opts: Partial<SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts> = {},
|
||||||
): Promise<SmartContractParamsInfo<ForwarderSmartContractParams>> {
|
): Promise<SmartContractParamsInfo<ForwarderSmartContractParams>> {
|
||||||
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
||||||
|
|
||||||
const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge(
|
const { ethAmount: providedEthAmount, feeRecipient, feePercentage } = _.merge(
|
||||||
{},
|
{},
|
||||||
constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
|
constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
|
||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.isValidPercentage('feePercentage', unFormattedFeePercentage);
|
assert.isValidPercentage('feePercentage', feePercentage);
|
||||||
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
||||||
if (ethAmount !== undefined) {
|
if (providedEthAmount !== undefined) {
|
||||||
assert.isBigNumber('ethAmount', ethAmount);
|
assert.isBigNumber('ethAmount', providedEthAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
const quoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, unFormattedFeePercentage);
|
const { orders, worstCaseQuoteInfo } = quote;
|
||||||
|
|
||||||
const { orders, feeOrders, worstCaseQuoteInfo } = quoteWithAffiliateFee;
|
|
||||||
|
|
||||||
// lowercase input addresses
|
// lowercase input addresses
|
||||||
const normalizedFeeRecipientAddress = feeRecipient.toLowerCase();
|
const normalizedFeeRecipientAddress = feeRecipient.toLowerCase();
|
||||||
|
|
||||||
const signatures = _.map(orders, o => o.signature);
|
const signatures = _.map(orders, o => o.signature);
|
||||||
const feeSignatures = _.map(feeOrders, o => o.signature);
|
|
||||||
|
|
||||||
const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage);
|
const formattedFeePercentage = utils.numberPercentageToEtherTokenAmountPercentage(feePercentage);
|
||||||
|
|
||||||
let params: ForwarderSmartContractParams;
|
let params: ForwarderSmartContractParams;
|
||||||
let methodName: string;
|
let methodName: string;
|
||||||
|
|
||||||
if (quoteWithAffiliateFee.type === MarketOperation.Buy) {
|
if (quote.type === MarketOperation.Buy) {
|
||||||
const { makerAssetFillAmount } = quoteWithAffiliateFee;
|
const { makerAssetFillAmount } = quote;
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
orders,
|
orders,
|
||||||
makerAssetFillAmount,
|
makerAssetFillAmount,
|
||||||
signatures,
|
signatures,
|
||||||
feeOrders,
|
feePercentage: formattedFeePercentage,
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
|
||||||
feeRecipient: normalizedFeeRecipientAddress,
|
feeRecipient: normalizedFeeRecipientAddress,
|
||||||
type: MarketOperation.Buy,
|
type: MarketOperation.Buy,
|
||||||
};
|
};
|
||||||
@ -129,23 +131,21 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
params = {
|
params = {
|
||||||
orders,
|
orders,
|
||||||
signatures,
|
signatures,
|
||||||
feeOrders,
|
feePercentage: formattedFeePercentage,
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
|
||||||
feeRecipient: normalizedFeeRecipientAddress,
|
feeRecipient: normalizedFeeRecipientAddress,
|
||||||
type: MarketOperation.Sell,
|
type: MarketOperation.Sell,
|
||||||
};
|
};
|
||||||
methodName = 'marketSellOrdersWithEth';
|
methodName = 'marketSellOrdersWithEth';
|
||||||
}
|
}
|
||||||
const methodAbi = utils.getMethodAbiFromContractAbi(
|
const methodAbi = utils.getMethodAbiFromContractAbi(this._forwarder.abi, methodName) as MethodAbi;
|
||||||
this._contractWrappers.forwarder.abi,
|
|
||||||
methodName,
|
|
||||||
) as MethodAbi;
|
|
||||||
|
|
||||||
|
const ethAmountWithFees = affiliateFeeUtils
|
||||||
|
.getTotalEthAmountWithAffiliateFee(worstCaseQuoteInfo, feePercentage)
|
||||||
|
.plus(worstCaseQuoteInfo.protocolFeeInEthAmount);
|
||||||
return {
|
return {
|
||||||
params,
|
params,
|
||||||
toAddress: this._contractWrappers.forwarder.address,
|
toAddress: this._forwarder.address,
|
||||||
ethAmount: ethAmount || worstCaseQuoteInfo.totalTakerTokenAmount,
|
ethAmount: providedEthAmount || ethAmountWithFees,
|
||||||
methodAbi,
|
methodAbi,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -157,11 +157,11 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
*/
|
*/
|
||||||
public async executeSwapQuoteOrThrowAsync(
|
public async executeSwapQuoteOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<ForwarderSwapQuoteExecutionOpts>,
|
opts: Partial<SwapQuoteExecutionOpts & ForwarderExtensionContractOpts>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
|
||||||
|
|
||||||
const { ethAmount, takerAddress, gasLimit, gasPrice, feeRecipient, feePercentage } = _.merge(
|
const { ethAmount: providedEthAmount, takerAddress, gasLimit, gasPrice, feeRecipient, feePercentage } = _.merge(
|
||||||
{},
|
{},
|
||||||
constants.DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
|
constants.DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
|
||||||
opts,
|
opts,
|
||||||
@ -169,8 +169,8 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
|
|
||||||
assert.isValidPercentage('feePercentage', feePercentage);
|
assert.isValidPercentage('feePercentage', feePercentage);
|
||||||
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
assert.isETHAddressHex('feeRecipient', feeRecipient);
|
||||||
if (ethAmount !== undefined) {
|
if (providedEthAmount !== undefined) {
|
||||||
assert.isBigNumber('ethAmount', ethAmount);
|
assert.isBigNumber('ethAmount', providedEthAmount);
|
||||||
}
|
}
|
||||||
if (takerAddress !== undefined) {
|
if (takerAddress !== undefined) {
|
||||||
assert.isETHAddressHex('takerAddress', takerAddress);
|
assert.isETHAddressHex('takerAddress', takerAddress);
|
||||||
@ -182,59 +182,50 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
|
|||||||
assert.isBigNumber('gasPrice', gasPrice);
|
assert.isBigNumber('gasPrice', gasPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
const quoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, feePercentage);
|
const { orders, worstCaseQuoteInfo } = quote; // tslint:disable-line:no-unused-variable
|
||||||
|
|
||||||
const { orders, feeOrders, worstCaseQuoteInfo } = quoteWithAffiliateFee; // tslint:disable-line:no-unused-variable
|
|
||||||
|
|
||||||
// get taker address
|
// get taker address
|
||||||
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
|
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
|
||||||
// if no ethAmount is provided, default to the worst totalTakerTokenAmount
|
// if no ethAmount is provided, default to the worst totalTakerAssetAmount
|
||||||
const value = ethAmount || worstCaseQuoteInfo.totalTakerTokenAmount;
|
const ethAmountWithFees = affiliateFeeUtils
|
||||||
|
.getTotalEthAmountWithAffiliateFee(worstCaseQuoteInfo, feePercentage)
|
||||||
|
.plus(worstCaseQuoteInfo.protocolFeeInEthAmount);
|
||||||
// format fee percentage
|
// format fee percentage
|
||||||
const formattedFeePercentage = utils.numberPercentageToEtherTokenAmountPercentage(feePercentage);
|
const formattedFeePercentage = utils.numberPercentageToEtherTokenAmountPercentage(feePercentage);
|
||||||
try {
|
let txHash: string;
|
||||||
let txHash: string;
|
if (quote.type === MarketOperation.Buy) {
|
||||||
if (quoteWithAffiliateFee.type === MarketOperation.Buy) {
|
const { makerAssetFillAmount } = quote;
|
||||||
const { makerAssetFillAmount } = quoteWithAffiliateFee;
|
txHash = await this._forwarder
|
||||||
txHash = await this._contractWrappers.forwarder
|
.marketBuyOrdersWithEth(
|
||||||
.marketBuyOrdersWithEth(
|
orders,
|
||||||
orders,
|
makerAssetFillAmount,
|
||||||
makerAssetFillAmount,
|
orders.map(o => o.signature),
|
||||||
orders.map(o => o.signature),
|
formattedFeePercentage,
|
||||||
formattedFeePercentage,
|
feeRecipient,
|
||||||
feeRecipient,
|
)
|
||||||
)
|
.sendTransactionAsync({
|
||||||
.sendTransactionAsync({
|
from: finalTakerAddress,
|
||||||
value,
|
gas: gasLimit,
|
||||||
from: finalTakerAddress.toLowerCase(),
|
gasPrice,
|
||||||
gas: gasLimit,
|
value: providedEthAmount || ethAmountWithFees,
|
||||||
gasPrice,
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
txHash = await this._forwarder
|
||||||
txHash = await this._contractWrappers.forwarder
|
.marketSellOrdersWithEth(orders, orders.map(o => o.signature), formattedFeePercentage, feeRecipient)
|
||||||
.marketSellOrdersWithEth(orders, orders.map(o => o.signature), formattedFeePercentage, feeRecipient)
|
.sendTransactionAsync({
|
||||||
.sendTransactionAsync({
|
from: finalTakerAddress,
|
||||||
value,
|
gas: gasLimit,
|
||||||
from: finalTakerAddress.toLowerCase(),
|
gasPrice,
|
||||||
gas: gasLimit,
|
value: providedEthAmount || ethAmountWithFees,
|
||||||
gasPrice,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
return txHash;
|
|
||||||
} catch (err) {
|
|
||||||
if (_.includes(err.message, ContractError.SignatureRequestDenied)) {
|
|
||||||
throw new Error(SwapQuoteConsumerError.SignatureRequestDenied);
|
|
||||||
} else if (_.includes(err.message, ForwarderError.CompleteFillFailed)) {
|
|
||||||
throw new Error(SwapQuoteConsumerError.TransactionValueTooLow);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// TODO(dorothy-zbornak): Handle signature request denied
|
||||||
|
// (see contract-wrappers/decorators)
|
||||||
|
// and ForwarderRevertErrors.CompleteBuyFailed.
|
||||||
|
return txHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getEtherTokenAssetDataOrThrowAsync(): Promise<string> {
|
private async _getEtherTokenAssetDataOrThrowAsync(): Promise<string> {
|
||||||
return this._contractWrappers.devUtils
|
return this._devUtils.encodeERC20AssetData(this._contractAddresses.etherToken).callAsync();
|
||||||
.encodeERC20AssetData(this._contractWrappers.contractAddresses.etherToken)
|
|
||||||
.callAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
import { providerUtils } from '@0x/utils';
|
import { providerUtils } from '@0x/utils';
|
||||||
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteConsumerBase,
|
SwapQuoteConsumerBase,
|
||||||
SwapQuoteConsumerOpts,
|
SwapQuoteConsumerOpts,
|
||||||
|
SwapQuoteConsumingOpts,
|
||||||
SwapQuoteExecutionOpts,
|
SwapQuoteExecutionOpts,
|
||||||
SwapQuoteGetOutputOpts,
|
SwapQuoteGetOutputOpts,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
@ -28,7 +29,14 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
|
|||||||
|
|
||||||
private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
|
private readonly _exchangeConsumer: ExchangeSwapQuoteConsumer;
|
||||||
private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
|
private readonly _forwarderConsumer: ForwarderSwapQuoteConsumer;
|
||||||
private readonly _contractWrappers: ContractWrappers;
|
private readonly _contractAddresses: ContractAddresses;
|
||||||
|
|
||||||
|
public static getSwapQuoteConsumer(
|
||||||
|
supportedProvider: SupportedProvider,
|
||||||
|
options: Partial<SwapQuoteConsumerOpts> = {},
|
||||||
|
): SwapQuoteConsumer {
|
||||||
|
return new SwapQuoteConsumer(supportedProvider, options);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
constructor(supportedProvider: SupportedProvider, options: Partial<SwapQuoteConsumerOpts> = {}) {
|
||||||
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
||||||
@ -37,22 +45,19 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
|
|||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.chainId = chainId;
|
this.chainId = chainId;
|
||||||
|
this._contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
||||||
this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, options);
|
this._exchangeConsumer = new ExchangeSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
|
||||||
this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, options);
|
this._forwarderConsumer = new ForwarderSwapQuoteConsumer(supportedProvider, this._contractAddresses, options);
|
||||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
|
||||||
chainId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a SwapQuote, returns 'CalldataInfo' for a 0x exchange call. See type definition of CalldataInfo for more information.
|
* Given a SwapQuote, returns 'CalldataInfo' for a 0x extesion or exchange call. See type definition of CalldataInfo for more information.
|
||||||
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||||
* @param opts Options for getting SmartContractParams. See type definition for more information.
|
* @param opts Options for getting SmartContractParams. See type definition for more information.
|
||||||
*/
|
*/
|
||||||
public async getCalldataOrThrowAsync(
|
public async getCalldataOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
opts: Partial<SwapQuoteGetOutputOpts & SwapQuoteConsumingOpts> = {},
|
||||||
): Promise<CalldataInfo> {
|
): Promise<CalldataInfo> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
||||||
@ -60,13 +65,13 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a SwapQuote, returns 'SmartContractParamsInfo' for a 0x exchange call. See type definition of SmartContractParamsInfo for more information.
|
* Given a SwapQuote, returns 'SmartContractParamsInfo' for a 0x extension or exchange call. See type definition of SmartContractParamsInfo for more information.
|
||||||
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||||
* @param opts Options for getting SmartContractParams. See type definition for more information.
|
* @param opts Options for getting SmartContractParams. See type definition for more information.
|
||||||
*/
|
*/
|
||||||
public async getSmartContractParamsOrThrowAsync(
|
public async getSmartContractParamsOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteGetOutputOpts> = {},
|
opts: Partial<SwapQuoteGetOutputOpts & SwapQuoteConsumingOpts> = {},
|
||||||
): Promise<SmartContractParamsInfo<SmartContractParams>> {
|
): Promise<SmartContractParamsInfo<SmartContractParams>> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
||||||
@ -74,33 +79,38 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a SwapQuote and desired rate (in takerAsset), attempt to execute the swap.
|
* Given a SwapQuote and desired rate (in takerAsset), attempt to execute the swap with 0x extension or exchange contract.
|
||||||
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||||
* @param opts Options for getting CalldataInfo. See type definition for more information.
|
* @param opts Options for getting CalldataInfo. See type definition for more information.
|
||||||
*/
|
*/
|
||||||
public async executeSwapQuoteOrThrowAsync(
|
public async executeSwapQuoteOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteExecutionOpts> = {},
|
opts: Partial<SwapQuoteExecutionOpts & SwapQuoteConsumingOpts> = {},
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
assert.isValidSwapQuote('quote', quote);
|
assert.isValidSwapQuote('quote', quote);
|
||||||
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
|
||||||
return consumer.executeSwapQuoteOrThrowAsync(quote, opts);
|
return consumer.executeSwapQuoteOrThrowAsync(quote, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a SwapQuote, returns optimal 0x protocol interface (extension or no extension) to perform the swap.
|
||||||
|
* @param quote An object that conforms to SwapQuote. See type definition for more information.
|
||||||
|
* @param opts Options for getting optimal exteion contract to fill quote. See type definition for more information.
|
||||||
|
*/
|
||||||
public async getOptimalExtensionContractTypeAsync(
|
public async getOptimalExtensionContractTypeAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<GetExtensionContractTypeOpts> = {},
|
opts: Partial<GetExtensionContractTypeOpts> = {},
|
||||||
): Promise<ExtensionContractType> {
|
): Promise<ExtensionContractType> {
|
||||||
return swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(
|
return swapQuoteConsumerUtils.getExtensionContractTypeForSwapQuoteAsync(
|
||||||
quote,
|
quote,
|
||||||
this._contractWrappers,
|
this._contractAddresses,
|
||||||
this.provider,
|
this.provider,
|
||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getConsumerForSwapQuoteAsync(
|
private async _getConsumerForSwapQuoteAsync(
|
||||||
opts: Partial<SwapQuoteGetOutputOpts>,
|
opts: Partial<SwapQuoteConsumingOpts>,
|
||||||
): Promise<SwapQuoteConsumerBase<SmartContractParams>> {
|
): Promise<SwapQuoteConsumerBase<SmartContractParams>> {
|
||||||
if (opts.useExtensionContract === ExtensionContractType.Forwarder) {
|
if (opts.useExtensionContract === ExtensionContractType.Forwarder) {
|
||||||
return this._forwarderConsumer;
|
return this._forwarderConsumer;
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
import { schemas } from '@0x/json-schemas';
|
import { schemas } from '@0x/json-schemas';
|
||||||
import { assetDataUtils, SignedOrder } from '@0x/order-utils';
|
import { SignedOrder } from '@0x/order-utils';
|
||||||
import { MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
|
import { MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
|
||||||
import { MarketOperation } from '@0x/types';
|
|
||||||
import { BigNumber, providerUtils } from '@0x/utils';
|
import { BigNumber, providerUtils } from '@0x/utils';
|
||||||
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
import {
|
import {
|
||||||
LiquidityForAssetData,
|
LiquidityForTakerMakerAssetDataPair,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
|
MarketOperation,
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
OrdersAndFillableAmounts,
|
OrderPrunerPermittedFeeTypes,
|
||||||
|
PrunedSignedOrder,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteRequestOpts,
|
SwapQuoteRequestOpts,
|
||||||
SwapQuoterError,
|
SwapQuoterError,
|
||||||
@ -20,16 +22,19 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { assert } from './utils/assert';
|
import { assert } from './utils/assert';
|
||||||
import { calculateLiquidity } from './utils/calculate_liquidity';
|
import { calculateLiquidity } from './utils/calculate_liquidity';
|
||||||
import { orderProviderResponseProcessor } from './utils/order_provider_response_processor';
|
import { OrderPruner } from './utils/order_prune_utils';
|
||||||
|
import { protocolFeeUtils } from './utils/protocol_fee_utils';
|
||||||
|
import { sortingUtils } from './utils/sorting_utils';
|
||||||
import { swapQuoteCalculator } from './utils/swap_quote_calculator';
|
import { swapQuoteCalculator } from './utils/swap_quote_calculator';
|
||||||
import { utils } from './utils/utils';
|
|
||||||
|
|
||||||
export class SwapQuoter {
|
export class SwapQuoter {
|
||||||
public readonly provider: ZeroExProvider;
|
public readonly provider: ZeroExProvider;
|
||||||
public readonly orderbook: Orderbook;
|
public readonly orderbook: Orderbook;
|
||||||
public readonly expiryBufferMs: number;
|
public readonly expiryBufferMs: number;
|
||||||
private readonly _contractWrappers: ContractWrappers;
|
public readonly permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
|
||||||
|
private readonly _contractAddresses: ContractAddresses;
|
||||||
|
private readonly _orderPruner: OrderPruner;
|
||||||
|
private readonly _devUtilsContract: DevUtilsContract;
|
||||||
/**
|
/**
|
||||||
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
* Instantiates a new SwapQuoter instance given existing liquidity in the form of orders and feeOrders.
|
||||||
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
|
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
|
||||||
@ -132,7 +137,11 @@ export class SwapQuoter {
|
|||||||
* @return An instance of SwapQuoter
|
* @return An instance of SwapQuoter
|
||||||
*/
|
*/
|
||||||
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
|
constructor(supportedProvider: SupportedProvider, orderbook: Orderbook, options: Partial<SwapQuoterOpts> = {}) {
|
||||||
const { chainId, expiryBufferMs } = _.merge({}, constants.DEFAULT_SWAP_QUOTER_OPTS, options);
|
const { chainId, expiryBufferMs, permittedOrderFeeTypes } = _.merge(
|
||||||
|
{},
|
||||||
|
constants.DEFAULT_SWAP_QUOTER_OPTS,
|
||||||
|
options,
|
||||||
|
);
|
||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
assert.isValidOrderbook('orderbook', orderbook);
|
assert.isValidOrderbook('orderbook', orderbook);
|
||||||
assert.isNumber('chainId', chainId);
|
assert.isNumber('chainId', chainId);
|
||||||
@ -140,10 +149,15 @@ export class SwapQuoter {
|
|||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.orderbook = orderbook;
|
this.orderbook = orderbook;
|
||||||
this.expiryBufferMs = expiryBufferMs;
|
this.expiryBufferMs = expiryBufferMs;
|
||||||
this._contractWrappers = new ContractWrappers(this.provider, {
|
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||||
chainId,
|
this._contractAddresses = getContractAddressesForChainOrThrow(chainId);
|
||||||
|
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
|
||||||
|
this._orderPruner = new OrderPruner(this._devUtilsContract, {
|
||||||
|
expiryBufferMs: this.expiryBufferMs,
|
||||||
|
permittedOrderFeeTypes: this.permittedOrderFeeTypes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
|
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
|
||||||
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.
|
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.
|
||||||
@ -214,12 +228,8 @@ export class SwapQuoter {
|
|||||||
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
|
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
|
||||||
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
|
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
|
||||||
assert.isBigNumber('makerAssetBuyAmount', makerAssetBuyAmount);
|
assert.isBigNumber('makerAssetBuyAmount', makerAssetBuyAmount);
|
||||||
const makerAssetData = await this._contractWrappers.devUtils
|
const makerAssetData = await this._devUtilsContract.encodeERC20AssetData(makerTokenAddress).callAsync();
|
||||||
.encodeERC20AssetData(makerTokenAddress)
|
const takerAssetData = await this._devUtilsContract.encodeERC20AssetData(takerTokenAddress).callAsync();
|
||||||
.callAsync();
|
|
||||||
const takerAssetData = await this._contractWrappers.devUtils
|
|
||||||
.encodeERC20AssetData(takerTokenAddress)
|
|
||||||
.callAsync();
|
|
||||||
const swapQuote = this.getMarketBuySwapQuoteForAssetDataAsync(
|
const swapQuote = this.getMarketBuySwapQuoteForAssetDataAsync(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
@ -248,12 +258,8 @@ export class SwapQuoter {
|
|||||||
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
|
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
|
||||||
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
|
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
|
||||||
assert.isBigNumber('takerAssetSellAmount', takerAssetSellAmount);
|
assert.isBigNumber('takerAssetSellAmount', takerAssetSellAmount);
|
||||||
const makerAssetData = await this._contractWrappers.devUtils
|
const makerAssetData = await this._devUtilsContract.encodeERC20AssetData(makerTokenAddress).callAsync();
|
||||||
.encodeERC20AssetData(makerTokenAddress)
|
const takerAssetData = await this._devUtilsContract.encodeERC20AssetData(takerTokenAddress).callAsync();
|
||||||
.callAsync();
|
|
||||||
const takerAssetData = await this._contractWrappers.devUtils
|
|
||||||
.encodeERC20AssetData(takerTokenAddress)
|
|
||||||
.callAsync();
|
|
||||||
const swapQuote = this.getMarketSellSwapQuoteForAssetDataAsync(
|
const swapQuote = this.getMarketSellSwapQuoteForAssetDataAsync(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
@ -269,27 +275,26 @@ export class SwapQuoter {
|
|||||||
* @param makerAssetData The makerAssetData of the desired asset to swap for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* @param makerAssetData The makerAssetData of the desired asset to swap for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
* @param takerAssetData The takerAssetData of the asset to swap makerAssetData for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* @param takerAssetData The takerAssetData of the asset to swap makerAssetData for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
*
|
*
|
||||||
* @return An object that conforms to LiquidityForAssetData that satisfies the request. See type definition for more information.
|
* @return An object that conforms to LiquidityForTakerMakerAssetDataPair that satisfies the request. See type definition for more information.
|
||||||
*/
|
*/
|
||||||
public async getLiquidityForMakerTakerAssetDataPairAsync(
|
public async getLiquidityForMakerTakerAssetDataPairAsync(
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
): Promise<LiquidityForAssetData> {
|
): Promise<LiquidityForTakerMakerAssetDataPair> {
|
||||||
assert.isString('makerAssetData', makerAssetData);
|
assert.isString('makerAssetData', makerAssetData);
|
||||||
assert.isString('takerAssetData', takerAssetData);
|
assert.isString('takerAssetData', takerAssetData);
|
||||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(takerAssetData).callAsync();
|
||||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
|
||||||
|
|
||||||
const assetPairs = await this.getAvailableMakerAssetDatasAsync(takerAssetData);
|
const assetPairs = await this.getAvailableMakerAssetDatasAsync(takerAssetData);
|
||||||
if (!assetPairs.includes(makerAssetData)) {
|
if (!assetPairs.includes(makerAssetData)) {
|
||||||
return {
|
return {
|
||||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
makerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
takerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ordersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData);
|
const prunedOrders = await this.getPrunedSignedOrdersAsync(makerAssetData, takerAssetData);
|
||||||
return calculateLiquidity(ordersAndFillableAmounts);
|
return calculateLiquidity(prunedOrders);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,7 +304,7 @@ export class SwapQuoter {
|
|||||||
*/
|
*/
|
||||||
public async getAvailableTakerAssetDatasAsync(makerAssetData: string): Promise<string[]> {
|
public async getAvailableTakerAssetDatasAsync(makerAssetData: string): Promise<string[]> {
|
||||||
assert.isString('makerAssetData', makerAssetData);
|
assert.isString('makerAssetData', makerAssetData);
|
||||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
|
||||||
const allAssetPairs = await this.orderbook.getAvailableAssetDatasAsync();
|
const allAssetPairs = await this.orderbook.getAvailableAssetDatasAsync();
|
||||||
const assetPairs = allAssetPairs
|
const assetPairs = allAssetPairs
|
||||||
.filter(pair => pair.assetDataA.assetData === makerAssetData)
|
.filter(pair => pair.assetDataA.assetData === makerAssetData)
|
||||||
@ -314,7 +319,7 @@ export class SwapQuoter {
|
|||||||
*/
|
*/
|
||||||
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
|
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
|
||||||
assert.isString('takerAssetData', takerAssetData);
|
assert.isString('takerAssetData', takerAssetData);
|
||||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(takerAssetData).callAsync();
|
||||||
const allAssetPairs = await this.orderbook.getAvailableAssetDatasAsync();
|
const allAssetPairs = await this.orderbook.getAvailableAssetDatasAsync();
|
||||||
const assetPairs = allAssetPairs
|
const assetPairs = allAssetPairs
|
||||||
.filter(pair => pair.assetDataB.assetData === takerAssetData)
|
.filter(pair => pair.assetDataB.assetData === takerAssetData)
|
||||||
@ -324,6 +329,8 @@ export class SwapQuoter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the taker + maker asset pair is available from the order provider provided to `SwapQuote`.
|
* Validates the taker + maker asset pair is available from the order provider provided to `SwapQuote`.
|
||||||
|
* @param makerAssetData The makerAssetData of the desired asset to swap for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
|
* @param takerAssetData The takerAssetData of the asset to swap makerAssetData for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
*
|
*
|
||||||
* @return A boolean on if the taker, maker pair exists
|
* @return A boolean on if the taker, maker pair exists
|
||||||
*/
|
*/
|
||||||
@ -333,41 +340,31 @@ export class SwapQuoter {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
assert.isString('makerAssetData', makerAssetData);
|
assert.isString('makerAssetData', makerAssetData);
|
||||||
assert.isString('takerAssetData', takerAssetData);
|
assert.isString('takerAssetData', takerAssetData);
|
||||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
|
||||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(takerAssetData).callAsync();
|
||||||
const availableMakerAssetDatas = await this.getAvailableMakerAssetDatasAsync(takerAssetData);
|
const availableMakerAssetDatas = await this.getAvailableMakerAssetDatasAsync(takerAssetData);
|
||||||
return _.includes(availableMakerAssetDatas, makerAssetData);
|
return _.includes(availableMakerAssetDatas, makerAssetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
|
* Grab orders from the order provider, prunes for valid orders with provided OrderPruner options
|
||||||
* @param makerAssetData The makerAssetData of the desired asset to swap for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* @param makerAssetData The makerAssetData of the desired asset to swap for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
* @param takerAssetData The takerAssetData of the asset to swap makerAssetData for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* @param takerAssetData The takerAssetData of the asset to swap makerAssetData for (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
*/
|
*/
|
||||||
public async getOrdersAndFillableAmountsAsync(
|
public async getPrunedSignedOrdersAsync(
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
): Promise<OrdersAndFillableAmounts> {
|
): Promise<PrunedSignedOrder[]> {
|
||||||
assert.isString('makerAssetData', makerAssetData);
|
assert.isString('makerAssetData', makerAssetData);
|
||||||
assert.isString('takerAssetData', takerAssetData);
|
assert.isString('takerAssetData', takerAssetData);
|
||||||
assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(takerAssetData).callAsync();
|
||||||
assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
|
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
|
||||||
const zrxTokenAssetData = await this._getZrxTokenAssetDataOrThrowAsync();
|
|
||||||
// get orders
|
// get orders
|
||||||
const response = await this.orderbook.getOrdersAsync(makerAssetData, takerAssetData);
|
const apiOrders = await this.orderbook.getOrdersAsync(makerAssetData, takerAssetData);
|
||||||
const adaptedResponse = { orders: response.map(o => ({ ...o.order, ...o.metaData })) };
|
const orders = _.map(apiOrders, o => o.order);
|
||||||
// since the order provider is an injected dependency, validate that it respects the API
|
const prunedOrders = await this._orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
// ie. it should only return maker/taker assetDatas that are specified
|
const sortedPrunedOrders = sortingUtils.sortOrders(prunedOrders);
|
||||||
orderProviderResponseProcessor.throwIfInvalidResponse(adaptedResponse, makerAssetData, takerAssetData);
|
return sortedPrunedOrders;
|
||||||
// process the responses into one object
|
|
||||||
const isMakerAssetZrxToken = makerAssetData === zrxTokenAssetData;
|
|
||||||
const ordersAndFillableAmounts = await orderProviderResponseProcessor.processAsync(
|
|
||||||
adaptedResponse,
|
|
||||||
isMakerAssetZrxToken,
|
|
||||||
this.expiryBufferMs,
|
|
||||||
this._contractWrappers.orderValidator,
|
|
||||||
);
|
|
||||||
return ordersAndFillableAmounts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -375,18 +372,16 @@ export class SwapQuoter {
|
|||||||
* @param swapQuote The swapQuote in question to check enough allowance enabled for 0x exchange contracts to conduct the swap.
|
* @param swapQuote The swapQuote in question to check enough allowance enabled for 0x exchange contracts to conduct the swap.
|
||||||
* @param takerAddress The address of the taker of the provided swapQuote
|
* @param takerAddress The address of the taker of the provided swapQuote
|
||||||
*/
|
*/
|
||||||
public async isTakerAddressAllowanceEnoughForBestAndWorstQuoteInfoAsync(
|
public async isSwapQuoteFillableByTakerAddressAsync(
|
||||||
swapQuote: SwapQuote,
|
swapQuote: SwapQuote,
|
||||||
takerAddress: string,
|
takerAddress: string,
|
||||||
): Promise<[boolean, boolean]> {
|
): Promise<[boolean, boolean]> {
|
||||||
const orderValidator = this._contractWrappers.orderValidator;
|
const balanceAndAllowance = await this._devUtilsContract
|
||||||
const balanceAndAllowance = await orderValidator
|
.getBalanceAndAssetProxyAllowance(takerAddress, swapQuote.takerAssetData)
|
||||||
.getBalanceAndAllowance(takerAddress, swapQuote.takerAssetData)
|
|
||||||
.callAsync();
|
.callAsync();
|
||||||
const allowance = balanceAndAllowance[1];
|
|
||||||
return [
|
return [
|
||||||
allowance.isGreaterThanOrEqualTo(swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount),
|
balanceAndAllowance[1].isGreaterThanOrEqualTo(swapQuote.bestCaseQuoteInfo.totalTakerAssetAmount),
|
||||||
allowance.isGreaterThanOrEqualTo(swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount),
|
balanceAndAllowance[1].isGreaterThanOrEqualTo(swapQuote.worstCaseQuoteInfo.totalTakerAssetAmount),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,13 +393,10 @@ export class SwapQuoter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the assetData that represents the ZRX token.
|
* Utility function to get assetData for Ether token.
|
||||||
* Will throw if ZRX does not exist for the current chain.
|
|
||||||
*/
|
*/
|
||||||
private async _getZrxTokenAssetDataOrThrowAsync(): Promise<string> {
|
public async getEtherTokenAssetDataOrThrowAsync(): Promise<string> {
|
||||||
return this._contractWrappers.devUtils
|
return this._devUtilsContract.encodeERC20AssetData(this._contractAddresses.etherToken).callAsync();
|
||||||
.encodeERC20AssetData(this._contractWrappers.contractAddresses.zrxToken)
|
|
||||||
.callAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -417,30 +409,20 @@ export class SwapQuoter {
|
|||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
options: Partial<SwapQuoteRequestOpts>,
|
options: Partial<SwapQuoteRequestOpts>,
|
||||||
): Promise<SwapQuote> {
|
): Promise<SwapQuote> {
|
||||||
const { slippagePercentage, shouldDisableRequestingFeeOrders } = _.merge(
|
const { slippagePercentage } = _.merge({}, constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS, options);
|
||||||
{},
|
|
||||||
constants.DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
assert.isString('makerAssetData', makerAssetData);
|
assert.isString('makerAssetData', makerAssetData);
|
||||||
assert.isString('takerAssetData', takerAssetData);
|
assert.isString('takerAssetData', takerAssetData);
|
||||||
assert.isNumber('slippagePercentage', slippagePercentage);
|
assert.isNumber('slippagePercentage', slippagePercentage);
|
||||||
const zrxTokenAssetData = await this._getZrxTokenAssetDataOrThrowAsync();
|
let gasPrice: BigNumber;
|
||||||
const isMakerAssetZrxToken = makerAssetData === zrxTokenAssetData;
|
if (!!options.gasPrice) {
|
||||||
// get the relevant orders for the makerAsset
|
gasPrice = options.gasPrice;
|
||||||
const ordersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData);
|
assert.isBigNumber('gasPrice', gasPrice);
|
||||||
const doesOrdersRequireFeeOrders =
|
} else {
|
||||||
!isMakerAssetZrxToken && utils.isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts);
|
gasPrice = await protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
|
||||||
const isRequestingFeeOrders = !shouldDisableRequestingFeeOrders && doesOrdersRequireFeeOrders;
|
|
||||||
let feeOrdersAndFillableAmounts = constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS;
|
|
||||||
if (isRequestingFeeOrders) {
|
|
||||||
feeOrdersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync(
|
|
||||||
zrxTokenAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
// get the relevant orders for the makerAsset
|
||||||
if (ordersAndFillableAmounts.orders.length === 0) {
|
const prunedOrders = await this.getPrunedSignedOrdersAsync(makerAssetData, takerAssetData);
|
||||||
|
if (prunedOrders.length === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${
|
`${
|
||||||
SwapQuoterError.AssetUnavailable
|
SwapQuoterError.AssetUnavailable
|
||||||
@ -448,33 +430,21 @@ export class SwapQuoter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRequestingFeeOrders && feeOrdersAndFillableAmounts.orders.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
`${
|
|
||||||
SwapQuoterError.FeeAssetUnavailable
|
|
||||||
}: For makerAssetdata ${makerAssetData} and takerAssetdata ${takerAssetData}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let swapQuote: SwapQuote;
|
let swapQuote: SwapQuote;
|
||||||
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
if (marketOperation === MarketOperation.Buy) {
|
||||||
swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
|
swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
feeOrdersAndFillableAmounts,
|
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
slippagePercentage,
|
slippagePercentage,
|
||||||
isMakerAssetZrxToken,
|
gasPrice,
|
||||||
shouldDisableRequestingFeeOrders,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
|
swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
feeOrdersAndFillableAmounts,
|
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
slippagePercentage,
|
slippagePercentage,
|
||||||
isMakerAssetZrxToken,
|
gasPrice,
|
||||||
shouldDisableRequestingFeeOrders,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,27 @@
|
|||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { MethodAbi } from 'ethereum-types';
|
import { MethodAbi } from 'ethereum-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||||
|
* permittedOrderFeeTypes: A set of all the takerFee types that OrderPruner will filter for
|
||||||
|
*/
|
||||||
|
export interface OrderPrunerOpts {
|
||||||
|
expiryBufferMs: number;
|
||||||
|
permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the on-chain metadata of a signed order
|
||||||
|
*/
|
||||||
|
export interface OrderPrunerOnChainMetadata {
|
||||||
|
orderStatus: number;
|
||||||
|
orderHash: string;
|
||||||
|
orderTakerAssetFilledAmount: BigNumber;
|
||||||
|
fillableTakerAssetAmount: BigNumber;
|
||||||
|
isValidSignature: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* makerAssetData: The assetData representing the desired makerAsset.
|
* makerAssetData: The assetData representing the desired makerAsset.
|
||||||
* takerAssetData: The assetData representing the desired takerAsset.
|
* takerAssetData: The assetData representing the desired takerAsset.
|
||||||
@ -12,18 +32,14 @@ export interface OrderProviderRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* orders: An array of orders with optional remaining fillable makerAsset amounts. See type for more info.
|
* fillableMakerAssetAmount: Amount of makerAsset that is fillable
|
||||||
|
* fillableTakerAssetAmount: Amount of takerAsset that is fillable
|
||||||
|
* fillableTakerFeeAmount: Amount of takerFee paid to fill fillableTakerAssetAmount
|
||||||
*/
|
*/
|
||||||
export interface OrderProviderResponse {
|
export interface PrunedSignedOrder extends SignedOrder {
|
||||||
orders: SignedOrderWithRemainingFillableMakerAssetAmount[];
|
fillableMakerAssetAmount: BigNumber;
|
||||||
}
|
fillableTakerAssetAmount: BigNumber;
|
||||||
|
fillableTakerFeeAmount: BigNumber;
|
||||||
/**
|
|
||||||
* A normal SignedOrder with one extra optional property `remainingFillableMakerAssetAmount`
|
|
||||||
* remainingFillableMakerAssetAmount: The amount of the makerAsset that is available to be filled
|
|
||||||
*/
|
|
||||||
export interface SignedOrderWithRemainingFillableMakerAssetAmount extends SignedOrder {
|
|
||||||
remainingFillableMakerAssetAmount?: BigNumber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,13 +47,13 @@ export interface SignedOrderWithRemainingFillableMakerAssetAmount extends Signed
|
|||||||
* calldataHexString: The hexstring of the calldata.
|
* calldataHexString: The hexstring of the calldata.
|
||||||
* methodAbi: The ABI of the smart contract method to call.
|
* methodAbi: The ABI of the smart contract method to call.
|
||||||
* toAddress: The contract address to call.
|
* toAddress: The contract address to call.
|
||||||
* ethAmount: If provided, the eth amount in wei to send with the smart contract call.
|
* ethAmount: The eth amount in wei to send with the smart contract call.
|
||||||
*/
|
*/
|
||||||
export interface CalldataInfo {
|
export interface CalldataInfo {
|
||||||
calldataHexString: string;
|
calldataHexString: string;
|
||||||
methodAbi: MethodAbi;
|
methodAbi: MethodAbi;
|
||||||
toAddress: string;
|
toAddress: string;
|
||||||
ethAmount?: BigNumber;
|
ethAmount: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,7 +66,7 @@ export interface CalldataInfo {
|
|||||||
export interface SmartContractParamsInfo<T> {
|
export interface SmartContractParamsInfo<T> {
|
||||||
params: T;
|
params: T;
|
||||||
toAddress: string;
|
toAddress: string;
|
||||||
ethAmount?: BigNumber;
|
ethAmount: BigNumber;
|
||||||
methodAbi: MethodAbi;
|
methodAbi: MethodAbi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,14 +111,10 @@ export enum ExtensionContractType {
|
|||||||
export type ExchangeSmartContractParams = ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams;
|
export type ExchangeSmartContractParams = ExchangeMarketBuySmartContractParams | ExchangeMarketSellSmartContractParams;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above.
|
|
||||||
* feeSignatures: An array of signatures that attest that the maker of the orders in fact made the orders.
|
|
||||||
* feePercentage: Optional affiliate fee percentage used to calculate the eth amount paid to fee recipient.
|
* feePercentage: Optional affiliate fee percentage used to calculate the eth amount paid to fee recipient.
|
||||||
* feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
|
* feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
|
||||||
*/
|
*/
|
||||||
export interface ForwarderSmartContractParamsBase {
|
export interface ForwarderSmartContractParamsBase {
|
||||||
feeOrders: SignedOrder[];
|
|
||||||
feeSignatures: string[];
|
|
||||||
feePercentage: BigNumber;
|
feePercentage: BigNumber;
|
||||||
feeRecipient: string;
|
feeRecipient: string;
|
||||||
}
|
}
|
||||||
@ -137,12 +149,12 @@ export type SmartContractParams = ForwarderSmartContractParams | ExchangeSmartCo
|
|||||||
* executeSwapQuoteOrThrowAsync: Executes a web3 transaction to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
|
* executeSwapQuoteOrThrowAsync: Executes a web3 transaction to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoteConsumerBase<T> {
|
export interface SwapQuoteConsumerBase<T> {
|
||||||
getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteGetOutputOptsBase>): Promise<CalldataInfo>;
|
getCalldataOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteGetOutputOpts>): Promise<CalldataInfo>;
|
||||||
getSmartContractParamsOrThrowAsync(
|
getSmartContractParamsOrThrowAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
opts: Partial<SwapQuoteGetOutputOptsBase>,
|
opts: Partial<SwapQuoteGetOutputOpts>,
|
||||||
): Promise<SmartContractParamsInfo<T>>;
|
): Promise<SmartContractParamsInfo<T>>;
|
||||||
executeSwapQuoteOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteExecutionOptsBase>): Promise<string>;
|
executeSwapQuoteOrThrowAsync(quote: SwapQuote, opts: Partial<SwapQuoteExecutionOpts>): Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,28 +167,37 @@ export interface SwapQuoteConsumerOpts {
|
|||||||
/**
|
/**
|
||||||
* Represents the options provided to a generic SwapQuoteConsumer
|
* Represents the options provided to a generic SwapQuoteConsumer
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoteGetOutputOptsBase {}
|
export interface SwapQuoteGetOutputOpts {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
||||||
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
|
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
|
||||||
* gasPrice: Gas price in Wei to use for a transaction
|
* gasPrice: Gas price in Wei to use for a transaction
|
||||||
|
* ethAmount: The amount of eth sent with the execution of a swap
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoteExecutionOptsBase extends SwapQuoteGetOutputOptsBase {
|
export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
|
||||||
takerAddress?: string;
|
takerAddress?: string;
|
||||||
gasLimit?: number;
|
gasLimit?: number;
|
||||||
gasPrice?: BigNumber;
|
gasPrice?: BigNumber;
|
||||||
|
ethAmount?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* ethAmount: The amount of eth (in Wei) sent to the forwarder contract.
|
||||||
* feePercentage: percentage (up to 5%) of the taker asset paid to feeRecipient
|
* feePercentage: percentage (up to 5%) of the taker asset paid to feeRecipient
|
||||||
* feeRecipient: address of the receiver of the feePercentage of taker asset
|
* feeRecipient: address of the receiver of the feePercentage of taker asset
|
||||||
* ethAmount: The amount of eth (in Wei) sent to the forwarder contract.
|
|
||||||
*/
|
*/
|
||||||
export interface ForwarderSwapQuoteGetOutputOpts extends SwapQuoteGetOutputOptsBase {
|
export interface ForwarderExtensionContractOpts {
|
||||||
|
ethAmount?: BigNumber;
|
||||||
feePercentage: number;
|
feePercentage: number;
|
||||||
feeRecipient: string;
|
feeRecipient: string;
|
||||||
ethAmount?: BigNumber;
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Options for how SwapQuoteConsumer will generate output
|
||||||
|
*/
|
||||||
|
export interface SwapQuoteConsumingOpts {
|
||||||
|
useExtensionContract: ExtensionContractType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
|
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
|
||||||
@ -186,26 +207,10 @@ export interface GetExtensionContractTypeOpts {
|
|||||||
ethAmount?: BigNumber;
|
ethAmount?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
|
|
||||||
* useConsumerType: If provided, defaults the SwapQuoteConsumer to create output consumed by ConsumerType.
|
|
||||||
*/
|
|
||||||
export interface SwapQuoteGetOutputOpts extends ForwarderSwapQuoteGetOutputOpts {
|
|
||||||
useExtensionContract: ExtensionContractType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ForwarderSwapQuoteExecutionOpts extends ForwarderSwapQuoteGetOutputOpts, SwapQuoteExecutionOptsBase {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the options for executing a swap quote with SwapQuoteConsumer
|
|
||||||
*/
|
|
||||||
export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts, ForwarderSwapQuoteExecutionOpts {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
* makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
* makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
|
||||||
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
|
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
|
||||||
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above.
|
|
||||||
* bestCaseQuoteInfo: Info about the best case price for the asset.
|
* bestCaseQuoteInfo: Info about the best case price for the asset.
|
||||||
* worstCaseQuoteInfo: Info about the worst case price for the asset.
|
* worstCaseQuoteInfo: Info about the worst case price for the asset.
|
||||||
*/
|
*/
|
||||||
@ -213,7 +218,6 @@ export interface SwapQuoteBase {
|
|||||||
takerAssetData: string;
|
takerAssetData: string;
|
||||||
makerAssetData: string;
|
makerAssetData: string;
|
||||||
orders: SignedOrder[];
|
orders: SignedOrder[];
|
||||||
feeOrders: SignedOrder[];
|
|
||||||
bestCaseQuoteInfo: SwapQuoteInfo;
|
bestCaseQuoteInfo: SwapQuoteInfo;
|
||||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||||
}
|
}
|
||||||
@ -236,36 +240,28 @@ export interface MarketBuySwapQuote extends SwapQuoteBase {
|
|||||||
type: MarketOperation.Buy;
|
type: MarketOperation.Buy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SwapQuoteWithAffiliateFeeBase {
|
|
||||||
feePercentage: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarketSellSwapQuoteWithAffiliateFee extends SwapQuoteWithAffiliateFeeBase, MarketSellSwapQuote {}
|
|
||||||
|
|
||||||
export interface MarketBuySwapQuoteWithAffiliateFee extends SwapQuoteWithAffiliateFeeBase, MarketBuySwapQuote {}
|
|
||||||
|
|
||||||
export type SwapQuoteWithAffiliateFee = MarketBuySwapQuoteWithAffiliateFee | MarketSellSwapQuoteWithAffiliateFee;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* feeTakerTokenAmount: The amount of takerToken required any fee concerned with completing the swap.
|
* feeTakerAssetAmount: The amount of takerAsset reserved for paying takerFees when swapping for desired assets.
|
||||||
* takerTokenAmount: The amount of takerToken required to conduct the swap.
|
* takerAssetAmount: The amount of takerAsset swapped for desired makerAsset.
|
||||||
* totalTakerTokenAmount: The total amount of takerToken required to complete the swap (filling orders, feeOrders, and paying affiliate fee)
|
* totalTakerAssetAmount: The total amount of takerAsset required to complete the swap (filling orders, and paying takerFees).
|
||||||
* makerTokenAmount: The amount of makerToken that will be acquired through the swap.
|
* makerAssetAmount: The amount of makerAsset that will be acquired through the swap.
|
||||||
|
* protocolFeeInEthAmount: The amount of eth to pay as protocol fee to perform the swap for desired asset.
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoteInfo {
|
export interface SwapQuoteInfo {
|
||||||
feeTakerTokenAmount: BigNumber;
|
feeTakerAssetAmount: BigNumber;
|
||||||
totalTakerTokenAmount: BigNumber;
|
takerAssetAmount: BigNumber;
|
||||||
takerTokenAmount: BigNumber;
|
totalTakerAssetAmount: BigNumber;
|
||||||
makerTokenAmount: BigNumber;
|
makerAssetAmount: BigNumber;
|
||||||
|
protocolFeeInEthAmount: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shouldDisableRequestingFeeOrders: If set to true, requesting a swapQuote will not perform any computation or requests for fees.
|
|
||||||
* slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%).
|
* slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%).
|
||||||
|
* gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoteRequestOpts {
|
export interface SwapQuoteRequestOpts {
|
||||||
shouldDisableRequestingFeeOrders: boolean;
|
|
||||||
slippagePercentage: number;
|
slippagePercentage: number;
|
||||||
|
gasPrice?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,7 +269,7 @@ export interface SwapQuoteRequestOpts {
|
|||||||
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
|
||||||
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||||
*/
|
*/
|
||||||
export interface SwapQuoterOpts {
|
export interface SwapQuoterOpts extends OrderPrunerOpts {
|
||||||
chainId: number;
|
chainId: number;
|
||||||
orderRefreshIntervalMs: number;
|
orderRefreshIntervalMs: number;
|
||||||
expiryBufferMs: number;
|
expiryBufferMs: number;
|
||||||
@ -295,28 +291,33 @@ export enum SwapQuoteConsumerError {
|
|||||||
*/
|
*/
|
||||||
export enum SwapQuoterError {
|
export enum SwapQuoterError {
|
||||||
NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
|
NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
|
||||||
NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND',
|
|
||||||
StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR',
|
StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR',
|
||||||
InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY',
|
InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY',
|
||||||
InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
|
|
||||||
InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE',
|
|
||||||
AssetUnavailable = 'ASSET_UNAVAILABLE',
|
AssetUnavailable = 'ASSET_UNAVAILABLE',
|
||||||
FeeAssetUnavailable = 'FEE_ASSET_UNAVAILABLE',
|
NoGasPriceProvidedOrEstimated = 'NO_GAS_PRICE_PROVIDED_OR_ESTIMATED',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* orders: An array of signed orders
|
* Represents available liquidity for a given assetData.
|
||||||
* remainingFillableMakerAssetAmounts: A list of fillable amounts for the signed orders. The index of an item in the array associates the amount with the corresponding order.
|
|
||||||
*/
|
*/
|
||||||
export interface OrdersAndFillableAmounts {
|
export interface LiquidityForTakerMakerAssetDataPair {
|
||||||
orders: SignedOrder[];
|
makerAssetAvailableInBaseUnits: BigNumber;
|
||||||
remainingFillableMakerAssetAmounts: BigNumber[];
|
takerAssetAvailableInBaseUnits: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents available liquidity for a given assetData
|
* Represents two main market operations supported by asset-swapper.
|
||||||
*/
|
*/
|
||||||
export interface LiquidityForAssetData {
|
export enum MarketOperation {
|
||||||
makerTokensAvailableInBaseUnits: BigNumber;
|
Sell = 'Sell',
|
||||||
takerTokensAvailableInBaseUnits: BigNumber;
|
Buy = 'Buy',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents varying order takerFee types that can be pruned for by OrderPruner.
|
||||||
|
*/
|
||||||
|
export enum OrderPrunerPermittedFeeTypes {
|
||||||
|
NoFees = 'NO_FEES',
|
||||||
|
MakerDenominatedTakerFee = 'MAKER_DENOMINATED_TAKER_FEE',
|
||||||
|
TakerDenominatedTakerFee = 'TAKER_DENOMINATED_TAKER_FEE',
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { SwapQuote, SwapQuoteInfo, SwapQuoteWithAffiliateFee } from '../types';
|
import { constants } from '../constants';
|
||||||
|
import { SwapQuoteInfo } from '../types';
|
||||||
|
|
||||||
|
import { assert } from './assert';
|
||||||
|
|
||||||
export const affiliateFeeUtils = {
|
export const affiliateFeeUtils = {
|
||||||
getSwapQuoteWithAffiliateFee(quote: SwapQuote, feePercentage: number): SwapQuoteWithAffiliateFee {
|
/**
|
||||||
const newQuote = _.clone(quote);
|
* Get the amount of eth to send for a forwarder contract call (includes takerAssetAmount, protocol fees, and specified affiliate fee amount)
|
||||||
newQuote.bestCaseQuoteInfo = getSwapQuoteInfoWithAffiliateFee(newQuote.bestCaseQuoteInfo, feePercentage);
|
* @param swapQuoteInfo SwapQuoteInfo to generate total eth amount from
|
||||||
newQuote.worstCaseQuoteInfo = getSwapQuoteInfoWithAffiliateFee(newQuote.worstCaseQuoteInfo, feePercentage);
|
* @param feePercentage Percentage of additive fees to apply to totalTakerAssetAmount + protocol fee. (max 5%)
|
||||||
return { ...newQuote, ...{ feePercentage } };
|
*/
|
||||||
|
getTotalEthAmountWithAffiliateFee(swapQuoteInfo: SwapQuoteInfo, feePercentage: number): BigNumber {
|
||||||
|
assert.assert(
|
||||||
|
feePercentage >= 0 && feePercentage <= constants.MAX_AFFILIATE_FEE_PERCENTAGE,
|
||||||
|
'feePercentage must be between range 0-0.05 (inclusive)',
|
||||||
|
);
|
||||||
|
const ethAmount = swapQuoteInfo.protocolFeeInEthAmount.plus(swapQuoteInfo.totalTakerAssetAmount);
|
||||||
|
const affiliateFeeAmount = ethAmount.multipliedBy(feePercentage);
|
||||||
|
const ethAmountWithFees = ethAmount.plus(affiliateFeeAmount);
|
||||||
|
return ethAmountWithFees;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a fee based on feePercentage of the takerTokenAmount and adds it to the feeTakerTokenAmount and totalTakerTokenAmount
|
|
||||||
* @param quoteInfo quote information to add fee to
|
|
||||||
* @param feePercentage the percentage of takerTokenAmount charged additionally as a fee
|
|
||||||
*/
|
|
||||||
const getSwapQuoteInfoWithAffiliateFee = (quoteInfo: SwapQuoteInfo, feePercentage: number): SwapQuoteInfo => {
|
|
||||||
const newQuoteInfo = _.clone(quoteInfo);
|
|
||||||
const affiliateFeeAmount = quoteInfo.takerTokenAmount
|
|
||||||
.multipliedBy(feePercentage)
|
|
||||||
.integerValue(BigNumber.ROUND_CEIL);
|
|
||||||
const newFeeAmount = quoteInfo.feeTakerTokenAmount.plus(affiliateFeeAmount);
|
|
||||||
newQuoteInfo.feeTakerTokenAmount = newFeeAmount;
|
|
||||||
newQuoteInfo.totalTakerTokenAmount = newFeeAmount.plus(quoteInfo.takerTokenAmount);
|
|
||||||
return newQuoteInfo;
|
|
||||||
};
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { assert as sharedAssert } from '@0x/assert';
|
import { assert as sharedAssert } from '@0x/assert';
|
||||||
import { schemas } from '@0x/json-schemas';
|
import { schemas } from '@0x/json-schemas';
|
||||||
import { Orderbook } from '@0x/orderbook';
|
import { Orderbook } from '@0x/orderbook';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
import { Order, SignedOrder } from '@0x/types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { OrderProviderRequest, SwapQuote, SwapQuoteInfo } from '../types';
|
import { MarketOperation, OrderProviderRequest, SwapQuote, SwapQuoteInfo } from '../types';
|
||||||
|
|
||||||
|
import { utils } from './utils';
|
||||||
|
|
||||||
export const assert = {
|
export const assert = {
|
||||||
...sharedAssert,
|
...sharedAssert,
|
||||||
@ -12,8 +14,7 @@ export const assert = {
|
|||||||
sharedAssert.isHexString(`${variableName}.takerAssetData`, swapQuote.takerAssetData);
|
sharedAssert.isHexString(`${variableName}.takerAssetData`, swapQuote.takerAssetData);
|
||||||
sharedAssert.isHexString(`${variableName}.makerAssetData`, swapQuote.makerAssetData);
|
sharedAssert.isHexString(`${variableName}.makerAssetData`, swapQuote.makerAssetData);
|
||||||
sharedAssert.doesConformToSchema(`${variableName}.orders`, swapQuote.orders, schemas.signedOrdersSchema);
|
sharedAssert.doesConformToSchema(`${variableName}.orders`, swapQuote.orders, schemas.signedOrdersSchema);
|
||||||
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, swapQuote.feeOrders, schemas.signedOrdersSchema);
|
assert.isValidSwapQuoteOrders(
|
||||||
assert.isValidOrdersForSwapQuote(
|
|
||||||
`${variableName}.orders`,
|
`${variableName}.orders`,
|
||||||
swapQuote.orders,
|
swapQuote.orders,
|
||||||
swapQuote.makerAssetData,
|
swapQuote.makerAssetData,
|
||||||
@ -27,7 +28,7 @@ export const assert = {
|
|||||||
sharedAssert.isBigNumber(`${variableName}.takerAssetFillAmount`, swapQuote.takerAssetFillAmount);
|
sharedAssert.isBigNumber(`${variableName}.takerAssetFillAmount`, swapQuote.takerAssetFillAmount);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValidOrdersForSwapQuote(
|
isValidSwapQuoteOrders(
|
||||||
variableName: string,
|
variableName: string,
|
||||||
orders: SignedOrder[],
|
orders: SignedOrder[],
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
@ -48,10 +49,21 @@ export const assert = {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isValidOrdersForSwapQuoter<T extends Order>(variableName: string, orders: T[]): void {
|
||||||
|
_.every(orders, (order: T, index: number) => {
|
||||||
|
assert.assert(
|
||||||
|
order.takerFee.isZero() ||
|
||||||
|
utils.isOrderTakerFeePayableWithTakerAsset(order) ||
|
||||||
|
utils.isOrderTakerFeePayableWithMakerAsset(order),
|
||||||
|
`Expected ${variableName}[${index}].takerFeeAssetData to be ${order.makerAssetData} or ${
|
||||||
|
order.takerAssetData
|
||||||
|
} but found ${order.takerFeeAssetData}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
isValidForwarderSwapQuote(variableName: string, swapQuote: SwapQuote, wethAssetData: string): void {
|
isValidForwarderSwapQuote(variableName: string, swapQuote: SwapQuote, wethAssetData: string): void {
|
||||||
assert.isValidSwapQuote(variableName, swapQuote);
|
assert.isValidSwapQuote(variableName, swapQuote);
|
||||||
assert.isValidForwarderSignedOrders(`${variableName}.orders`, swapQuote.orders, wethAssetData);
|
assert.isValidForwarderSignedOrders(`${variableName}.orders`, swapQuote.orders, wethAssetData);
|
||||||
assert.isValidForwarderSignedOrders(`${variableName}.feeOrders`, swapQuote.feeOrders, wethAssetData);
|
|
||||||
},
|
},
|
||||||
isValidForwarderSignedOrders(variableName: string, orders: SignedOrder[], wethAssetData: string): void {
|
isValidForwarderSignedOrders(variableName: string, orders: SignedOrder[], wethAssetData: string): void {
|
||||||
_.forEach(orders, (o: SignedOrder, i: number) => {
|
_.forEach(orders, (o: SignedOrder, i: number) => {
|
||||||
@ -65,10 +77,10 @@ export const assert = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void {
|
isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void {
|
||||||
sharedAssert.isBigNumber(`${variableName}.feeTakerTokenAmount`, swapQuoteInfo.feeTakerTokenAmount);
|
sharedAssert.isBigNumber(`${variableName}.feeTakerAssetAmount`, swapQuoteInfo.feeTakerAssetAmount);
|
||||||
sharedAssert.isBigNumber(`${variableName}.totalTakerTokenAmount`, swapQuoteInfo.totalTakerTokenAmount);
|
sharedAssert.isBigNumber(`${variableName}.totalTakerAssetAmount`, swapQuoteInfo.totalTakerAssetAmount);
|
||||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.takerTokenAmount);
|
sharedAssert.isBigNumber(`${variableName}.takerAssetAmount`, swapQuoteInfo.takerAssetAmount);
|
||||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.makerTokenAmount);
|
sharedAssert.isBigNumber(`${variableName}.makerAssetAmount`, swapQuoteInfo.makerAssetAmount);
|
||||||
},
|
},
|
||||||
isValidOrderbook(variableName: string, orderFetcher: Orderbook): void {
|
isValidOrderbook(variableName: string, orderFetcher: Orderbook): void {
|
||||||
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
|
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
|
||||||
|
@ -1,40 +1,27 @@
|
|||||||
import { orderCalculationUtils } from '@0x/order-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../types';
|
import { LiquidityForTakerMakerAssetDataPair, PrunedSignedOrder } from '../types';
|
||||||
|
|
||||||
export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAmounts): LiquidityForAssetData => {
|
import { utils } from './utils';
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
|
|
||||||
const liquidityInBigNumbers = orders.reduce(
|
|
||||||
(acc, order, curIndex) => {
|
|
||||||
const availableMakerAssetAmount = remainingFillableMakerAssetAmounts[curIndex];
|
|
||||||
if (availableMakerAssetAmount === undefined) {
|
|
||||||
throw new Error(`No corresponding fillableMakerAssetAmounts at index ${curIndex}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const makerTokensAvailableForCurrentOrder = availableMakerAssetAmount;
|
export const calculateLiquidity = (prunedOrders: PrunedSignedOrder[]): LiquidityForTakerMakerAssetDataPair => {
|
||||||
const takerTokensAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount(
|
const liquidityInBigNumbers = prunedOrders.reduce(
|
||||||
order,
|
(acc, order) => {
|
||||||
makerTokensAvailableForCurrentOrder,
|
const fillableMakerAssetAmount = utils.isOrderTakerFeePayableWithMakerAsset(order)
|
||||||
);
|
? order.fillableMakerAssetAmount.minus(order.fillableTakerFeeAmount)
|
||||||
|
: order.fillableMakerAssetAmount;
|
||||||
|
const fillableTakerAssetAmount = utils.isOrderTakerFeePayableWithTakerAsset(order)
|
||||||
|
? order.fillableTakerAssetAmount.plus(order.fillableTakerFeeAmount)
|
||||||
|
: order.fillableTakerAssetAmount;
|
||||||
return {
|
return {
|
||||||
makerTokensAvailableInBaseUnits: acc.makerTokensAvailableInBaseUnits.plus(
|
makerAssetAvailableInBaseUnits: acc.makerAssetAvailableInBaseUnits.plus(fillableMakerAssetAmount),
|
||||||
makerTokensAvailableForCurrentOrder,
|
takerAssetAvailableInBaseUnits: acc.takerAssetAvailableInBaseUnits.plus(fillableTakerAssetAmount),
|
||||||
),
|
|
||||||
takerTokensAvailableInBaseUnits: acc.takerTokensAvailableInBaseUnits.plus(
|
|
||||||
takerTokensAvailableForCurrentOrder,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
makerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
takerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return liquidityInBigNumbers;
|
||||||
// Turn into regular numbers
|
|
||||||
return {
|
|
||||||
makerTokensAvailableInBaseUnits: liquidityInBigNumbers.makerTokensAvailableInBaseUnits,
|
|
||||||
takerTokensAvailableInBaseUnits: liquidityInBigNumbers.takerTokensAvailableInBaseUnits,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
92
packages/asset-swapper/src/utils/market_utils.ts
Normal file
92
packages/asset-swapper/src/utils/market_utils.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { constants } from '../constants';
|
||||||
|
import { MarketOperation, PrunedSignedOrder } from '../types';
|
||||||
|
|
||||||
|
import { assert } from './assert';
|
||||||
|
import { utils } from './utils';
|
||||||
|
|
||||||
|
export const marketUtils = {
|
||||||
|
findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
sortedOrders: PrunedSignedOrder[],
|
||||||
|
takerAssetFillAmount: BigNumber,
|
||||||
|
slippageBufferAmount: BigNumber = new BigNumber(0),
|
||||||
|
): { resultOrders: PrunedSignedOrder[]; remainingFillAmount: BigNumber } {
|
||||||
|
return findOrdersThatCoverAssetFillAmount(
|
||||||
|
sortedOrders,
|
||||||
|
takerAssetFillAmount,
|
||||||
|
MarketOperation.Sell,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
sortedOrders: PrunedSignedOrder[],
|
||||||
|
makerAssetFillAmount: BigNumber,
|
||||||
|
slippageBufferAmount: BigNumber = new BigNumber(0),
|
||||||
|
): { resultOrders: PrunedSignedOrder[]; remainingFillAmount: BigNumber } {
|
||||||
|
return findOrdersThatCoverAssetFillAmount(
|
||||||
|
sortedOrders,
|
||||||
|
makerAssetFillAmount,
|
||||||
|
MarketOperation.Buy,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function findOrdersThatCoverAssetFillAmount(
|
||||||
|
sortedOrders: PrunedSignedOrder[],
|
||||||
|
assetFillAmount: BigNumber,
|
||||||
|
operation: MarketOperation,
|
||||||
|
slippageBufferAmount: BigNumber,
|
||||||
|
): { resultOrders: PrunedSignedOrder[]; remainingFillAmount: BigNumber } {
|
||||||
|
assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount);
|
||||||
|
// calculate total amount of asset needed to be filled
|
||||||
|
const totalFillAmount = assetFillAmount.plus(slippageBufferAmount);
|
||||||
|
// iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount
|
||||||
|
const result = _.reduce(
|
||||||
|
sortedOrders,
|
||||||
|
({ resultOrders, remainingFillAmount }, order) => {
|
||||||
|
if (remainingFillAmount.isLessThanOrEqualTo(constants.ZERO_AMOUNT)) {
|
||||||
|
return {
|
||||||
|
resultOrders,
|
||||||
|
remainingFillAmount: constants.ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const assetAmountAvailable = getAssetAmountAvailable(order, operation);
|
||||||
|
const shouldIncludeOrder = assetAmountAvailable.gt(constants.ZERO_AMOUNT);
|
||||||
|
// if there is no assetAmountAvailable do not append order to resultOrders
|
||||||
|
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
|
||||||
|
return {
|
||||||
|
resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders,
|
||||||
|
remainingFillAmount: BigNumber.max(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
remainingFillAmount.minus(assetAmountAvailable),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultOrders: [] as PrunedSignedOrder[],
|
||||||
|
remainingFillAmount: totalFillAmount,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAssetAmountAvailable(order: PrunedSignedOrder, operation: MarketOperation): BigNumber {
|
||||||
|
if (operation === MarketOperation.Buy) {
|
||||||
|
if (utils.isOrderTakerFeePayableWithMakerAsset(order)) {
|
||||||
|
return order.fillableMakerAssetAmount.minus(order.fillableTakerFeeAmount);
|
||||||
|
} else {
|
||||||
|
return order.fillableMakerAssetAmount;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (utils.isOrderTakerFeePayableWithTakerAsset(order)) {
|
||||||
|
return order.fillableTakerAssetAmount.plus(order.fillableTakerFeeAmount);
|
||||||
|
} else {
|
||||||
|
return order.fillableTakerAssetAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,182 +0,0 @@
|
|||||||
import { OrderStatus, OrderValidatorContract } from '@0x/contract-wrappers';
|
|
||||||
import { orderCalculationUtils, sortingUtils } from '@0x/order-utils';
|
|
||||||
import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator';
|
|
||||||
import { SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { constants } from '../constants';
|
|
||||||
import {
|
|
||||||
OrderProviderResponse,
|
|
||||||
OrdersAndFillableAmounts,
|
|
||||||
SignedOrderWithRemainingFillableMakerAssetAmount,
|
|
||||||
SwapQuoterError,
|
|
||||||
} from '../types';
|
|
||||||
|
|
||||||
export const orderProviderResponseProcessor = {
|
|
||||||
throwIfInvalidResponse(response: OrderProviderResponse, makerAssetData: string, takerAssetData: string): void {
|
|
||||||
_.forEach(response.orders, order => {
|
|
||||||
if (order.makerAssetData !== makerAssetData || order.takerAssetData !== takerAssetData) {
|
|
||||||
throw new Error(SwapQuoterError.InvalidOrderProviderResponse);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Take the responses for the target orders to buy and fee orders and process them.
|
|
||||||
* Processing includes:
|
|
||||||
* - Drop orders that are expired or not open orders (null taker address)
|
|
||||||
* - If an orderValidator is provided, attempt to grab fillable amounts from on-chain otherwise assume completely fillable
|
|
||||||
* - Sort by rate
|
|
||||||
*/
|
|
||||||
async processAsync(
|
|
||||||
orderProviderResponse: OrderProviderResponse,
|
|
||||||
isMakerAssetZrxToken: boolean,
|
|
||||||
expiryBufferMs: number,
|
|
||||||
orderValidator?: OrderValidatorContract,
|
|
||||||
): Promise<OrdersAndFillableAmounts> {
|
|
||||||
// drop orders that are expired or not open
|
|
||||||
const filteredOrders = filterOutExpiredAndNonOpenOrders(
|
|
||||||
orderProviderResponse.orders,
|
|
||||||
expiryBufferMs / constants.ONE_SECOND_MS,
|
|
||||||
);
|
|
||||||
// set the orders to be sorted equal to the filtered orders
|
|
||||||
let unsortedOrders = filteredOrders;
|
|
||||||
// if an orderValidator is provided, use on chain information to calculate remaining fillable makerAsset amounts
|
|
||||||
if (orderValidator !== undefined) {
|
|
||||||
const takerAddresses = _.map(filteredOrders, () => constants.NULL_ADDRESS);
|
|
||||||
try {
|
|
||||||
const [ordersInfo, tradersInfo] = await orderValidator
|
|
||||||
.getOrdersAndTradersInfo(filteredOrders, takerAddresses)
|
|
||||||
.callAsync();
|
|
||||||
const ordersAndTradersInfo: any[] = ordersInfo.map((orderInfo, index) => {
|
|
||||||
const singleOrderAndTraderInfo = {
|
|
||||||
orderInfo,
|
|
||||||
traderInfo: tradersInfo[index],
|
|
||||||
};
|
|
||||||
return singleOrderAndTraderInfo;
|
|
||||||
});
|
|
||||||
// take orders + on chain information and find the valid orders and remaining fillable maker asset amounts
|
|
||||||
unsortedOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
|
|
||||||
filteredOrders,
|
|
||||||
ordersAndTradersInfo,
|
|
||||||
isMakerAssetZrxToken,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
// Sometimes we observe this call to orderValidator fail with response `0x`
|
|
||||||
// Because of differences in Parity / Geth implementations, its very hard to tell if this response is a "system error"
|
|
||||||
// or a revert. In this case we just swallow these errors and fallback to partial fill information from the SRA.
|
|
||||||
// TODO(bmillman): report these errors so we have an idea of how often we're getting these failures.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sort orders by rate
|
|
||||||
// TODO(bmillman): optimization
|
|
||||||
// provide a feeRate to the sorting function to more accurately sort based on the current market for ZRX tokens
|
|
||||||
const sortedOrders = isMakerAssetZrxToken
|
|
||||||
? sortingUtils.sortFeeOrdersByFeeAdjustedRate(unsortedOrders)
|
|
||||||
: sortingUtils.sortOrdersByFeeAdjustedRate(unsortedOrders);
|
|
||||||
// unbundle orders and fillable amounts and compile final result
|
|
||||||
const result = unbundleOrdersWithAmounts(sortedOrders);
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of orders, return a new array with expired and non open orders filtered out.
|
|
||||||
*/
|
|
||||||
function filterOutExpiredAndNonOpenOrders(
|
|
||||||
orders: SignedOrderWithRemainingFillableMakerAssetAmount[],
|
|
||||||
expiryBufferMs: number,
|
|
||||||
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
|
|
||||||
const result = _.filter(orders, order => {
|
|
||||||
return (
|
|
||||||
orderCalculationUtils.isOpenOrder(order) &&
|
|
||||||
!orderCalculationUtils.willOrderExpire(order, expiryBufferMs / constants.ONE_SECOND_MS)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of orders and corresponding on-chain infos, return a subset of the orders
|
|
||||||
* that are still fillable orders with their corresponding remainingFillableMakerAssetAmounts.
|
|
||||||
*/
|
|
||||||
function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
|
|
||||||
inputOrders: SignedOrder[],
|
|
||||||
ordersAndTradersInfo: any[],
|
|
||||||
isMakerAssetZrxToken: boolean,
|
|
||||||
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
|
|
||||||
// iterate through the input orders and find the ones that are still fillable
|
|
||||||
// for the orders that are still fillable, calculate the remaining fillable maker asset amount
|
|
||||||
const result = _.reduce(
|
|
||||||
inputOrders,
|
|
||||||
(accOrders, order, index) => {
|
|
||||||
// get corresponding on-chain state for the order
|
|
||||||
const { orderInfo, traderInfo } = ordersAndTradersInfo[index];
|
|
||||||
// if the order IS NOT fillable, do not add anything to the accumulations and continue iterating
|
|
||||||
if (orderInfo.orderStatus !== OrderStatus.Fillable) {
|
|
||||||
return accOrders;
|
|
||||||
}
|
|
||||||
// if the order IS fillable, add the order and calculate the remaining fillable amount
|
|
||||||
const transferrableAssetAmount = BigNumber.min(traderInfo.makerAllowance, traderInfo.makerBalance);
|
|
||||||
const transferrableFeeAssetAmount = BigNumber.min(traderInfo.makerZrxAllowance, traderInfo.makerZrxBalance);
|
|
||||||
const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount);
|
|
||||||
const remainingMakerAssetAmount = orderCalculationUtils.getMakerFillAmount(
|
|
||||||
order,
|
|
||||||
remainingTakerAssetAmount,
|
|
||||||
);
|
|
||||||
const remainingFillableCalculator = new RemainingFillableCalculator(
|
|
||||||
order.makerFee,
|
|
||||||
order.makerAssetAmount,
|
|
||||||
isMakerAssetZrxToken,
|
|
||||||
transferrableAssetAmount,
|
|
||||||
transferrableFeeAssetAmount,
|
|
||||||
remainingMakerAssetAmount,
|
|
||||||
);
|
|
||||||
const remainingFillableAmount = remainingFillableCalculator.computeRemainingFillable();
|
|
||||||
// if the order does not have any remaining fillable makerAsset, do not add anything to the accumulations and continue iterating
|
|
||||||
if (remainingFillableAmount.lte(constants.ZERO_AMOUNT)) {
|
|
||||||
return accOrders;
|
|
||||||
}
|
|
||||||
const orderWithRemainingFillableMakerAssetAmount = {
|
|
||||||
...order,
|
|
||||||
remainingFillableMakerAssetAmount: remainingFillableAmount,
|
|
||||||
};
|
|
||||||
const newAccOrders = _.concat(accOrders, orderWithRemainingFillableMakerAssetAmount);
|
|
||||||
return newAccOrders;
|
|
||||||
},
|
|
||||||
[] as SignedOrderWithRemainingFillableMakerAssetAmount[],
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of orders with remaining fillable maker asset amounts. Unbundle into an instance of OrdersAndRemainingFillableMakerAssetAmounts.
|
|
||||||
* If an order is missing a corresponding remainingFillableMakerAssetAmount, assume it is completely fillable.
|
|
||||||
*/
|
|
||||||
function unbundleOrdersWithAmounts(
|
|
||||||
ordersWithAmounts: SignedOrderWithRemainingFillableMakerAssetAmount[],
|
|
||||||
): OrdersAndFillableAmounts {
|
|
||||||
const result = _.reduce(
|
|
||||||
ordersWithAmounts,
|
|
||||||
(acc, orderWithAmount) => {
|
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = acc;
|
|
||||||
const { remainingFillableMakerAssetAmount, ...order } = orderWithAmount;
|
|
||||||
// if we are still missing a remainingFillableMakerAssetAmount, assume the order is completely fillable
|
|
||||||
const newRemainingAmount = remainingFillableMakerAssetAmount || order.makerAssetAmount;
|
|
||||||
// if remaining amount is less than or equal to zero, do not add it
|
|
||||||
if (newRemainingAmount.lte(constants.ZERO_AMOUNT)) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
const newAcc = {
|
|
||||||
orders: _.concat(orders, order),
|
|
||||||
remainingFillableMakerAssetAmounts: _.concat(remainingFillableMakerAssetAmounts, newRemainingAmount),
|
|
||||||
};
|
|
||||||
return newAcc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
orders: [] as SignedOrder[],
|
|
||||||
remainingFillableMakerAssetAmounts: [] as BigNumber[],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
96
packages/asset-swapper/src/utils/order_prune_utils.ts
Normal file
96
packages/asset-swapper/src/utils/order_prune_utils.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { orderCalculationUtils } from '@0x/order-utils';
|
||||||
|
import { OrderStatus, SignedOrder } from '@0x/types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { constants } from '../constants';
|
||||||
|
import { OrderPrunerOnChainMetadata, OrderPrunerOpts, OrderPrunerPermittedFeeTypes, PrunedSignedOrder } from '../types';
|
||||||
|
import { utils } from '../utils/utils';
|
||||||
|
|
||||||
|
export class OrderPruner {
|
||||||
|
public readonly expiryBufferMs: number;
|
||||||
|
public readonly permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
|
||||||
|
private readonly _devUtils: DevUtilsContract;
|
||||||
|
|
||||||
|
// TODO(dave4506): OrderPruneCalculator can be more powerful if it takes in a specified takerAddress
|
||||||
|
constructor(devUtils: DevUtilsContract, opts: Partial<OrderPrunerOpts> = {}) {
|
||||||
|
const { expiryBufferMs, permittedOrderFeeTypes } = _.assign({}, constants.DEFAULT_ORDER_PRUNER_OPTS, opts);
|
||||||
|
|
||||||
|
this.expiryBufferMs = expiryBufferMs;
|
||||||
|
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
|
||||||
|
this._devUtils = devUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pruneSignedOrdersAsync(signedOrders: SignedOrder[]): Promise<PrunedSignedOrder[]> {
|
||||||
|
const unsortedOrders = this._filterForUsableOrders(signedOrders, this.expiryBufferMs);
|
||||||
|
|
||||||
|
const signatures = _.map(unsortedOrders, o => o.signature);
|
||||||
|
const [ordersInfo, fillableTakerAssetAmounts, isValidSignatures] = await this._devUtils
|
||||||
|
.getOrderRelevantStates(unsortedOrders, signatures)
|
||||||
|
.callAsync();
|
||||||
|
const ordersOnChainMetadata: OrderPrunerOnChainMetadata[] = ordersInfo.map((orderInfo, index) => {
|
||||||
|
return {
|
||||||
|
...orderInfo,
|
||||||
|
fillableTakerAssetAmount: fillableTakerAssetAmounts[index],
|
||||||
|
isValidSignature: isValidSignatures[index],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// take orders + on chain information and find the valid orders and fillable makerAsset or takerAsset amounts
|
||||||
|
const prunedOrders = this._filterForFillableAndPermittedFeeTypeOrders(unsortedOrders, ordersOnChainMetadata);
|
||||||
|
|
||||||
|
return prunedOrders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: prefer-function-over-method
|
||||||
|
private _filterForFillableAndPermittedFeeTypeOrders(
|
||||||
|
orders: SignedOrder[],
|
||||||
|
ordersOnChainMetadata: OrderPrunerOnChainMetadata[],
|
||||||
|
): PrunedSignedOrder[] {
|
||||||
|
const result = _.chain(orders)
|
||||||
|
.filter(
|
||||||
|
(order: SignedOrder, index: number): boolean => {
|
||||||
|
const { isValidSignature, orderStatus } = ordersOnChainMetadata[index];
|
||||||
|
return (
|
||||||
|
isValidSignature &&
|
||||||
|
orderStatus === OrderStatus.Fillable &&
|
||||||
|
((this.permittedOrderFeeTypes.has(OrderPrunerPermittedFeeTypes.NoFees) &&
|
||||||
|
order.takerFee.eq(constants.ZERO_AMOUNT)) ||
|
||||||
|
(this.permittedOrderFeeTypes.has(OrderPrunerPermittedFeeTypes.TakerDenominatedTakerFee) &&
|
||||||
|
utils.isOrderTakerFeePayableWithTakerAsset(order)) ||
|
||||||
|
(this.permittedOrderFeeTypes.has(OrderPrunerPermittedFeeTypes.MakerDenominatedTakerFee) &&
|
||||||
|
utils.isOrderTakerFeePayableWithMakerAsset(order)))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(order: SignedOrder, index: number): PrunedSignedOrder => {
|
||||||
|
const { fillableTakerAssetAmount } = ordersOnChainMetadata[index];
|
||||||
|
return {
|
||||||
|
...order,
|
||||||
|
fillableTakerAssetAmount,
|
||||||
|
fillableMakerAssetAmount: orderCalculationUtils.getMakerFillAmount(
|
||||||
|
order,
|
||||||
|
fillableTakerAssetAmount,
|
||||||
|
),
|
||||||
|
fillableTakerFeeAmount: orderCalculationUtils.getTakerFeeAmount(
|
||||||
|
order,
|
||||||
|
fillableTakerAssetAmount,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.value();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: prefer-function-over-method
|
||||||
|
private _filterForUsableOrders(orders: SignedOrder[], expiryBufferMs: number): SignedOrder[] {
|
||||||
|
const result = _.filter(orders, order => {
|
||||||
|
return (
|
||||||
|
orderCalculationUtils.isOpenOrder(order) &&
|
||||||
|
!orderCalculationUtils.willOrderExpire(order, expiryBufferMs / constants.ONE_SECOND_MS)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
31
packages/asset-swapper/src/utils/protocol_fee_utils.ts
Normal file
31
packages/asset-swapper/src/utils/protocol_fee_utils.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Order } from '@0x/types';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { constants } from '../constants';
|
||||||
|
import { SwapQuoterError } from '../types';
|
||||||
|
|
||||||
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
|
export const protocolFeeUtils = {
|
||||||
|
/**
|
||||||
|
* Gets 'fast' gas price from Eth Gas Station.
|
||||||
|
*/
|
||||||
|
async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${constants.ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`);
|
||||||
|
const gasInfo = await res.json();
|
||||||
|
// Eth Gas Station result is gwei * 10
|
||||||
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
|
return new BigNumber(gasInfo.fast / 10);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(SwapQuoterError.NoGasPriceProvidedOrEstimated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Calculates protocol fee with protofol fee multiplier for each fill.
|
||||||
|
*/
|
||||||
|
calculateWorstCaseProtocolFee<T extends Order>(orders: T[], gasPrice: BigNumber): BigNumber {
|
||||||
|
const protocolFee = new BigNumber(orders.length * constants.PROTOCOL_FEE_MULTIPLIER).times(gasPrice);
|
||||||
|
return protocolFee;
|
||||||
|
},
|
||||||
|
};
|
29
packages/asset-swapper/src/utils/sorting_utils.ts
Normal file
29
packages/asset-swapper/src/utils/sorting_utils.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { schemas } from '@0x/json-schemas';
|
||||||
|
import { Order } from '@0x/types';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { assert } from './assert';
|
||||||
|
import { utils } from './utils';
|
||||||
|
|
||||||
|
export const sortingUtils = {
|
||||||
|
sortOrders<T extends Order>(orders: T[]): T[] {
|
||||||
|
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
|
||||||
|
assert.isValidOrdersForSwapQuoter('orders', orders);
|
||||||
|
const copiedOrders = _.cloneDeep(orders);
|
||||||
|
copiedOrders.sort((firstOrder, secondOrder) => {
|
||||||
|
const firstOrderRate = getTakerFeeAdjustedRateOfOrder(firstOrder);
|
||||||
|
const secondOrderRate = getTakerFeeAdjustedRateOfOrder(secondOrder);
|
||||||
|
return firstOrderRate.comparedTo(secondOrderRate);
|
||||||
|
});
|
||||||
|
return copiedOrders;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getTakerFeeAdjustedRateOfOrder(order: Order): BigNumber {
|
||||||
|
const [adjustedMakerAssetAmount, adjustedTakerAssetAmount] = utils.getAdjustedMakerAndTakerAmountsFromTakerFees(
|
||||||
|
order,
|
||||||
|
);
|
||||||
|
const rate = adjustedTakerAssetAmount.div(adjustedMakerAssetAmount);
|
||||||
|
return rate;
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { marketUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
|
import { orderCalculationUtils } from '@0x/order-utils';
|
||||||
import { MarketOperation } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
@ -7,106 +6,75 @@ import { constants } from '../constants';
|
|||||||
import { InsufficientAssetLiquidityError } from '../errors';
|
import { InsufficientAssetLiquidityError } from '../errors';
|
||||||
import {
|
import {
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
|
MarketOperation,
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
OrdersAndFillableAmounts,
|
PrunedSignedOrder,
|
||||||
SwapQuote,
|
SwapQuote,
|
||||||
SwapQuoteInfo,
|
SwapQuoteInfo,
|
||||||
SwapQuoterError,
|
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
|
import { marketUtils } from './market_utils';
|
||||||
|
import { protocolFeeUtils } from './protocol_fee_utils';
|
||||||
|
import { utils } from './utils';
|
||||||
|
|
||||||
// Calculates a swap quote for orders
|
// Calculates a swap quote for orders
|
||||||
export const swapQuoteCalculator = {
|
export const swapQuoteCalculator = {
|
||||||
calculateMarketSellSwapQuote(
|
calculateMarketSellSwapQuote(
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
|
||||||
takerAssetFillAmount: BigNumber,
|
takerAssetFillAmount: BigNumber,
|
||||||
slippagePercentage: number,
|
slippagePercentage: number,
|
||||||
isMakerAssetZrxToken: boolean,
|
gasPrice: BigNumber,
|
||||||
shouldDisableFeeOrderCalculations: boolean,
|
|
||||||
): MarketSellSwapQuote {
|
): MarketSellSwapQuote {
|
||||||
return calculateSwapQuote(
|
return calculateSwapQuote(
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
feeOrdersAndFillableAmounts,
|
|
||||||
takerAssetFillAmount,
|
takerAssetFillAmount,
|
||||||
slippagePercentage,
|
slippagePercentage,
|
||||||
isMakerAssetZrxToken,
|
gasPrice,
|
||||||
shouldDisableFeeOrderCalculations,
|
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
) as MarketSellSwapQuote;
|
) as MarketSellSwapQuote;
|
||||||
},
|
},
|
||||||
calculateMarketBuySwapQuote(
|
calculateMarketBuySwapQuote(
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
takerAssetFillAmount: BigNumber,
|
||||||
makerAssetFillAmount: BigNumber,
|
|
||||||
slippagePercentage: number,
|
slippagePercentage: number,
|
||||||
isMakerAssetZrxToken: boolean,
|
gasPrice: BigNumber,
|
||||||
shouldDisableFeeOrderCalculations: boolean,
|
|
||||||
): MarketBuySwapQuote {
|
): MarketBuySwapQuote {
|
||||||
return calculateSwapQuote(
|
return calculateSwapQuote(
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
feeOrdersAndFillableAmounts,
|
takerAssetFillAmount,
|
||||||
makerAssetFillAmount,
|
|
||||||
slippagePercentage,
|
slippagePercentage,
|
||||||
isMakerAssetZrxToken,
|
gasPrice,
|
||||||
shouldDisableFeeOrderCalculations,
|
|
||||||
MarketOperation.Buy,
|
MarketOperation.Buy,
|
||||||
) as MarketBuySwapQuote;
|
) as MarketBuySwapQuote;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function calculateSwapQuote(
|
function calculateSwapQuote(
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
|
||||||
assetFillAmount: BigNumber,
|
assetFillAmount: BigNumber,
|
||||||
slippagePercentage: number,
|
slippagePercentage: number,
|
||||||
isMakerAssetZrxToken: boolean,
|
gasPrice: BigNumber,
|
||||||
shouldDisableFeeOrderCalculations: boolean,
|
|
||||||
marketOperation: MarketOperation,
|
marketOperation: MarketOperation,
|
||||||
): SwapQuote {
|
): SwapQuote {
|
||||||
const orders = ordersAndFillableAmounts.orders;
|
|
||||||
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
|
||||||
const remainingFillableTakerAssetAmounts = remainingFillableMakerAssetAmounts.map(
|
|
||||||
(makerAssetAmount: BigNumber, index: number) => {
|
|
||||||
return orderCalculationUtils.getTakerFillAmount(orders[index], makerAssetAmount);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const feeOrders = feeOrdersAndFillableAmounts.orders;
|
|
||||||
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
|
||||||
|
|
||||||
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
|
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
|
||||||
|
|
||||||
let resultOrders: SignedOrder[];
|
let resultOrders: PrunedSignedOrder[];
|
||||||
let remainingFillAmount: BigNumber;
|
let remainingFillAmount: BigNumber;
|
||||||
let ordersRemainingFillableMakerAssetAmounts: BigNumber[];
|
|
||||||
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
if (marketOperation === MarketOperation.Buy) {
|
||||||
// find the orders that cover the desired assetBuyAmount (with slippage)
|
// find the orders that cover the desired assetBuyAmount (with slippage)
|
||||||
({
|
({ resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
resultOrders,
|
prunedOrders,
|
||||||
remainingFillAmount,
|
assetFillAmount,
|
||||||
ordersRemainingFillableMakerAssetAmounts,
|
|
||||||
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetFillAmount, {
|
|
||||||
remainingFillableMakerAssetAmounts,
|
|
||||||
slippageBufferAmount,
|
slippageBufferAmount,
|
||||||
}));
|
));
|
||||||
} else {
|
} else {
|
||||||
let ordersRemainingFillableTakerAssetAmounts: BigNumber[];
|
|
||||||
// find the orders that cover the desired assetBuyAmount (with slippage)
|
// find the orders that cover the desired assetBuyAmount (with slippage)
|
||||||
({
|
({ resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
resultOrders,
|
prunedOrders,
|
||||||
remainingFillAmount,
|
assetFillAmount,
|
||||||
ordersRemainingFillableTakerAssetAmounts,
|
|
||||||
} = marketUtils.findOrdersThatCoverTakerAssetFillAmount(orders, assetFillAmount, {
|
|
||||||
remainingFillableTakerAssetAmounts,
|
|
||||||
slippageBufferAmount,
|
slippageBufferAmount,
|
||||||
}));
|
));
|
||||||
|
|
||||||
ordersRemainingFillableMakerAssetAmounts = _.map(
|
|
||||||
ordersRemainingFillableTakerAssetAmounts,
|
|
||||||
(takerAssetAmount: BigNumber, index: number) => {
|
|
||||||
return orderCalculationUtils.getMakerFillAmount(resultOrders[index], takerAssetAmount);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we do not have enough orders to cover the desired assetBuyAmount, throw
|
// if we do not have enough orders to cover the desired assetBuyAmount, throw
|
||||||
@ -126,60 +94,16 @@ function calculateSwapQuote(
|
|||||||
|
|
||||||
throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage);
|
throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage);
|
||||||
}
|
}
|
||||||
// if we are not buying ZRX:
|
|
||||||
// given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage)
|
|
||||||
// TODO(bmillman): optimization
|
|
||||||
// update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to
|
|
||||||
// finding order that cover all fees, this will help with estimating ETH and minimizing gas usage
|
|
||||||
let resultFeeOrders = [] as SignedOrder[];
|
|
||||||
let feeOrdersRemainingFillableMakerAssetAmounts = [] as BigNumber[];
|
|
||||||
if (!shouldDisableFeeOrderCalculations && !isMakerAssetZrxToken) {
|
|
||||||
const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
|
||||||
resultOrders,
|
|
||||||
feeOrders,
|
|
||||||
{
|
|
||||||
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
|
|
||||||
remainingFillableFeeAmounts,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// if we do not have enough feeOrders to cover the fees, throw
|
|
||||||
if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
|
|
||||||
throw new Error(SwapQuoterError.InsufficientZrxLiquidity);
|
|
||||||
}
|
|
||||||
resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders;
|
|
||||||
feeOrdersRemainingFillableMakerAssetAmounts =
|
|
||||||
feeOrdersAndRemainingFeeAmount.feeOrdersRemainingFillableMakerAssetAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// assetData information for the result
|
// assetData information for the result
|
||||||
const takerAssetData = orders[0].takerAssetData;
|
const takerAssetData = resultOrders[0].takerAssetData;
|
||||||
const makerAssetData = orders[0].makerAssetData;
|
const makerAssetData = resultOrders[0].makerAssetData;
|
||||||
|
|
||||||
// compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount
|
const bestCaseQuoteInfo = calculateQuoteInfo(resultOrders, assetFillAmount, gasPrice, marketOperation);
|
||||||
const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
|
|
||||||
orders: resultOrders,
|
|
||||||
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
|
|
||||||
};
|
|
||||||
const trimmedFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
|
|
||||||
orders: resultFeeOrders,
|
|
||||||
remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts,
|
|
||||||
};
|
|
||||||
|
|
||||||
const bestCaseQuoteInfo = calculateQuoteInfo(
|
|
||||||
trimmedOrdersAndFillableAmounts,
|
|
||||||
trimmedFeeOrdersAndFillableAmounts,
|
|
||||||
assetFillAmount,
|
|
||||||
isMakerAssetZrxToken,
|
|
||||||
shouldDisableFeeOrderCalculations,
|
|
||||||
marketOperation,
|
|
||||||
);
|
|
||||||
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
|
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
|
||||||
const worstCaseQuoteInfo = calculateQuoteInfo(
|
const worstCaseQuoteInfo = calculateQuoteInfo(
|
||||||
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
|
_.reverse(_.clone(resultOrders)),
|
||||||
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
|
|
||||||
assetFillAmount,
|
assetFillAmount,
|
||||||
isMakerAssetZrxToken,
|
gasPrice,
|
||||||
shouldDisableFeeOrderCalculations,
|
|
||||||
marketOperation,
|
marketOperation,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -187,7 +111,6 @@ function calculateSwapQuote(
|
|||||||
takerAssetData,
|
takerAssetData,
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
orders: resultOrders,
|
orders: resultOrders,
|
||||||
feeOrders: resultFeeOrders,
|
|
||||||
bestCaseQuoteInfo,
|
bestCaseQuoteInfo,
|
||||||
worstCaseQuoteInfo,
|
worstCaseQuoteInfo,
|
||||||
};
|
};
|
||||||
@ -208,199 +131,159 @@ function calculateSwapQuote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function calculateQuoteInfo(
|
function calculateQuoteInfo(
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
assetFillAmount: BigNumber,
|
||||||
tokenAmount: BigNumber,
|
gasPrice: BigNumber,
|
||||||
isMakerAssetZrxToken: boolean,
|
operation: MarketOperation,
|
||||||
shouldDisableFeeOrderCalculations: boolean,
|
|
||||||
marketOperation: MarketOperation,
|
|
||||||
): SwapQuoteInfo {
|
): SwapQuoteInfo {
|
||||||
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
|
if (operation === MarketOperation.Buy) {
|
||||||
let makerTokenAmount = marketOperation === MarketOperation.Buy ? tokenAmount : constants.ZERO_AMOUNT;
|
return calculateMarketBuyQuoteInfo(prunedOrders, assetFillAmount, gasPrice);
|
||||||
let takerTokenAmount = marketOperation === MarketOperation.Sell ? tokenAmount : constants.ZERO_AMOUNT;
|
|
||||||
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
|
|
||||||
|
|
||||||
if (isMakerAssetZrxToken) {
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
|
||||||
takerTokenAmount = findTakerTokenAmountNeededToBuyZrx(ordersAndFillableAmounts, makerTokenAmount);
|
|
||||||
} else {
|
|
||||||
makerTokenAmount = findZrxTokenAmountFromSellingTakerTokenAmount(
|
|
||||||
ordersAndFillableAmounts,
|
|
||||||
takerTokenAmount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const findTokenAndZrxAmount =
|
return calculateMarketSellQuoteInfo(prunedOrders, assetFillAmount, gasPrice);
|
||||||
marketOperation === MarketOperation.Buy
|
|
||||||
? findTakerTokenAndZrxAmountNeededToBuyAsset
|
|
||||||
: findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset;
|
|
||||||
// find eth and zrx amounts needed to buy
|
|
||||||
const tokenAndZrxAmountToBuyAsset = findTokenAndZrxAmount(
|
|
||||||
ordersAndFillableAmounts,
|
|
||||||
marketOperation === MarketOperation.Buy ? makerTokenAmount : takerTokenAmount,
|
|
||||||
);
|
|
||||||
if (marketOperation === MarketOperation.Buy) {
|
|
||||||
takerTokenAmount = tokenAndZrxAmountToBuyAsset[0];
|
|
||||||
} else {
|
|
||||||
makerTokenAmount = tokenAndZrxAmountToBuyAsset[0];
|
|
||||||
}
|
|
||||||
const zrxAmountToBuyAsset = tokenAndZrxAmountToBuyAsset[1];
|
|
||||||
// find eth amount needed to buy zrx
|
|
||||||
zrxTakerTokenAmount = shouldDisableFeeOrderCalculations
|
|
||||||
? constants.ZERO_AMOUNT
|
|
||||||
: findTakerTokenAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeTakerTokenAmount = zrxTakerTokenAmount;
|
|
||||||
|
|
||||||
// eth amount needed in total is the sum of the amount needed for the asset and the amount needed for fees
|
|
||||||
const totalTakerTokenAmount = takerTokenAmount.plus(feeTakerTokenAmount);
|
|
||||||
return {
|
|
||||||
makerTokenAmount,
|
|
||||||
takerTokenAmount,
|
|
||||||
feeTakerTokenAmount,
|
|
||||||
totalTakerTokenAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties
|
|
||||||
function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts {
|
|
||||||
const ordersCopy = _.clone(ordersAndFillableAmounts.orders);
|
|
||||||
const remainingFillableMakerAssetAmountsCopy = _.clone(ordersAndFillableAmounts.remainingFillableMakerAssetAmounts);
|
|
||||||
return {
|
|
||||||
orders: ordersCopy.reverse(),
|
|
||||||
remainingFillableMakerAssetAmounts: remainingFillableMakerAssetAmountsCopy.reverse(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function findZrxTokenAmountFromSellingTakerTokenAmount(
|
function calculateMarketSellQuoteInfo(
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
takerAssetSellAmount: BigNumber,
|
takerAssetSellAmount: BigNumber,
|
||||||
): BigNumber {
|
gasPrice: BigNumber,
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts;
|
): SwapQuoteInfo {
|
||||||
const result = _.reduce(
|
const result = _.reduce(
|
||||||
orders,
|
prunedOrders,
|
||||||
(acc, order, index) => {
|
(acc, order) => {
|
||||||
const { totalZrxTokenAmount, remainingTakerAssetFillAmount } = acc;
|
const {
|
||||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
totalMakerAssetAmount,
|
||||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(
|
totalTakerAssetAmount,
|
||||||
order,
|
totalFeeTakerAssetAmount,
|
||||||
remainingFillableMakerAssetAmount,
|
remainingTakerAssetFillAmount,
|
||||||
|
} = acc;
|
||||||
|
const [
|
||||||
|
adjustedFillableMakerAssetAmount,
|
||||||
|
adjustedFillableTakerAssetAmount,
|
||||||
|
] = utils.getAdjustedFillableMakerAndTakerAmountsFromTakerFees(order);
|
||||||
|
const takerAssetAmountWithFees = BigNumber.min(
|
||||||
|
remainingTakerAssetFillAmount,
|
||||||
|
adjustedFillableTakerAssetAmount,
|
||||||
);
|
);
|
||||||
const takerFillAmount = BigNumber.min(remainingTakerAssetFillAmount, remainingFillableTakerAssetAmount);
|
const { takerAssetAmount, feeTakerAssetAmount } = getTakerAssetAmountBreakDown(
|
||||||
const makerFillAmount = orderCalculationUtils.getMakerFillAmount(order, takerFillAmount);
|
order,
|
||||||
const feeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
takerAssetAmountWithFees,
|
||||||
|
);
|
||||||
|
const makerAssetAmount = takerAssetAmountWithFees
|
||||||
|
.div(adjustedFillableTakerAssetAmount)
|
||||||
|
.multipliedBy(adjustedFillableMakerAssetAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_CEIL);
|
||||||
return {
|
return {
|
||||||
totalZrxTokenAmount: totalZrxTokenAmount.plus(makerFillAmount).minus(feeAmount),
|
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerAssetAmount),
|
||||||
|
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount),
|
||||||
|
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount),
|
||||||
remainingTakerAssetFillAmount: BigNumber.max(
|
remainingTakerAssetFillAmount: BigNumber.max(
|
||||||
constants.ZERO_AMOUNT,
|
constants.ZERO_AMOUNT,
|
||||||
remainingTakerAssetFillAmount.minus(takerFillAmount),
|
remainingTakerAssetFillAmount.minus(takerAssetAmountWithFees),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
totalZrxTokenAmount: constants.ZERO_AMOUNT,
|
totalMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
totalTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
remainingTakerAssetFillAmount: takerAssetSellAmount,
|
remainingTakerAssetFillAmount: takerAssetSellAmount,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return result.totalZrxTokenAmount;
|
return {
|
||||||
|
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
|
||||||
|
takerAssetAmount: result.totalTakerAssetAmount,
|
||||||
|
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
|
||||||
|
makerAssetAmount: result.totalMakerAssetAmount,
|
||||||
|
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(prunedOrders, gasPrice),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTakerTokenAmountNeededToBuyZrx(
|
function calculateMarketBuyQuoteInfo(
|
||||||
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
zrxBuyAmount: BigNumber,
|
|
||||||
): BigNumber {
|
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts;
|
|
||||||
const result = _.reduce(
|
|
||||||
orders,
|
|
||||||
(acc, order, index) => {
|
|
||||||
const { totalTakerTokenAmount, remainingZrxBuyAmount } = acc;
|
|
||||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
|
||||||
const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount);
|
|
||||||
const [takerFillAmount, adjustedMakerFillAmount] = orderCalculationUtils.getTakerFillAmountForFeeOrder(
|
|
||||||
order,
|
|
||||||
makerFillAmount,
|
|
||||||
);
|
|
||||||
const extraFeeAmount = remainingFillableMakerAssetAmount.isGreaterThanOrEqualTo(adjustedMakerFillAmount)
|
|
||||||
? constants.ZERO_AMOUNT
|
|
||||||
: adjustedMakerFillAmount.minus(makerFillAmount);
|
|
||||||
return {
|
|
||||||
totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount),
|
|
||||||
remainingZrxBuyAmount: BigNumber.max(
|
|
||||||
constants.ZERO_AMOUNT,
|
|
||||||
remainingZrxBuyAmount.minus(makerFillAmount).plus(extraFeeAmount),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
totalTakerTokenAmount: constants.ZERO_AMOUNT,
|
|
||||||
remainingZrxBuyAmount: zrxBuyAmount,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return result.totalTakerTokenAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findTakerTokenAndZrxAmountNeededToBuyAsset(
|
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
|
||||||
makerAssetBuyAmount: BigNumber,
|
makerAssetBuyAmount: BigNumber,
|
||||||
): [BigNumber, BigNumber] {
|
gasPrice: BigNumber,
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
|
): SwapQuoteInfo {
|
||||||
const result = _.reduce(
|
const result = _.reduce(
|
||||||
orders,
|
prunedOrders,
|
||||||
(acc, order, index) => {
|
(acc, order) => {
|
||||||
const { totalTakerTokenAmount, totalZrxAmount, remainingmakerAssetFillAmount } = acc;
|
const {
|
||||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
totalMakerAssetAmount,
|
||||||
const makerFillAmount = BigNumber.min(acc.remainingmakerAssetFillAmount, remainingFillableMakerAssetAmount);
|
totalTakerAssetAmount,
|
||||||
const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount);
|
totalFeeTakerAssetAmount,
|
||||||
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
remainingMakerAssetFillAmount,
|
||||||
|
} = acc;
|
||||||
|
const [
|
||||||
|
adjustedFillableMakerAssetAmount,
|
||||||
|
adjustedFillableTakerAssetAmount,
|
||||||
|
] = utils.getAdjustedFillableMakerAndTakerAmountsFromTakerFees(order);
|
||||||
|
const makerFillAmount = BigNumber.min(remainingMakerAssetFillAmount, adjustedFillableMakerAssetAmount);
|
||||||
|
const takerAssetAmountWithFees = makerFillAmount
|
||||||
|
.div(adjustedFillableMakerAssetAmount)
|
||||||
|
.multipliedBy(adjustedFillableTakerAssetAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_CEIL);
|
||||||
|
const { takerAssetAmount, feeTakerAssetAmount } = getTakerAssetAmountBreakDown(
|
||||||
|
order,
|
||||||
|
takerAssetAmountWithFees,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
totalTakerTokenAmount: totalTakerTokenAmount.plus(takerFillAmount),
|
totalMakerAssetAmount: totalMakerAssetAmount.plus(makerFillAmount),
|
||||||
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),
|
totalTakerAssetAmount: totalTakerAssetAmount.plus(takerAssetAmount),
|
||||||
remainingmakerAssetFillAmount: BigNumber.max(
|
totalFeeTakerAssetAmount: totalFeeTakerAssetAmount.plus(feeTakerAssetAmount),
|
||||||
|
remainingMakerAssetFillAmount: BigNumber.max(
|
||||||
constants.ZERO_AMOUNT,
|
constants.ZERO_AMOUNT,
|
||||||
remainingmakerAssetFillAmount.minus(makerFillAmount),
|
remainingMakerAssetFillAmount.minus(makerFillAmount),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
totalTakerTokenAmount: constants.ZERO_AMOUNT,
|
totalMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
totalZrxAmount: constants.ZERO_AMOUNT,
|
totalTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
remainingmakerAssetFillAmount: makerAssetBuyAmount,
|
totalFeeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
remainingMakerAssetFillAmount: makerAssetBuyAmount,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return [result.totalTakerTokenAmount, result.totalZrxAmount];
|
return {
|
||||||
|
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
|
||||||
|
takerAssetAmount: result.totalTakerAssetAmount,
|
||||||
|
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
|
||||||
|
makerAssetAmount: result.totalMakerAssetAmount,
|
||||||
|
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(prunedOrders, gasPrice),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset(
|
function getTakerAssetAmountBreakDown(
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
order: PrunedSignedOrder,
|
||||||
takerAssetSellAmount: BigNumber,
|
takerAssetAmountWithFees: BigNumber,
|
||||||
): [BigNumber, BigNumber] {
|
): { feeTakerAssetAmount: BigNumber; takerAssetAmount: BigNumber } {
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
|
if (utils.isOrderTakerFeePayableWithTakerAsset(order)) {
|
||||||
const result = _.reduce(
|
const adjustedTakerAssetAmount = order.takerAssetAmount.plus(order.takerFee);
|
||||||
orders,
|
const filledRatio = takerAssetAmountWithFees.div(adjustedTakerAssetAmount);
|
||||||
(acc, order, index) => {
|
const takerAssetAmount = filledRatio.multipliedBy(order.takerAssetAmount).integerValue(BigNumber.ROUND_CEIL);
|
||||||
const { totalMakerTokenAmount, totalZrxAmount, remainingTakerAssetFillAmount } = acc;
|
return {
|
||||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
takerAssetAmount,
|
||||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(
|
feeTakerAssetAmount: takerAssetAmountWithFees.minus(takerAssetAmount),
|
||||||
order,
|
};
|
||||||
remainingFillableMakerAssetAmount,
|
} else if (utils.isOrderTakerFeePayableWithMakerAsset(order)) {
|
||||||
);
|
if (takerAssetAmountWithFees.isZero()) {
|
||||||
const takerFillAmount = BigNumber.min(acc.remainingTakerAssetFillAmount, remainingFillableTakerAssetAmount);
|
|
||||||
const makerFillAmount = orderCalculationUtils.getMakerFillAmount(order, takerFillAmount);
|
|
||||||
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
|
||||||
return {
|
return {
|
||||||
totalMakerTokenAmount: totalMakerTokenAmount.plus(makerFillAmount),
|
takerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),
|
feeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
remainingTakerAssetFillAmount: BigNumber.max(
|
|
||||||
constants.ZERO_AMOUNT,
|
|
||||||
remainingTakerAssetFillAmount.minus(takerFillAmount),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
{
|
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerAssetAmountWithFees);
|
||||||
totalMakerTokenAmount: constants.ZERO_AMOUNT,
|
const makerAssetFillAmount = orderCalculationUtils.getMakerFillAmount(order, takerAssetAmountWithFees);
|
||||||
totalZrxAmount: constants.ZERO_AMOUNT,
|
const takerAssetAmount = takerFeeAmount
|
||||||
remainingTakerAssetFillAmount: takerAssetSellAmount,
|
.div(makerAssetFillAmount)
|
||||||
},
|
.multipliedBy(takerAssetAmountWithFees)
|
||||||
);
|
.integerValue(BigNumber.ROUND_CEIL);
|
||||||
return [result.totalMakerTokenAmount, result.totalZrxAmount];
|
return {
|
||||||
|
takerAssetAmount,
|
||||||
|
feeTakerAssetAmount: takerAssetAmountWithFees.minus(takerAssetAmount),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
feeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
takerAssetAmount: takerAssetAmountWithFees,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { ContractWrappers } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { WETH9Contract } from '@0x/contracts-erc20';
|
||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
|
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import { Provider } from 'ethereum-types';
|
import { Provider } from 'ethereum-types';
|
||||||
@ -47,19 +49,17 @@ export const swapQuoteConsumerUtils = {
|
|||||||
},
|
},
|
||||||
async getEthAndWethBalanceAsync(
|
async getEthAndWethBalanceAsync(
|
||||||
provider: SupportedProvider,
|
provider: SupportedProvider,
|
||||||
contractWrappers: ContractWrappers,
|
contractAddresses: ContractAddresses,
|
||||||
takerAddress: string,
|
takerAddress: string,
|
||||||
): Promise<[BigNumber, BigNumber]> {
|
): Promise<[BigNumber, BigNumber]> {
|
||||||
|
const weth = new WETH9Contract(contractAddresses.etherToken, provider);
|
||||||
const web3Wrapper = new Web3Wrapper(provider);
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
const ethBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
const ethBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
|
||||||
const wethBalance = await contractWrappers.weth9.balanceOf(takerAddress).callAsync();
|
const wethBalance = await weth.balanceOf(takerAddress).callAsync();
|
||||||
return [ethBalance, wethBalance];
|
return [ethBalance, wethBalance];
|
||||||
},
|
},
|
||||||
isValidForwarderSwapQuote(swapQuote: SwapQuote, wethAssetData: string): boolean {
|
isValidForwarderSwapQuote(swapQuote: SwapQuote, wethAssetData: string): boolean {
|
||||||
return (
|
return swapQuoteConsumerUtils.isValidForwarderSignedOrders(swapQuote.orders, wethAssetData);
|
||||||
swapQuoteConsumerUtils.isValidForwarderSignedOrders(swapQuote.orders, wethAssetData) &&
|
|
||||||
swapQuoteConsumerUtils.isValidForwarderSignedOrders(swapQuote.feeOrders, wethAssetData)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
isValidForwarderSignedOrders(orders: SignedOrder[], wethAssetData: string): boolean {
|
isValidForwarderSignedOrders(orders: SignedOrder[], wethAssetData: string): boolean {
|
||||||
return _.every(orders, order => swapQuoteConsumerUtils.isValidForwarderSignedOrder(order, wethAssetData));
|
return _.every(orders, order => swapQuoteConsumerUtils.isValidForwarderSignedOrder(order, wethAssetData));
|
||||||
@ -67,35 +67,25 @@ export const swapQuoteConsumerUtils = {
|
|||||||
isValidForwarderSignedOrder(order: SignedOrder, wethAssetData: string): boolean {
|
isValidForwarderSignedOrder(order: SignedOrder, wethAssetData: string): boolean {
|
||||||
return order.takerAssetData === wethAssetData;
|
return order.takerAssetData === wethAssetData;
|
||||||
},
|
},
|
||||||
optimizeOrdersForMarketExchangeOperation(orders: SignedOrder[], operation: MarketOperation): SignedOrder[] {
|
|
||||||
return _.map(orders, (order: SignedOrder, index: number) => {
|
|
||||||
const optimizedOrder = _.clone(order);
|
|
||||||
if (operation === MarketOperation.Sell && index !== 0) {
|
|
||||||
optimizedOrder.takerAssetData = constants.NULL_BYTES;
|
|
||||||
} else if (index !== 0) {
|
|
||||||
optimizedOrder.makerAssetData = constants.NULL_BYTES;
|
|
||||||
}
|
|
||||||
return optimizedOrder;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async getExtensionContractTypeForSwapQuoteAsync(
|
async getExtensionContractTypeForSwapQuoteAsync(
|
||||||
quote: SwapQuote,
|
quote: SwapQuote,
|
||||||
contractWrappers: ContractWrappers,
|
contractAddresses: ContractAddresses,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
opts: Partial<GetExtensionContractTypeOpts>,
|
opts: Partial<GetExtensionContractTypeOpts>,
|
||||||
): Promise<ExtensionContractType> {
|
): Promise<ExtensionContractType> {
|
||||||
const wethAssetData = await contractWrappers.devUtils
|
const devUtils = new DevUtilsContract(contractAddresses.devUtils, provider);
|
||||||
.encodeERC20AssetData(contractWrappers.contractAddresses.etherToken)
|
const wethAssetData = await devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync();
|
||||||
.callAsync();
|
|
||||||
if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) {
|
if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) {
|
||||||
if (opts.takerAddress !== undefined) {
|
if (opts.takerAddress !== undefined) {
|
||||||
assert.isETHAddressHex('takerAddress', opts.takerAddress);
|
assert.isETHAddressHex('takerAddress', opts.takerAddress);
|
||||||
}
|
}
|
||||||
const ethAmount = opts.ethAmount || quote.worstCaseQuoteInfo.totalTakerTokenAmount;
|
const ethAmount =
|
||||||
|
opts.ethAmount ||
|
||||||
|
quote.worstCaseQuoteInfo.takerAssetAmount.plus(quote.worstCaseQuoteInfo.protocolFeeInEthAmount);
|
||||||
const takerAddress = await swapQuoteConsumerUtils.getTakerAddressAsync(provider, opts);
|
const takerAddress = await swapQuoteConsumerUtils.getTakerAddressAsync(provider, opts);
|
||||||
const takerEthAndWethBalance =
|
const takerEthAndWethBalance =
|
||||||
takerAddress !== undefined
|
takerAddress !== undefined
|
||||||
? await swapQuoteConsumerUtils.getEthAndWethBalanceAsync(provider, contractWrappers, takerAddress)
|
? await swapQuoteConsumerUtils.getEthAndWethBalanceAsync(provider, contractAddresses, takerAddress)
|
||||||
: [constants.ZERO_AMOUNT, constants.ZERO_AMOUNT];
|
: [constants.ZERO_AMOUNT, constants.ZERO_AMOUNT];
|
||||||
// TODO(david): when considering if there is enough Eth balance, should account for gas costs.
|
// TODO(david): when considering if there is enough Eth balance, should account for gas costs.
|
||||||
const isEnoughEthAndWethBalance = _.map(takerEthAndWethBalance, (balance: BigNumber) =>
|
const isEnoughEthAndWethBalance = _.map(takerEthAndWethBalance, (balance: BigNumber) =>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { SignedOrder } from '@0x/types';
|
import { Order } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types';
|
import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants } from '../constants';
|
import { constants } from '../constants';
|
||||||
import { OrdersAndFillableAmounts } from '../types';
|
import { PrunedSignedOrder } from '../types';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
export const utils = {
|
export const utils = {
|
||||||
@ -27,15 +27,30 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
) as MethodAbi | undefined;
|
) as MethodAbi | undefined;
|
||||||
},
|
},
|
||||||
isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts: OrdersAndFillableAmounts): boolean {
|
isOrderTakerFeePayableWithMakerAsset<T extends Order>(order: T): boolean {
|
||||||
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
|
return order.takerFeeAssetData === order.makerAssetData;
|
||||||
return _.some(
|
},
|
||||||
orders,
|
isOrderTakerFeePayableWithTakerAsset<T extends Order>(order: T): boolean {
|
||||||
(order: SignedOrder, index: number): boolean => {
|
return order.takerFeeAssetData === order.takerAssetData;
|
||||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
},
|
||||||
// If takerFee is a non zero value and order is still fillable, fee orders are required
|
getAdjustedMakerAndTakerAmountsFromTakerFees<T extends Order>(order: T): [BigNumber, BigNumber] {
|
||||||
return !order.takerFee.isZero() && !remainingFillableMakerAssetAmount.isZero();
|
const adjustedMakerAssetAmount = utils.isOrderTakerFeePayableWithMakerAsset(order)
|
||||||
},
|
? order.makerAssetAmount.minus(order.takerFee)
|
||||||
);
|
: order.makerAssetAmount;
|
||||||
|
const adjustedTakerAssetAmount = utils.isOrderTakerFeePayableWithTakerAsset(order)
|
||||||
|
? order.takerAssetAmount.plus(order.takerFee)
|
||||||
|
: order.takerAssetAmount;
|
||||||
|
return [adjustedMakerAssetAmount, adjustedTakerAssetAmount];
|
||||||
|
},
|
||||||
|
getAdjustedFillableMakerAndTakerAmountsFromTakerFees<T extends PrunedSignedOrder>(
|
||||||
|
order: T,
|
||||||
|
): [BigNumber, BigNumber] {
|
||||||
|
const adjustedFillableMakerAssetAmount = utils.isOrderTakerFeePayableWithMakerAsset(order)
|
||||||
|
? order.fillableMakerAssetAmount.minus(order.fillableTakerFeeAmount)
|
||||||
|
: order.fillableMakerAssetAmount;
|
||||||
|
const adjustedFillableTakerAssetAmount = utils.isOrderTakerFeePayableWithTakerAsset(order)
|
||||||
|
? order.fillableTakerAssetAmount.plus(order.fillableTakerFeeAmount)
|
||||||
|
: order.fillableTakerAssetAmount;
|
||||||
|
return [adjustedFillableMakerAssetAmount, adjustedFillableTakerAssetAmount];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
import { MarketOperation } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as chai from 'chai';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { constants } from '../src/constants';
|
|
||||||
import { affiliateFeeUtils } from '../src/utils/affiliate_fee_utils';
|
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
|
||||||
import {
|
|
||||||
getFullyFillableSwapQuoteWithFees,
|
|
||||||
getFullyFillableSwapQuoteWithNoFees,
|
|
||||||
getPartialSignedOrdersWithFees,
|
|
||||||
getPartialSignedOrdersWithNoFees,
|
|
||||||
} from './utils/swap_quote';
|
|
||||||
|
|
||||||
chaiSetup.configure();
|
|
||||||
const expect = chai.expect;
|
|
||||||
|
|
||||||
const FAKE_TAKER_ASSET_DATA = '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48';
|
|
||||||
const FAKE_MAKER_ASSET_DATA = '0xf47261b00000000000000000000000009f5B0C7e1623793bF0620569b9749e79DF6D0bC5';
|
|
||||||
const NULL_ADDRESS = constants.NULL_ADDRESS;
|
|
||||||
const FEE_PERCENTAGE = 0.1;
|
|
||||||
const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)];
|
|
||||||
const FILLABLE_FEE_AMOUNTS = [new BigNumber(1), new BigNumber(1), new BigNumber(1)];
|
|
||||||
const MARKET_OPERATION = MarketOperation.Sell;
|
|
||||||
|
|
||||||
describe('affiliateFeeUtils', () => {
|
|
||||||
const fakeFeeOrders = getPartialSignedOrdersWithNoFees(
|
|
||||||
FAKE_MAKER_ASSET_DATA,
|
|
||||||
FAKE_TAKER_ASSET_DATA,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
FILLABLE_FEE_AMOUNTS,
|
|
||||||
);
|
|
||||||
const fakeOrders = getPartialSignedOrdersWithNoFees(
|
|
||||||
FAKE_MAKER_ASSET_DATA,
|
|
||||||
FAKE_TAKER_ASSET_DATA,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fakeOrdersWithFees = getPartialSignedOrdersWithFees(
|
|
||||||
FAKE_MAKER_ASSET_DATA,
|
|
||||||
FAKE_TAKER_ASSET_DATA,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
NULL_ADDRESS,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
FILLABLE_FEE_AMOUNTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fakeSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
||||||
FAKE_MAKER_ASSET_DATA,
|
|
||||||
FAKE_TAKER_ASSET_DATA,
|
|
||||||
fakeOrders,
|
|
||||||
MARKET_OPERATION,
|
|
||||||
);
|
|
||||||
|
|
||||||
const fakeSwapQuoteWithFees = getFullyFillableSwapQuoteWithFees(
|
|
||||||
FAKE_MAKER_ASSET_DATA,
|
|
||||||
FAKE_TAKER_ASSET_DATA,
|
|
||||||
fakeOrdersWithFees,
|
|
||||||
fakeFeeOrders,
|
|
||||||
MARKET_OPERATION,
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('getSwapQuoteWithAffiliateFee', () => {
|
|
||||||
it('should return unchanged swapQuote if feePercentage is 0', () => {
|
|
||||||
const updatedSwapQuote = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(fakeSwapQuote, 0);
|
|
||||||
const fakeSwapQuoteWithAffiliateFees = { ...fakeSwapQuote, ...{ feePercentage: 0 } };
|
|
||||||
expect(updatedSwapQuote).to.deep.equal(fakeSwapQuoteWithAffiliateFees);
|
|
||||||
});
|
|
||||||
it('should return correct feeTakerToken and totalTakerToken amounts when provided SwapQuote with no fees', () => {
|
|
||||||
const updatedSwapQuote = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(fakeSwapQuote, FEE_PERCENTAGE);
|
|
||||||
expect(updatedSwapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.deep.equal(new BigNumber(1));
|
|
||||||
expect(updatedSwapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.deep.equal(new BigNumber(11));
|
|
||||||
expect(updatedSwapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.deep.equal(new BigNumber(1));
|
|
||||||
expect(updatedSwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.deep.equal(new BigNumber(11));
|
|
||||||
});
|
|
||||||
it('should return correct feeTakerToken and totalTakerToken amounts when provides SwapQuote with fees', () => {
|
|
||||||
const updatedSwapQuote = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(
|
|
||||||
fakeSwapQuoteWithFees,
|
|
||||||
FEE_PERCENTAGE,
|
|
||||||
);
|
|
||||||
expect(updatedSwapQuote.bestCaseQuoteInfo.feeTakerTokenAmount).to.deep.equal(new BigNumber(4));
|
|
||||||
expect(updatedSwapQuote.bestCaseQuoteInfo.totalTakerTokenAmount).to.deep.equal(new BigNumber(14));
|
|
||||||
expect(updatedSwapQuote.worstCaseQuoteInfo.feeTakerTokenAmount).to.deep.equal(new BigNumber(4));
|
|
||||||
expect(updatedSwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount).to.deep.equal(new BigNumber(14));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
57
packages/asset-swapper/test/calculate_liquidity_test.ts
Normal file
57
packages/asset-swapper/test/calculate_liquidity_test.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as chai from 'chai';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { calculateLiquidity } from '../src/utils/calculate_liquidity';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { testOrders } from './utils/test_orders';
|
||||||
|
import { baseUnitAmount } from './utils/utils';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
const {
|
||||||
|
PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
|
||||||
|
} = testOrders;
|
||||||
|
|
||||||
|
// tslint:disable:custom-no-magic-numbers
|
||||||
|
describe('#calculateLiquidity', () => {
|
||||||
|
it('should provide correct liquidity result with feeless orders', () => {
|
||||||
|
const prunedSignedOrders = PRUNED_SIGNED_ORDERS_FEELESS;
|
||||||
|
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||||
|
prunedSignedOrders,
|
||||||
|
);
|
||||||
|
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10));
|
||||||
|
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(9));
|
||||||
|
});
|
||||||
|
it('should provide correct liquidity result with orders with takerFees in takerAsset', () => {
|
||||||
|
const prunedSignedOrders = PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET;
|
||||||
|
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||||
|
prunedSignedOrders,
|
||||||
|
);
|
||||||
|
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(10));
|
||||||
|
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(15));
|
||||||
|
});
|
||||||
|
it('should provide correct liquidity result with orders with takerFees in makerAsset', () => {
|
||||||
|
const prunedSignedOrders = PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET;
|
||||||
|
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||||
|
prunedSignedOrders,
|
||||||
|
);
|
||||||
|
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(5));
|
||||||
|
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(9));
|
||||||
|
});
|
||||||
|
it('should provide correct liquidity result with mixed orders with fees and no fees', () => {
|
||||||
|
const prunedSignedOrders = _.concat(
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
);
|
||||||
|
const { makerAssetAvailableInBaseUnits, takerAssetAvailableInBaseUnits } = calculateLiquidity(
|
||||||
|
prunedSignedOrders,
|
||||||
|
);
|
||||||
|
expect(makerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(25));
|
||||||
|
expect(takerAssetAvailableInBaseUnits).to.bignumber.eq(baseUnitAmount(33));
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,9 @@
|
|||||||
import { ContractAddresses, ContractWrappers, ERC20TokenContract } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||||
|
import { ExchangeContract } from '@0x/contracts-exchange';
|
||||||
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
|
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
|
||||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
@ -13,7 +15,9 @@ import {
|
|||||||
ExchangeMarketBuySmartContractParams,
|
ExchangeMarketBuySmartContractParams,
|
||||||
ExchangeMarketSellSmartContractParams,
|
ExchangeMarketSellSmartContractParams,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
|
MarketOperation,
|
||||||
MarketSellSwapQuote,
|
MarketSellSwapQuote,
|
||||||
|
PrunedSignedOrder,
|
||||||
} from '../src/types';
|
} from '../src/types';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
@ -25,16 +29,47 @@ chaiSetup.configure();
|
|||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
|
||||||
|
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
||||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
||||||
const TESTRPC_CHAIN_ID = 1337;
|
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
|
||||||
const FILLABLE_AMOUNTS = [new BigNumber(3), new BigNumber(2), new BigNumber(5)].map(value =>
|
const UNLIMITED_ALLOWANCE = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
||||||
value.multipliedBy(ONE_ETH_IN_WEI),
|
|
||||||
);
|
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectMakerAndTakerBalancesAsyncFactory = (
|
||||||
|
erc20TokenContract: ERC20TokenContract,
|
||||||
|
makerAddress: string,
|
||||||
|
takerAddress: string,
|
||||||
|
) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => {
|
||||||
|
const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
||||||
|
const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
||||||
|
expect(makerBalance).to.bignumber.equal(expectedMakerBalance);
|
||||||
|
expect(takerBalance).to.bignumber.equal(expectedTakerBalance);
|
||||||
|
};
|
||||||
|
|
||||||
describe('ExchangeSwapQuoteConsumer', () => {
|
describe('ExchangeSwapQuoteConsumer', () => {
|
||||||
let contractWrappers: ContractWrappers;
|
|
||||||
let userAddresses: string[];
|
let userAddresses: string[];
|
||||||
let erc20TokenContract: ERC20TokenContract;
|
let erc20MakerTokenContract: ERC20TokenContract;
|
||||||
|
let erc20TakerTokenContract: ERC20TokenContract;
|
||||||
let coinbaseAddress: string;
|
let coinbaseAddress: string;
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
@ -46,32 +81,38 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
let takerAssetData: string;
|
let takerAssetData: string;
|
||||||
let wethAssetData: string;
|
let wethAssetData: string;
|
||||||
let contractAddresses: ContractAddresses;
|
let contractAddresses: ContractAddresses;
|
||||||
|
let exchangeContract: ExchangeContract;
|
||||||
|
|
||||||
const chainId = TESTRPC_CHAIN_ID;
|
const chainId = TESTRPC_CHAIN_ID;
|
||||||
|
|
||||||
let orders: SignedOrder[];
|
let orders: PrunedSignedOrder[];
|
||||||
let marketSellSwapQuote: SwapQuote;
|
let marketSellSwapQuote: SwapQuote;
|
||||||
let marketBuySwapQuote: SwapQuote;
|
let marketBuySwapQuote: SwapQuote;
|
||||||
let swapQuoteConsumer: ExchangeSwapQuoteConsumer;
|
let swapQuoteConsumer: ExchangeSwapQuoteConsumer;
|
||||||
|
let expectMakerAndTakerBalancesForMakerAssetAsync: (
|
||||||
|
expectedMakerBalance: BigNumber,
|
||||||
|
expectedTakerBalance: BigNumber,
|
||||||
|
) => Promise<void>;
|
||||||
|
let expectMakerAndTakerBalancesForTakerAssetAsync: (
|
||||||
|
expectedMakerBalance: BigNumber,
|
||||||
|
expectedTakerBalance: BigNumber,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
contractAddresses = await migrateOnceAsync();
|
contractAddresses = await migrateOnceAsync();
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
const config = {
|
|
||||||
chainId,
|
|
||||||
contractAddresses,
|
|
||||||
};
|
|
||||||
contractWrappers = new ContractWrappers(provider, config);
|
|
||||||
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
||||||
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
||||||
[makerAssetData, takerAssetData, wethAssetData] = [
|
const devUtils = new DevUtilsContract(contractAddresses.devUtils, provider);
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
[makerAssetData, takerAssetData, wethAssetData] = await Promise.all([
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
||||||
];
|
devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
||||||
erc20TokenContract = new ERC20TokenContract(makerTokenAddress, provider);
|
]);
|
||||||
|
erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider);
|
||||||
|
erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider);
|
||||||
|
exchangeContract = new ExchangeContract(contractAddresses.exchange, provider);
|
||||||
// Configure order defaults
|
// Configure order defaults
|
||||||
const defaultOrderParams = {
|
const defaultOrderParams = {
|
||||||
...devConstants.STATIC_ORDER_PARAMS,
|
...devConstants.STATIC_ORDER_PARAMS,
|
||||||
@ -79,17 +120,26 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
takerAddress,
|
takerAddress,
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
makerFeeAssetData: await contractWrappers.devUtils
|
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
.encodeERC20AssetData(contractAddresses.zrxToken)
|
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
.callAsync(),
|
makerFee: constants.ZERO_AMOUNT,
|
||||||
takerFeeAssetData: await contractWrappers.devUtils
|
takerFee: constants.ZERO_AMOUNT,
|
||||||
.encodeERC20AssetData(contractAddresses.zrxToken)
|
feeRecipientAddress: feeRecipient,
|
||||||
.callAsync(),
|
|
||||||
exchangeAddress: contractAddresses.exchange,
|
exchangeAddress: contractAddresses.exchange,
|
||||||
chainId,
|
chainId,
|
||||||
};
|
};
|
||||||
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||||
|
expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
|
||||||
|
erc20TakerTokenContract,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress,
|
||||||
|
);
|
||||||
|
expectMakerAndTakerBalancesForMakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
|
||||||
|
erc20MakerTokenContract,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
@ -97,12 +147,13 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
orders = [];
|
orders = [];
|
||||||
for (const fillableAmount of FILLABLE_AMOUNTS) {
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
|
||||||
const order = await orderFactory.newSignedOrderAsync({
|
const order = await orderFactory.newSignedOrderAsync(partialOrder);
|
||||||
makerAssetAmount: fillableAmount,
|
const prunedOrder = {
|
||||||
takerAssetAmount: fillableAmount,
|
...order,
|
||||||
});
|
...partialOrder,
|
||||||
orders.push(order);
|
};
|
||||||
|
orders.push(prunedOrder as PrunedSignedOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
@ -110,6 +161,7 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
takerAssetData,
|
takerAssetData,
|
||||||
orders,
|
orders,
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
@ -117,45 +169,87 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
takerAssetData,
|
takerAssetData,
|
||||||
orders,
|
orders,
|
||||||
MarketOperation.Buy,
|
MarketOperation.Buy,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, {
|
swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, contractAddresses, {
|
||||||
chainId,
|
chainId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await erc20MakerTokenContract
|
||||||
|
.transfer(makerAddress, marketBuySwapQuote.worstCaseQuoteInfo.makerAssetAmount)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: coinbaseAddress,
|
||||||
|
});
|
||||||
|
await erc20TakerTokenContract
|
||||||
|
.transfer(takerAddress, marketBuySwapQuote.worstCaseQuoteInfo.totalTakerAssetAmount)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: coinbaseAddress,
|
||||||
|
});
|
||||||
|
await erc20MakerTokenContract
|
||||||
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
|
||||||
|
.sendTransactionAsync({ from: makerAddress });
|
||||||
|
await erc20TakerTokenContract
|
||||||
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
|
||||||
|
.sendTransactionAsync({ from: takerAddress });
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
});
|
});
|
||||||
describe('executeSwapQuoteOrThrowAsync', () => {
|
describe('#executeSwapQuoteOrThrowAsync', () => {
|
||||||
/*
|
/*
|
||||||
* Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
* Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
||||||
* Does not test the validity of the state change performed by the forwarder smart contract
|
* Does not test the validity of the state change performed by the forwarder smart contract
|
||||||
*/
|
*/
|
||||||
it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
|
it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
|
||||||
let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress });
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
||||||
makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
);
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
|
||||||
|
takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gasLimit: 4000000,
|
||||||
|
});
|
||||||
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
||||||
let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { takerAddress });
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
||||||
makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
);
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
|
||||||
|
takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gasLimit: 4000000,
|
||||||
|
});
|
||||||
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
await expectMakerAndTakerBalancesForTakerAssetAsync(
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSmartContractParamsOrThrow', () => {
|
describe('#getSmartContractParamsOrThrow', () => {
|
||||||
describe('valid swap quote', async () => {
|
describe('valid swap quote', async () => {
|
||||||
// TODO(david) Check for valid MethodAbi
|
// TODO(david) Check for valid MethodAbi
|
||||||
it('provide correct and optimized smart contract params for a marketSell SwapQuote', async () => {
|
it('provide correct and optimized smart contract params for a marketSell SwapQuote', async () => {
|
||||||
@ -163,7 +257,7 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
marketSellSwapQuote,
|
marketSellSwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
||||||
const { takerAssetFillAmount, signatures, type } = params as ExchangeMarketSellSmartContractParams;
|
const { takerAssetFillAmount, signatures, type } = params as ExchangeMarketSellSmartContractParams;
|
||||||
expect(type).to.deep.equal(MarketOperation.Sell);
|
expect(type).to.deep.equal(MarketOperation.Sell);
|
||||||
expect(takerAssetFillAmount).to.bignumber.equal(
|
expect(takerAssetFillAmount).to.bignumber.equal(
|
||||||
@ -172,12 +266,12 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
||||||
expect(signatures).to.deep.equal(orderSignatures);
|
expect(signatures).to.deep.equal(orderSignatures);
|
||||||
});
|
});
|
||||||
it('provide correct and optimized smart contract params for a marketBuy SwapQuote', async () => {
|
it('provide correct smart contract params for a marketBuy SwapQuote', async () => {
|
||||||
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
||||||
marketBuySwapQuote,
|
marketBuySwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
||||||
const { makerAssetFillAmount, signatures, type } = params as ExchangeMarketBuySmartContractParams;
|
const { makerAssetFillAmount, signatures, type } = params as ExchangeMarketBuySmartContractParams;
|
||||||
expect(type).to.deep.equal(MarketOperation.Buy);
|
expect(type).to.deep.equal(MarketOperation.Buy);
|
||||||
expect(makerAssetFillAmount).to.bignumber.equal(
|
expect(makerAssetFillAmount).to.bignumber.equal(
|
||||||
@ -189,49 +283,53 @@ describe('ExchangeSwapQuoteConsumer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getCalldataOrThrow', () => {
|
describe('#getCalldataOrThrow', () => {
|
||||||
describe('valid swap quote', async () => {
|
describe('valid swap quote', async () => {
|
||||||
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
||||||
let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
const { calldataHexString, toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
marketSellSwapQuote,
|
marketSellSwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
||||||
await web3Wrapper.sendTransactionAsync({
|
await web3Wrapper.sendTransactionAsync({
|
||||||
from: takerAddress,
|
from: takerAddress,
|
||||||
to: toAddress,
|
to: toAddress,
|
||||||
data: calldataHexString,
|
data: calldataHexString,
|
||||||
gas: 4000000,
|
gas: 4000000,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
value: ethAmount,
|
||||||
});
|
});
|
||||||
makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
});
|
});
|
||||||
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
||||||
let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
const { calldataHexString, toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
marketBuySwapQuote,
|
marketBuySwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
expect(toAddress).to.deep.equal(exchangeContract.address);
|
||||||
await web3Wrapper.sendTransactionAsync({
|
await web3Wrapper.sendTransactionAsync({
|
||||||
from: takerAddress,
|
from: takerAddress,
|
||||||
to: toAddress,
|
to: toAddress,
|
||||||
data: calldataHexString,
|
data: calldataHexString,
|
||||||
gas: 4000000,
|
gas: 4000000,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
value: ethAmount,
|
||||||
});
|
});
|
||||||
makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesForMakerAssetAsync(
|
||||||
takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { ContractAddresses, ContractWrappers, ERC20TokenContract } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||||
|
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
|
||||||
|
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
|
||||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
@ -12,28 +15,61 @@ import {
|
|||||||
ForwarderMarketBuySmartContractParams,
|
ForwarderMarketBuySmartContractParams,
|
||||||
ForwarderMarketSellSmartContractParams,
|
ForwarderMarketSellSmartContractParams,
|
||||||
MarketBuySwapQuote,
|
MarketBuySwapQuote,
|
||||||
|
MarketOperation,
|
||||||
|
PrunedSignedOrder,
|
||||||
} from '../src/types';
|
} from '../src/types';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
import { migrateOnceAsync } from './utils/migrate';
|
import { migrateOnceAsync } from './utils/migrate';
|
||||||
import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote';
|
import { getFullyFillableSwapQuoteWithNoFees } from './utils/swap_quote';
|
||||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
|
||||||
|
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
||||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
||||||
const TESTRPC_CHAIN_ID = 1337;
|
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
|
||||||
const MARKET_OPERATION = MarketOperation.Sell;
|
|
||||||
const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value =>
|
const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value =>
|
||||||
value.multipliedBy(ONE_ETH_IN_WEI),
|
value.multipliedBy(ONE_ETH_IN_WEI),
|
||||||
);
|
);
|
||||||
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
||||||
|
|
||||||
|
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(1).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectMakerAndTakerBalancesAsyncFactory = (
|
||||||
|
erc20TokenContract: ERC20TokenContract,
|
||||||
|
makerAddress: string,
|
||||||
|
takerAddress: string,
|
||||||
|
) => async (expectedMakerBalance: BigNumber, expectedTakerBalance: BigNumber) => {
|
||||||
|
const makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
||||||
|
const takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
||||||
|
expect(makerBalance).to.bignumber.equal(expectedMakerBalance);
|
||||||
|
expect(takerBalance).to.bignumber.equal(expectedTakerBalance);
|
||||||
|
};
|
||||||
|
|
||||||
describe('ForwarderSwapQuoteConsumer', () => {
|
describe('ForwarderSwapQuoteConsumer', () => {
|
||||||
let contractWrappers: ContractWrappers;
|
const FEE_PERCENTAGE = 0.05;
|
||||||
let erc20Token: ERC20TokenContract;
|
|
||||||
let userAddresses: string[];
|
let userAddresses: string[];
|
||||||
let coinbaseAddress: string;
|
let coinbaseAddress: string;
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
@ -43,33 +79,68 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
let takerTokenAddress: string;
|
let takerTokenAddress: string;
|
||||||
let makerAssetData: string;
|
let makerAssetData: string;
|
||||||
let takerAssetData: string;
|
let takerAssetData: string;
|
||||||
|
let orderFactory: OrderFactory;
|
||||||
|
let invalidOrderFactory: OrderFactory;
|
||||||
let wethAssetData: string;
|
let wethAssetData: string;
|
||||||
let contractAddresses: ContractAddresses;
|
let contractAddresses: ContractAddresses;
|
||||||
|
let erc20TokenContract: ERC20TokenContract;
|
||||||
|
let forwarderContract: ForwarderContract;
|
||||||
|
|
||||||
let orders: SignedOrder[];
|
let orders: PrunedSignedOrder[];
|
||||||
|
let invalidOrders: PrunedSignedOrder[];
|
||||||
let marketSellSwapQuote: SwapQuote;
|
let marketSellSwapQuote: SwapQuote;
|
||||||
let marketBuySwapQuote: SwapQuote;
|
let marketBuySwapQuote: SwapQuote;
|
||||||
|
let invalidMarketBuySwapQuote: SwapQuote;
|
||||||
let swapQuoteConsumer: ForwarderSwapQuoteConsumer;
|
let swapQuoteConsumer: ForwarderSwapQuoteConsumer;
|
||||||
let erc20ProxyAddress: string;
|
let expectMakerAndTakerBalancesAsync: (
|
||||||
|
expectedMakerBalance: BigNumber,
|
||||||
|
expectedTakerBalance: BigNumber,
|
||||||
|
) => Promise<void>;
|
||||||
const chainId = TESTRPC_CHAIN_ID;
|
const chainId = TESTRPC_CHAIN_ID;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
contractAddresses = await migrateOnceAsync();
|
contractAddresses = await migrateOnceAsync();
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
const config = {
|
|
||||||
chainId,
|
|
||||||
contractAddresses,
|
|
||||||
};
|
|
||||||
contractWrappers = new ContractWrappers(provider, config);
|
|
||||||
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
||||||
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
||||||
erc20Token = new ERC20TokenContract(makerTokenAddress, provider);
|
erc20TokenContract = new ERC20TokenContract(makerTokenAddress, provider);
|
||||||
[makerAssetData, takerAssetData, wethAssetData] = [
|
forwarderContract = new ForwarderContract(contractAddresses.forwarder, provider);
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
const devUtils = new DevUtilsContract(contractAddresses.devUtils, provider);
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
[makerAssetData, takerAssetData, wethAssetData] = await Promise.all([
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
||||||
];
|
devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
||||||
|
devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
||||||
|
]);
|
||||||
|
// Configure order defaults
|
||||||
|
const defaultOrderParams = {
|
||||||
|
...devConstants.STATIC_ORDER_PARAMS,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress: constants.NULL_ADDRESS,
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData: wethAssetData,
|
||||||
|
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
makerFee: constants.ZERO_AMOUNT,
|
||||||
|
takerFee: constants.ZERO_AMOUNT,
|
||||||
|
feeRecipientAddress: feeRecipient,
|
||||||
|
exchangeAddress: contractAddresses.exchange,
|
||||||
|
chainId,
|
||||||
|
};
|
||||||
|
const invalidDefaultOrderParams = {
|
||||||
|
...defaultOrderParams,
|
||||||
|
...{
|
||||||
|
takerAssetData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||||
|
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||||
|
expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory(
|
||||||
|
erc20TokenContract,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress,
|
||||||
|
);
|
||||||
|
invalidOrderFactory = new OrderFactory(privateKey, invalidDefaultOrderParams);
|
||||||
});
|
});
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
@ -77,35 +148,48 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
const UNLIMITED_ALLOWANCE = UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
const UNLIMITED_ALLOWANCE = UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||||
erc20ProxyAddress = contractAddresses.erc20Proxy;
|
|
||||||
|
|
||||||
const totalFillableAmount = FILLABLE_AMOUNTS.reduce(
|
const totalFillableAmount = FILLABLE_AMOUNTS.reduce(
|
||||||
(a: BigNumber, c: BigNumber) => a.plus(c),
|
(a: BigNumber, c: BigNumber) => a.plus(c),
|
||||||
new BigNumber(0),
|
new BigNumber(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
await erc20Token.transfer(makerAddress, totalFillableAmount).sendTransactionAsync({
|
await erc20TokenContract.transfer(makerAddress, totalFillableAmount).sendTransactionAsync({
|
||||||
from: coinbaseAddress,
|
from: coinbaseAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
await erc20Token.approve(erc20ProxyAddress, UNLIMITED_ALLOWANCE).sendTransactionAsync({
|
await erc20TokenContract
|
||||||
from: makerAddress,
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE)
|
||||||
});
|
.sendTransactionAsync({ from: makerAddress });
|
||||||
orders = await getSignedOrdersWithNoFeesAsync(
|
|
||||||
provider,
|
await forwarderContract.approveMakerAssetProxy(makerAssetData).sendTransactionAsync({ from: makerAddress });
|
||||||
makerAssetData,
|
|
||||||
wethAssetData,
|
orders = [];
|
||||||
makerAddress,
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
|
||||||
takerAddress,
|
const order = await orderFactory.newSignedOrderAsync(partialOrder);
|
||||||
FILLABLE_AMOUNTS,
|
const prunedOrder = {
|
||||||
contractAddresses.exchange,
|
...order,
|
||||||
);
|
...partialOrder,
|
||||||
|
};
|
||||||
|
orders.push(prunedOrder as PrunedSignedOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidOrders = [];
|
||||||
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS) {
|
||||||
|
const order = await invalidOrderFactory.newSignedOrderAsync(partialOrder);
|
||||||
|
const prunedOrder = {
|
||||||
|
...order,
|
||||||
|
...partialOrder,
|
||||||
|
};
|
||||||
|
invalidOrders.push(prunedOrder as PrunedSignedOrder);
|
||||||
|
}
|
||||||
|
|
||||||
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
wethAssetData,
|
wethAssetData,
|
||||||
orders,
|
orders,
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
@ -113,34 +197,29 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
wethAssetData,
|
wethAssetData,
|
||||||
orders,
|
orders,
|
||||||
MarketOperation.Buy,
|
MarketOperation.Buy,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, {
|
invalidMarketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData,
|
||||||
|
invalidOrders,
|
||||||
|
MarketOperation.Buy,
|
||||||
|
GAS_PRICE,
|
||||||
|
);
|
||||||
|
|
||||||
|
swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, contractAddresses, {
|
||||||
chainId,
|
chainId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
});
|
});
|
||||||
describe('executeSwapQuoteOrThrowAsync', () => {
|
describe('#executeSwapQuoteOrThrowAsync', () => {
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
it('should throw if swapQuote provided is not a valid forwarder SwapQuote (taker asset is wEth', async () => {
|
it('should throw if swapQuote provided is not a valid forwarder SwapQuote (taker asset is wEth)', async () => {
|
||||||
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
|
||||||
provider,
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
makerAddress,
|
|
||||||
takerAddress,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
);
|
|
||||||
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
invalidSignedOrders,
|
|
||||||
MARKET_OPERATION,
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
swapQuoteConsumer.executeSwapQuoteOrThrowAsync(invalidSwapQuote, { takerAddress }),
|
swapQuoteConsumer.executeSwapQuoteOrThrowAsync(invalidMarketBuySwapQuote, { takerAddress }),
|
||||||
).to.be.rejectedWith(
|
).to.be.rejectedWith(
|
||||||
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
||||||
);
|
);
|
||||||
@ -153,89 +232,98 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
* Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
* Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
||||||
* Does not test the validity of the state change performed by the forwarder smart contract
|
* Does not test the validity of the state change performed by the forwarder smart contract
|
||||||
*/
|
*/
|
||||||
it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
|
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
||||||
let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress });
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
|
||||||
makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
takerAddress,
|
||||||
takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
gasPrice: GAS_PRICE,
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(0.5).multipliedBy(ONE_ETH_IN_WEI));
|
gasLimit: 4000000,
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(9.5).multipliedBy(ONE_ETH_IN_WEI));
|
});
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should perform a marketBuy execution when provided a MarketBuy type swapQuote', async () => {
|
it('should perform a marketSell execution when provided a MarketSell type swapQuote', async () => {
|
||||||
let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { takerAddress });
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
|
||||||
makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
takerAddress,
|
||||||
takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
gasPrice: GAS_PRICE,
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
gasLimit: 4000000,
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
});
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should perform a marketBuy execution with affiliate fees', async () => {
|
it('should perform a marketBuy execution with affiliate fees', async () => {
|
||||||
let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
);
|
||||||
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
||||||
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
|
||||||
takerAddress,
|
takerAddress,
|
||||||
feePercentage: 0.05,
|
gasPrice: GAS_PRICE,
|
||||||
|
gasLimit: 4000000,
|
||||||
|
feePercentage: FEE_PERCENTAGE,
|
||||||
feeRecipient,
|
feeRecipient,
|
||||||
});
|
});
|
||||||
makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
|
||||||
|
);
|
||||||
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
|
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
|
||||||
new BigNumber(0.5).multipliedBy(ONE_ETH_IN_WEI),
|
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(david) Finish marketSell affiliate fee excution testing
|
it('should perform a marketSell execution with affiliate fees', async () => {
|
||||||
// it('should perform a marketSell execution with affiliate fees', async () => {
|
await expectMakerAndTakerBalancesAsync(
|
||||||
// let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
// let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
// const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
);
|
||||||
// expect(makerBalance).to.bignumber.equal((new BigNumber(10)).multipliedBy(ONE_ETH_IN_WEI));
|
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
// expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
|
||||||
// console.log(makerBalance, takerBalance, feeRecipientEthBalanceBefore);
|
takerAddress,
|
||||||
// await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress, feePercentage: 0.05, feeRecipient });
|
feePercentage: FEE_PERCENTAGE,
|
||||||
// makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
feeRecipient,
|
||||||
// takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
gasPrice: GAS_PRICE,
|
||||||
// const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
gasLimit: 4000000,
|
||||||
// console.log(makerBalance, takerBalance, feeRecipientEthBalanceAfter);
|
});
|
||||||
// expect(makerBalance).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
await expectMakerAndTakerBalancesAsync(
|
||||||
// expect(takerBalance).to.bignumber.equal((new BigNumber(9.5)).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
// expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal((new BigNumber(0.5)).multipliedBy(ONE_ETH_IN_WEI));
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
// });
|
);
|
||||||
|
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
|
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
|
||||||
|
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
|
||||||
|
);
|
||||||
|
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
|
||||||
|
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSmartContractParamsOrThrow', () => {
|
describe('#getSmartContractParamsOrThrow', () => {
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
||||||
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
expect(
|
||||||
provider,
|
swapQuoteConsumer.getSmartContractParamsOrThrowAsync(invalidMarketBuySwapQuote, {}),
|
||||||
makerAssetData,
|
).to.be.rejectedWith(
|
||||||
takerAssetData,
|
|
||||||
makerAddress,
|
|
||||||
takerAddress,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
);
|
|
||||||
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
invalidSignedOrders,
|
|
||||||
MARKET_OPERATION,
|
|
||||||
);
|
|
||||||
expect(swapQuoteConsumer.getSmartContractParamsOrThrowAsync(invalidSwapQuote, {})).to.be.rejectedWith(
|
|
||||||
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -247,9 +335,8 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
marketSellSwapQuote,
|
marketSellSwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
expect(toAddress).to.deep.equal(forwarderContract.address);
|
||||||
const {
|
const {
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
feePercentage,
|
||||||
feeRecipient: feeRecipientFromParams,
|
feeRecipient: feeRecipientFromParams,
|
||||||
signatures,
|
signatures,
|
||||||
@ -260,17 +347,15 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
||||||
expect(signatures).to.deep.equal(orderSignatures);
|
expect(signatures).to.deep.equal(orderSignatures);
|
||||||
expect(feePercentage).to.bignumber.equal(0);
|
expect(feePercentage).to.bignumber.equal(0);
|
||||||
expect(feeSignatures).to.deep.equal([]);
|
|
||||||
});
|
});
|
||||||
it('provide correct and optimized smart contract params with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
it('provide correct and optimized smart contract params with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
||||||
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
||||||
marketBuySwapQuote,
|
marketBuySwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
expect(toAddress).to.deep.equal(forwarderContract.address);
|
||||||
const {
|
const {
|
||||||
makerAssetFillAmount,
|
makerAssetFillAmount,
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
feePercentage,
|
||||||
feeRecipient: feeRecipientFromParams,
|
feeRecipient: feeRecipientFromParams,
|
||||||
signatures,
|
signatures,
|
||||||
@ -284,7 +369,6 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
||||||
expect(signatures).to.deep.equal(orderSignatures);
|
expect(signatures).to.deep.equal(orderSignatures);
|
||||||
expect(feePercentage).to.bignumber.equal(0);
|
expect(feePercentage).to.bignumber.equal(0);
|
||||||
expect(feeSignatures).to.deep.equal([]);
|
|
||||||
});
|
});
|
||||||
it('provide correct and optimized smart contract params with affiliate fees for a marketSell SwapQuote', async () => {
|
it('provide correct and optimized smart contract params with affiliate fees for a marketSell SwapQuote', async () => {
|
||||||
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
||||||
@ -294,9 +378,8 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
feeRecipient,
|
feeRecipient,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
expect(toAddress).to.deep.equal(forwarderContract.address);
|
||||||
const {
|
const {
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
feePercentage,
|
||||||
feeRecipient: feeRecipientFromParams,
|
feeRecipient: feeRecipientFromParams,
|
||||||
signatures,
|
signatures,
|
||||||
@ -307,7 +390,6 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
const orderSignatures = marketSellSwapQuote.orders.map(order => order.signature);
|
||||||
expect(signatures).to.deep.equal(orderSignatures);
|
expect(signatures).to.deep.equal(orderSignatures);
|
||||||
expect(feePercentage).to.bignumber.equal(new BigNumber(0.05).multipliedBy(ONE_ETH_IN_WEI));
|
expect(feePercentage).to.bignumber.equal(new BigNumber(0.05).multipliedBy(ONE_ETH_IN_WEI));
|
||||||
expect(feeSignatures).to.deep.equal([]);
|
|
||||||
});
|
});
|
||||||
it('provide correct and optimized smart contract params with affiliate fees for a marketBuy SwapQuote', async () => {
|
it('provide correct and optimized smart contract params with affiliate fees for a marketBuy SwapQuote', async () => {
|
||||||
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
|
||||||
@ -317,10 +399,9 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
feeRecipient,
|
feeRecipient,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
expect(toAddress).to.deep.equal(forwarderContract.address);
|
||||||
const {
|
const {
|
||||||
makerAssetFillAmount,
|
makerAssetFillAmount,
|
||||||
feeSignatures,
|
|
||||||
feePercentage,
|
feePercentage,
|
||||||
feeRecipient: feeRecipientFromParams,
|
feeRecipient: feeRecipientFromParams,
|
||||||
signatures,
|
signatures,
|
||||||
@ -334,29 +415,14 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
const orderSignatures = marketBuySwapQuote.orders.map(order => order.signature);
|
||||||
expect(signatures).to.deep.equal(orderSignatures);
|
expect(signatures).to.deep.equal(orderSignatures);
|
||||||
expect(feePercentage).to.bignumber.equal(new BigNumber(0.05).multipliedBy(ONE_ETH_IN_WEI));
|
expect(feePercentage).to.bignumber.equal(new BigNumber(0.05).multipliedBy(ONE_ETH_IN_WEI));
|
||||||
expect(feeSignatures).to.deep.equal([]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getCalldataOrThrow', () => {
|
describe('#getCalldataOrThrow', () => {
|
||||||
describe('validation', () => {
|
describe('validation', () => {
|
||||||
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
it('should throw if swap quote provided is not a valid forwarder SwapQuote (taker asset is WETH)', async () => {
|
||||||
const invalidSignedOrders = await getSignedOrdersWithNoFeesAsync(
|
expect(swapQuoteConsumer.getCalldataOrThrowAsync(invalidMarketBuySwapQuote, {})).to.be.rejectedWith(
|
||||||
provider,
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
makerAddress,
|
|
||||||
takerAddress,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
);
|
|
||||||
const invalidSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
invalidSignedOrders,
|
|
||||||
MARKET_OPERATION,
|
|
||||||
);
|
|
||||||
expect(swapQuoteConsumer.getCalldataOrThrowAsync(invalidSwapQuote, {})).to.be.rejectedWith(
|
|
||||||
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
`Expected quote.orders[0] to have takerAssetData set as ${wethAssetData}, but is ${takerAssetData}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -364,33 +430,34 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
|
|
||||||
describe('valid swap quote', async () => {
|
describe('valid swap quote', async () => {
|
||||||
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
it('provide correct and optimized calldata options with default options for a marketSell SwapQuote (no affiliate fees)', async () => {
|
||||||
let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
const { calldataHexString, toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
marketSellSwapQuote,
|
marketSellSwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
expect(toAddress).to.deep.equal(forwarderContract.address);
|
||||||
await web3Wrapper.sendTransactionAsync({
|
await web3Wrapper.sendTransactionAsync({
|
||||||
from: takerAddress,
|
from: takerAddress,
|
||||||
to: toAddress,
|
to: toAddress,
|
||||||
data: calldataHexString,
|
data: calldataHexString,
|
||||||
value: marketSellSwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount,
|
value: ethAmount,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
gas: 4000000,
|
gas: 4000000,
|
||||||
});
|
});
|
||||||
makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(0.5).multipliedBy(ONE_ETH_IN_WEI));
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(9.5).multipliedBy(ONE_ETH_IN_WEI));
|
);
|
||||||
});
|
});
|
||||||
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
it('provide correct and optimized calldata options with default options for a marketBuy SwapQuote (no affiliate fees)', async () => {
|
||||||
let makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
let takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
const { calldataHexString, toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
marketBuySwapQuote,
|
marketBuySwapQuote,
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
@ -399,19 +466,84 @@ describe('ForwarderSwapQuoteConsumer', () => {
|
|||||||
from: takerAddress,
|
from: takerAddress,
|
||||||
to: toAddress,
|
to: toAddress,
|
||||||
data: calldataHexString,
|
data: calldataHexString,
|
||||||
value: marketBuySwapQuote.worstCaseQuoteInfo.totalTakerTokenAmount,
|
value: ethAmount,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
gas: 4000000,
|
gas: 4000000,
|
||||||
});
|
});
|
||||||
makerBalance = await erc20Token.balanceOf(makerAddress).callAsync();
|
await expectMakerAndTakerBalancesAsync(
|
||||||
takerBalance = await erc20Token.balanceOf(takerAddress).callAsync();
|
constants.ZERO_AMOUNT,
|
||||||
expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
);
|
||||||
|
});
|
||||||
|
it('provide correct and optimized calldata options with affiliate fees for a marketSell SwapQuote', async () => {
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
);
|
||||||
|
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
|
marketSellSwapQuote,
|
||||||
|
{
|
||||||
|
feePercentage: FEE_PERCENTAGE,
|
||||||
|
feeRecipient,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(toAddress).to.deep.equal(contractAddresses.forwarder);
|
||||||
|
await web3Wrapper.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
to: toAddress,
|
||||||
|
data: calldataHexString,
|
||||||
|
value: ethAmount,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
});
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
|
||||||
|
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
|
||||||
|
);
|
||||||
|
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
|
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
|
||||||
|
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('provide correct and optimized calldata options with affiliate fees for a marketBuy SwapQuote', async () => {
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
);
|
||||||
|
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
|
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
|
||||||
|
marketBuySwapQuote,
|
||||||
|
{
|
||||||
|
feePercentage: FEE_PERCENTAGE,
|
||||||
|
feeRecipient,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(toAddress).to.deep.equal(contractAddresses.forwarder);
|
||||||
|
await web3Wrapper.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
to: toAddress,
|
||||||
|
data: calldataHexString,
|
||||||
|
value: ethAmount,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
});
|
||||||
|
await expectMakerAndTakerBalancesAsync(
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
|
||||||
|
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
|
||||||
|
);
|
||||||
|
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
|
||||||
|
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
|
||||||
|
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
// TODO(david) finish testing for affiliate fees calldata output
|
|
||||||
// it('provide correct and optimized calldata options with affiliate fees for a marketSell SwapQuote', async () => {
|
|
||||||
// });
|
|
||||||
// it('provide correct and optimized calldata options with affiliate fees for a marketBuy SwapQuote', async () => {
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// tslint:disable-next-line: max-file-line-count
|
||||||
});
|
});
|
||||||
|
374
packages/asset-swapper/test/market_utils_test.ts
Normal file
374
packages/asset-swapper/test/market_utils_test.ts
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
import * as chai from 'chai';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { constants } from '../src/constants';
|
||||||
|
import { marketUtils } from '../src/utils/market_utils';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { testOrders } from './utils/test_orders';
|
||||||
|
import { baseUnitAmount } from './utils/utils';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
// tslint:disable:custom-no-magic-numbers
|
||||||
|
// tslint:disable: no-unused-expression
|
||||||
|
describe('marketUtils', () => {
|
||||||
|
describe('#findOrdersThatCoverTakerAssetFillAmount', () => {
|
||||||
|
describe('no orders', () => {
|
||||||
|
it('returns empty and unchanged remainingFillAmount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(9);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
[],
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.empty;
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders do not have fees', () => {
|
||||||
|
const inputOrders = testOrders.PRUNED_SIGNED_ORDERS_FEELESS;
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(5);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(3);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(10);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(2);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(3));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(1);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders have fees in takerAsset', () => {
|
||||||
|
const inputOrders = testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET;
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(10);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(5);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(6);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(10);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(6);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(1));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(9);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders are feeless or have fees in takerAsset', () => {
|
||||||
|
const inputOrders = _.concat(
|
||||||
|
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
|
||||||
|
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
);
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(20);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(10);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(12);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(20);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(6);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(2));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first four orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(16);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverTakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1], inputOrders[2], inputOrders[3]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#findOrdersThatCoverMakerAssetFillAmount', () => {
|
||||||
|
describe('no orders', () => {
|
||||||
|
it('returns empty and unchanged remainingFillAmount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(9);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
[],
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.empty;
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders do not have fees', () => {
|
||||||
|
const inputOrders = testOrders.PRUNED_SIGNED_ORDERS_FEELESS;
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(3);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(10);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(2);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(2));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(6);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(8);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders have fees in makerAsset', () => {
|
||||||
|
const inputOrders = testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET;
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(2);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(3);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(4);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(0.5);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(3);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(3);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(1));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(1);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(2);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('orders are feeless or have fees in makerAsset', () => {
|
||||||
|
const inputOrders = _.concat(
|
||||||
|
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
|
||||||
|
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
);
|
||||||
|
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(12);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(3);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(12);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(2);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(14);
|
||||||
|
const slippageBufferAmount = baseUnitAmount(4);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
slippageBufferAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal(inputOrders);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(baseUnitAmount(3));
|
||||||
|
});
|
||||||
|
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(1);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
it('returns first four orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
|
||||||
|
const fillAmount = baseUnitAmount(11);
|
||||||
|
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
|
||||||
|
inputOrders,
|
||||||
|
fillAmount,
|
||||||
|
);
|
||||||
|
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1], inputOrders[2], inputOrders[3]]);
|
||||||
|
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
336
packages/asset-swapper/test/order_prune_utils_test.ts
Normal file
336
packages/asset-swapper/test/order_prune_utils_test.ts
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||||
|
import { ExchangeContract } from '@0x/contracts-exchange';
|
||||||
|
import { constants as devConstants, getLatestBlockTimestampAsync, OrderFactory } from '@0x/contracts-test-utils';
|
||||||
|
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
||||||
|
import { assetDataUtils } from '@0x/order-utils';
|
||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { constants } from '../src/constants';
|
||||||
|
import { OrderPrunerPermittedFeeTypes } from '../src/types';
|
||||||
|
import { OrderPruner } from '../src/utils/order_prune_utils';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { migrateOnceAsync } from './utils/migrate';
|
||||||
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
|
||||||
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
||||||
|
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
|
||||||
|
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
||||||
|
const PROTOCOL_FEE_PER_FILL = GAS_PRICE.times(constants.PROTOCOL_FEE_MULTIPLIER);
|
||||||
|
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
||||||
|
|
||||||
|
// tslint:disable: no-unused-expression
|
||||||
|
// tslint:disable: custom-no-magic-numbers
|
||||||
|
describe('OrderPruner', () => {
|
||||||
|
let erc20MakerTokenContract: ERC20TokenContract;
|
||||||
|
let erc20TakerTokenContract: ERC20TokenContract;
|
||||||
|
let exchangeContract: ExchangeContract;
|
||||||
|
let devUtilsContract: DevUtilsContract;
|
||||||
|
let userAddresses: string[];
|
||||||
|
let coinbaseAddress: string;
|
||||||
|
let makerAddress: string;
|
||||||
|
let takerAddress: string;
|
||||||
|
let feeRecipient: string;
|
||||||
|
let makerTokenAddress: string;
|
||||||
|
let takerTokenAddress: string;
|
||||||
|
let makerAssetData: string;
|
||||||
|
let takerAssetData: string;
|
||||||
|
let orderFactory: OrderFactory;
|
||||||
|
let wethAssetData: string;
|
||||||
|
let contractAddresses: ContractAddresses;
|
||||||
|
let orderPruner: OrderPruner;
|
||||||
|
|
||||||
|
let nonOpenSignedOrder: SignedOrder;
|
||||||
|
let expiredOpenSignedOrder: SignedOrder;
|
||||||
|
let invalidSignatureOpenSignedOrder: SignedOrder;
|
||||||
|
let fullyFillableOpenSignedOrder: SignedOrder;
|
||||||
|
let partiallyFilledOpenSignedOrderFeeless: SignedOrder;
|
||||||
|
let partiallyFilledOpenSignedOrderFeeInTakerAsset: SignedOrder;
|
||||||
|
let partiallyFilledOpenSignedOrderFeeInMakerAsset: SignedOrder;
|
||||||
|
let filledOpenSignedOrder: SignedOrder;
|
||||||
|
|
||||||
|
const chainId = TESTRPC_CHAIN_ID;
|
||||||
|
const fillableAmount = new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI);
|
||||||
|
const partialFillAmount = new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI);
|
||||||
|
const takerFeeAmount = new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI);
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
contractAddresses = await migrateOnceAsync();
|
||||||
|
await blockchainLifecycle.startAsync();
|
||||||
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
|
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
||||||
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
||||||
|
erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider);
|
||||||
|
erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider);
|
||||||
|
[makerAssetData, takerAssetData, wethAssetData] = [
|
||||||
|
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
|
||||||
|
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
|
||||||
|
assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken),
|
||||||
|
];
|
||||||
|
exchangeContract = new ExchangeContract(contractAddresses.exchange, provider);
|
||||||
|
devUtilsContract = new DevUtilsContract(contractAddresses.devUtils, provider);
|
||||||
|
// Configure order defaults
|
||||||
|
const defaultOrderParams = {
|
||||||
|
...devConstants.STATIC_ORDER_PARAMS,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress: constants.NULL_ADDRESS,
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData,
|
||||||
|
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
makerFee: constants.ZERO_AMOUNT,
|
||||||
|
takerFee: constants.ZERO_AMOUNT,
|
||||||
|
feeRecipientAddress: feeRecipient,
|
||||||
|
exchangeAddress: contractAddresses.exchange,
|
||||||
|
chainId,
|
||||||
|
};
|
||||||
|
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||||
|
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await blockchainLifecycle.revertAsync();
|
||||||
|
});
|
||||||
|
beforeEach(async () => {
|
||||||
|
await blockchainLifecycle.startAsync();
|
||||||
|
|
||||||
|
nonOpenSignedOrder = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
expiredOpenSignedOrder = await orderFactory.newSignedOrderAsync({
|
||||||
|
expirationTimeSeconds: new BigNumber(await getLatestBlockTimestampAsync()).minus(10),
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidSignatureOpenSignedOrder = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAddress,
|
||||||
|
});
|
||||||
|
invalidSignatureOpenSignedOrder.signature = constants.NULL_BYTES;
|
||||||
|
|
||||||
|
fullyFillableOpenSignedOrder = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAssetAmount: fillableAmount,
|
||||||
|
makerAssetAmount: fillableAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
// give double fillableAmount to maker and taker as buffer
|
||||||
|
await erc20MakerTokenContract
|
||||||
|
.transfer(makerAddress, fillableAmount.multipliedBy(4))
|
||||||
|
.sendTransactionAsync({ from: coinbaseAddress });
|
||||||
|
await erc20TakerTokenContract
|
||||||
|
.transfer(takerAddress, fillableAmount.multipliedBy(4))
|
||||||
|
.sendTransactionAsync({ from: coinbaseAddress });
|
||||||
|
await erc20MakerTokenContract
|
||||||
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
|
||||||
|
.sendTransactionAsync({ from: makerAddress });
|
||||||
|
await erc20MakerTokenContract
|
||||||
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
|
||||||
|
.sendTransactionAsync({ from: takerAddress });
|
||||||
|
await erc20TakerTokenContract
|
||||||
|
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
|
||||||
|
.sendTransactionAsync({ from: takerAddress });
|
||||||
|
|
||||||
|
partiallyFilledOpenSignedOrderFeeless = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAssetAmount: fillableAmount,
|
||||||
|
makerAssetAmount: fillableAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
await exchangeContract
|
||||||
|
.fillOrKillOrder(
|
||||||
|
partiallyFilledOpenSignedOrderFeeless,
|
||||||
|
partialFillAmount,
|
||||||
|
partiallyFilledOpenSignedOrderFeeless.signature,
|
||||||
|
)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
value: PROTOCOL_FEE_PER_FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAssetAmount: fillableAmount,
|
||||||
|
makerAssetAmount: fillableAmount,
|
||||||
|
takerFee: takerFeeAmount,
|
||||||
|
takerFeeAssetData: takerAssetData,
|
||||||
|
});
|
||||||
|
|
||||||
|
await exchangeContract
|
||||||
|
.fillOrKillOrder(
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset,
|
||||||
|
partialFillAmount,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset.signature,
|
||||||
|
)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
value: PROTOCOL_FEE_PER_FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAssetAmount: fillableAmount,
|
||||||
|
makerAssetAmount: fillableAmount,
|
||||||
|
takerFee: takerFeeAmount,
|
||||||
|
takerFeeAssetData: makerAssetData,
|
||||||
|
});
|
||||||
|
|
||||||
|
await exchangeContract
|
||||||
|
.fillOrKillOrder(
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset,
|
||||||
|
partialFillAmount,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset.signature,
|
||||||
|
)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
value: PROTOCOL_FEE_PER_FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
filledOpenSignedOrder = await orderFactory.newSignedOrderAsync({
|
||||||
|
takerAssetAmount: fillableAmount,
|
||||||
|
makerAssetAmount: fillableAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
await exchangeContract
|
||||||
|
.fillOrKillOrder(filledOpenSignedOrder, fillableAmount, filledOpenSignedOrder.signature)
|
||||||
|
.sendTransactionAsync({
|
||||||
|
from: takerAddress,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
gas: 4000000,
|
||||||
|
value: PROTOCOL_FEE_PER_FILL,
|
||||||
|
});
|
||||||
|
|
||||||
|
orderPruner = new OrderPruner(devUtilsContract, {
|
||||||
|
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([
|
||||||
|
OrderPrunerPermittedFeeTypes.NoFees,
|
||||||
|
OrderPrunerPermittedFeeTypes.MakerDenominatedTakerFee,
|
||||||
|
OrderPrunerPermittedFeeTypes.TakerDenominatedTakerFee,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(async () => {
|
||||||
|
await blockchainLifecycle.revertAsync();
|
||||||
|
});
|
||||||
|
describe('constructor options', () => {
|
||||||
|
it('should filter for only feeless orders if options permit only feeless orders', async () => {
|
||||||
|
orderPruner = new OrderPruner(devUtilsContract, {
|
||||||
|
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([OrderPrunerPermittedFeeTypes.NoFees]),
|
||||||
|
});
|
||||||
|
const orders = [
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeless,
|
||||||
|
];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
// checks for one order in results and check for signature of orders
|
||||||
|
expect(resultPrunedOrders.length).to.be.equal(1);
|
||||||
|
expect(resultPrunedOrders[0].signature).to.be.deep.equal(partiallyFilledOpenSignedOrderFeeless.signature);
|
||||||
|
});
|
||||||
|
it('should filter for only takerFee in takerAsset orders if options permit only takerFee in takerAsset orders', async () => {
|
||||||
|
orderPruner = new OrderPruner(devUtilsContract, {
|
||||||
|
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([
|
||||||
|
OrderPrunerPermittedFeeTypes.TakerDenominatedTakerFee,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
const orders = [
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeless,
|
||||||
|
];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
// checks for one order in results and check for signature of orders
|
||||||
|
expect(resultPrunedOrders.length).to.be.equal(1);
|
||||||
|
expect(resultPrunedOrders[0].signature).to.be.deep.equal(
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset.signature,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should filter for only makerFee in takerAsset orders if options permit only makerFee orders', async () => {
|
||||||
|
orderPruner = new OrderPruner(devUtilsContract, {
|
||||||
|
permittedOrderFeeTypes: new Set<OrderPrunerPermittedFeeTypes>([
|
||||||
|
OrderPrunerPermittedFeeTypes.MakerDenominatedTakerFee,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
const orders = [
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeless,
|
||||||
|
];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
// checks for one order in results and check for signature of orders
|
||||||
|
expect(resultPrunedOrders.length).to.be.equal(1);
|
||||||
|
expect(resultPrunedOrders[0].signature).to.be.deep.equal(
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset.signature,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#pruneSignedOrdersAsync', () => {
|
||||||
|
it('should filter out non open orders', async () => {
|
||||||
|
const orders = [nonOpenSignedOrder];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
expect(resultPrunedOrders).to.be.empty;
|
||||||
|
});
|
||||||
|
it('should filter out expired orders', async () => {
|
||||||
|
const orders = [expiredOpenSignedOrder];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
expect(resultPrunedOrders).to.be.empty;
|
||||||
|
});
|
||||||
|
it('should filter out invalid signature orders', async () => {
|
||||||
|
const orders = [invalidSignatureOpenSignedOrder];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
expect(resultPrunedOrders).to.be.empty;
|
||||||
|
});
|
||||||
|
it('should filter out fully filled orders', async () => {
|
||||||
|
const orders = [filledOpenSignedOrder];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
expect(resultPrunedOrders).to.be.empty;
|
||||||
|
});
|
||||||
|
it('should provide correct pruned signed orders for fully fillable orders', async () => {
|
||||||
|
const orders = [fullyFillableOpenSignedOrder];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
const prunedOrder = resultPrunedOrders[0];
|
||||||
|
expect(prunedOrder.fillableMakerAssetAmount).to.bignumber.equal(fillableAmount);
|
||||||
|
expect(prunedOrder.fillableTakerAssetAmount).to.bignumber.equal(fillableAmount);
|
||||||
|
});
|
||||||
|
it('should provide correct pruned signed orders for partially fillable orders', async () => {
|
||||||
|
const orders = [
|
||||||
|
partiallyFilledOpenSignedOrderFeeless,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInTakerAsset,
|
||||||
|
partiallyFilledOpenSignedOrderFeeInMakerAsset,
|
||||||
|
];
|
||||||
|
const resultPrunedOrders = await orderPruner.pruneSignedOrdersAsync(orders);
|
||||||
|
expect(resultPrunedOrders[0].fillableMakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[0].fillableTakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[1].fillableMakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[1].fillableTakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[1].fillableTakerFeeAmount).to.bignumber.equal(
|
||||||
|
new BigNumber(1.6).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[2].fillableMakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[2].fillableTakerAssetAmount).to.bignumber.equal(
|
||||||
|
fillableAmount.minus(partialFillAmount),
|
||||||
|
);
|
||||||
|
expect(resultPrunedOrders[2].fillableTakerFeeAmount).to.bignumber.equal(
|
||||||
|
new BigNumber(1.6).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
134
packages/asset-swapper/test/sorting_utils_test.ts
Normal file
134
packages/asset-swapper/test/sorting_utils_test.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { sortingUtils } from '../src/utils/sorting_utils';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { testOrderFactory } from './utils/test_order_factory';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
const FAKE_ERC20_TAKER_ASSET_DATA = '0xf47261b22222222222222222222222222222222222222222222222222222222222222222';
|
||||||
|
const FAKE_ERC20_MAKER_ASSET_DATA = '0xf47261b11111111111111111111111111111111111111111111111111111111111111111';
|
||||||
|
|
||||||
|
describe('sortingUtils', () => {
|
||||||
|
describe('#sortOrders', () => {
|
||||||
|
// rate: 2 takerAsset / makerAsset
|
||||||
|
const testOrder1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(200),
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 1 takerAsset / makerAsset
|
||||||
|
const testOrder2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 2.5 takerAsset / makerAsset
|
||||||
|
const testOrder3 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(250),
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 2 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInTakerAsset1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(100),
|
||||||
|
takerFee: new BigNumber(100),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 1 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInTakerAsset2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(50),
|
||||||
|
takerFee: new BigNumber(50),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 2.5 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInTakerAsset3 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(100),
|
||||||
|
takerAssetAmount: new BigNumber(200),
|
||||||
|
takerFee: new BigNumber(50),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 2 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInMakerAsset1 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(200),
|
||||||
|
takerAssetAmount: new BigNumber(200),
|
||||||
|
takerFee: new BigNumber(100),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 1 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInMakerAsset2 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(150),
|
||||||
|
takerAssetAmount: new BigNumber(100),
|
||||||
|
takerFee: new BigNumber(50),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
// rate: 2.5 takerAsset / makerAsset
|
||||||
|
const testOrderWithFeeInMakerAsset3 = testOrderFactory.generateTestSignedOrder({
|
||||||
|
makerAssetAmount: new BigNumber(150),
|
||||||
|
takerAssetAmount: new BigNumber(250),
|
||||||
|
takerFee: new BigNumber(50),
|
||||||
|
takerFeeAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
});
|
||||||
|
it('correctly sorts by fee adjusted rate (feeless orders)', async () => {
|
||||||
|
const orders = [testOrder1, testOrder2, testOrder3];
|
||||||
|
const sortedOrders = sortingUtils.sortOrders(orders);
|
||||||
|
expect(sortedOrders).to.deep.equal([testOrder2, testOrder1, testOrder3]);
|
||||||
|
});
|
||||||
|
it('correctly sorts by fee adjusted rate (takerAsset denominated fee orders)', async () => {
|
||||||
|
const orders = [
|
||||||
|
testOrderWithFeeInTakerAsset1,
|
||||||
|
testOrderWithFeeInTakerAsset2,
|
||||||
|
testOrderWithFeeInTakerAsset3,
|
||||||
|
];
|
||||||
|
const sortedOrders = sortingUtils.sortOrders(orders);
|
||||||
|
expect(sortedOrders).to.deep.equal([
|
||||||
|
testOrderWithFeeInTakerAsset2,
|
||||||
|
testOrderWithFeeInTakerAsset1,
|
||||||
|
testOrderWithFeeInTakerAsset3,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('correctly sorts by fee adjusted rate (makerAsset denominated fee orders)', async () => {
|
||||||
|
const orders = [
|
||||||
|
testOrderWithFeeInMakerAsset1,
|
||||||
|
testOrderWithFeeInMakerAsset2,
|
||||||
|
testOrderWithFeeInMakerAsset3,
|
||||||
|
];
|
||||||
|
const sortedOrders = sortingUtils.sortOrders(orders);
|
||||||
|
expect(sortedOrders).to.deep.equal([
|
||||||
|
testOrderWithFeeInMakerAsset2,
|
||||||
|
testOrderWithFeeInMakerAsset1,
|
||||||
|
testOrderWithFeeInMakerAsset3,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('correctly sorts by fee adjusted rate (mixed orders)', async () => {
|
||||||
|
const orders = [testOrderWithFeeInMakerAsset1, testOrderWithFeeInTakerAsset2, testOrder3];
|
||||||
|
const sortedOrders = sortingUtils.sortOrders(orders);
|
||||||
|
expect(sortedOrders).to.deep.equal([
|
||||||
|
testOrderWithFeeInTakerAsset2,
|
||||||
|
testOrderWithFeeInMakerAsset1,
|
||||||
|
testOrder3,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
@ -1,176 +0,0 @@
|
|||||||
import { ContractAddresses, ContractWrappers, ERC20TokenContract } from '@0x/contract-wrappers';
|
|
||||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as chai from 'chai';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { SwapQuote, SwapQuoteConsumer } from '../src';
|
|
||||||
import { ExtensionContractType } from '../src/types';
|
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
|
||||||
import { migrateOnceAsync } from './utils/migrate';
|
|
||||||
import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote';
|
|
||||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|
||||||
|
|
||||||
chaiSetup.configure();
|
|
||||||
const expect = chai.expect;
|
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
||||||
|
|
||||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
|
||||||
const TESTRPC_CHAIN_ID = 1337;
|
|
||||||
const FILLABLE_AMOUNTS = [new BigNumber(3), new BigNumber(2), new BigNumber(5)].map(value =>
|
|
||||||
value.multipliedBy(ONE_ETH_IN_WEI),
|
|
||||||
);
|
|
||||||
|
|
||||||
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
|
|
||||||
|
|
||||||
describe('SwapQuoteConsumer', () => {
|
|
||||||
let contractWrappers: ContractWrappers;
|
|
||||||
let erc20Token: ERC20TokenContract;
|
|
||||||
let userAddresses: string[];
|
|
||||||
let coinbaseAddress: string;
|
|
||||||
let makerAddress: string;
|
|
||||||
let takerAddress: string;
|
|
||||||
let feeRecipient: string;
|
|
||||||
let makerTokenAddress: string;
|
|
||||||
let takerTokenAddress: string;
|
|
||||||
let makerAssetData: string;
|
|
||||||
let takerAssetData: string;
|
|
||||||
let wethAssetData: string;
|
|
||||||
let contractAddresses: ContractAddresses;
|
|
||||||
|
|
||||||
const chainId = TESTRPC_CHAIN_ID;
|
|
||||||
|
|
||||||
let orders: SignedOrder[];
|
|
||||||
let marketSellSwapQuote: SwapQuote;
|
|
||||||
let swapQuoteConsumer: SwapQuoteConsumer;
|
|
||||||
let erc20ProxyAddress: string;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
contractAddresses = await migrateOnceAsync();
|
|
||||||
await blockchainLifecycle.startAsync();
|
|
||||||
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
|
||||||
const config = {
|
|
||||||
chainId,
|
|
||||||
contractAddresses,
|
|
||||||
};
|
|
||||||
contractWrappers = new ContractWrappers(provider, config);
|
|
||||||
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
|
|
||||||
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
||||||
erc20Token = new ERC20TokenContract(makerTokenAddress, provider);
|
|
||||||
[makerAssetData, takerAssetData, wethAssetData] = [
|
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
after(async () => {
|
|
||||||
await blockchainLifecycle.revertAsync();
|
|
||||||
});
|
|
||||||
beforeEach(async () => {
|
|
||||||
await blockchainLifecycle.startAsync();
|
|
||||||
const UNLIMITED_ALLOWANCE = UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
|
||||||
erc20ProxyAddress = contractAddresses.erc20Proxy;
|
|
||||||
|
|
||||||
const totalFillableAmount = FILLABLE_AMOUNTS.reduce(
|
|
||||||
(a: BigNumber, c: BigNumber) => a.plus(c),
|
|
||||||
new BigNumber(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
await erc20Token.transfer(makerAddress, totalFillableAmount).sendTransactionAsync({
|
|
||||||
from: coinbaseAddress,
|
|
||||||
});
|
|
||||||
|
|
||||||
await erc20Token.approve(erc20ProxyAddress, UNLIMITED_ALLOWANCE).sendTransactionAsync({
|
|
||||||
from: makerAddress,
|
|
||||||
});
|
|
||||||
orders = await getSignedOrdersWithNoFeesAsync(
|
|
||||||
provider,
|
|
||||||
makerAssetData,
|
|
||||||
wethAssetData,
|
|
||||||
makerAddress,
|
|
||||||
takerAddress,
|
|
||||||
FILLABLE_AMOUNTS,
|
|
||||||
contractAddresses.exchange,
|
|
||||||
);
|
|
||||||
|
|
||||||
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
|
||||||
makerAssetData,
|
|
||||||
wethAssetData,
|
|
||||||
orders,
|
|
||||||
MarketOperation.Sell,
|
|
||||||
);
|
|
||||||
|
|
||||||
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
|
|
||||||
chainId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterEach(async () => {
|
|
||||||
await blockchainLifecycle.revertAsync();
|
|
||||||
});
|
|
||||||
// TODO(david): write tests to ensure options work for executeSwapQuote
|
|
||||||
// describe('executeSwapQuoteOrThrowAsync', () => {
|
|
||||||
// /*
|
|
||||||
// * Testing that SwapQuoteConsumer logic correctly performs a execution (doesn't throw or revert)
|
|
||||||
// * Does not test the validity of the state change performed by the forwarder smart contract
|
|
||||||
// */
|
|
||||||
// it('should perform an asset swap with Forwarder contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
// let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
|
||||||
// let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
|
||||||
// expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
|
||||||
// expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
||||||
// await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, { takerAddress, useExtensionContract: ConsumerType.Forwarder });
|
|
||||||
// makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
|
||||||
// takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
|
||||||
// expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
|
||||||
// expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
||||||
// });
|
|
||||||
// it('should perform an asset swap with Exchange contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
// let makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
|
||||||
// let takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
|
||||||
// expect(makerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
|
||||||
// expect(takerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
||||||
// await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, { takerAddress });
|
|
||||||
// makerBalance = await erc20TokenContract.balanceOf(makerAddress).callAsync();
|
|
||||||
// takerBalance = await erc20TokenContract.balanceOf(takerAddress).callAsync();
|
|
||||||
// expect(takerBalance).to.bignumber.equal(new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI));
|
|
||||||
// expect(makerBalance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('getSmartContractParamsOrThrow', () => {
|
|
||||||
describe('valid swap quote', async () => {
|
|
||||||
// TODO(david) Check for valid MethodAbi
|
|
||||||
it('should provide correct and optimized smart contract params for Forwarder contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, {
|
|
||||||
useExtensionContract: ExtensionContractType.Forwarder,
|
|
||||||
});
|
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
|
||||||
});
|
|
||||||
it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
const { toAddress } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(marketSellSwapQuote, {
|
|
||||||
useExtensionContract: ExtensionContractType.None,
|
|
||||||
});
|
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getCalldataOrThrow', () => {
|
|
||||||
describe('valid swap quote', async () => {
|
|
||||||
it('should provide correct and optimized calldata options for Forwarder contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, {
|
|
||||||
useExtensionContract: ExtensionContractType.Forwarder,
|
|
||||||
});
|
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.forwarder.address);
|
|
||||||
});
|
|
||||||
it('should provide correct and optimized smart contract params for Exchange contract when provided corresponding useExtensionContract option', async () => {
|
|
||||||
const { toAddress } = await swapQuoteConsumer.getCalldataOrThrowAsync(marketSellSwapQuote, {
|
|
||||||
useExtensionContract: ExtensionContractType.None,
|
|
||||||
});
|
|
||||||
expect(toAddress).to.deep.equal(contractWrappers.exchange.address);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,16 +1,19 @@
|
|||||||
import { ContractAddresses, ContractWrappers } from '@0x/contract-wrappers';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
|
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||||
|
import { WETH9Contract } from '@0x/contracts-erc20';
|
||||||
|
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
|
||||||
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { SwapQuote, SwapQuoteConsumer } from '../src';
|
import { SwapQuote, SwapQuoteConsumer } from '../src';
|
||||||
import { ExtensionContractType } from '../src/types';
|
import { constants } from '../src/constants';
|
||||||
|
import { ExtensionContractType, MarketOperation, PrunedSignedOrder } from '../src/types';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
import { migrateOnceAsync } from './utils/migrate';
|
import { migrateOnceAsync } from './utils/migrate';
|
||||||
import { getFullyFillableSwapQuoteWithNoFees, getSignedOrdersWithNoFeesAsync } from './utils/swap_quote';
|
import { getFullyFillableSwapQuoteWithNoFees } from './utils/swap_quote';
|
||||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
@ -19,15 +22,52 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|||||||
|
|
||||||
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
|
||||||
const TESTRPC_CHAIN_ID = 1337;
|
const TESTRPC_CHAIN_ID = 1337;
|
||||||
const FILLABLE_AMOUNTS = [new BigNumber(2), new BigNumber(3), new BigNumber(5)].map(value =>
|
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
|
||||||
value.multipliedBy(ONE_ETH_IN_WEI),
|
|
||||||
);
|
const PARTIAL_PRUNED_SIGNED_ORDERS: Array<Partial<PrunedSignedOrder>> = [
|
||||||
const LARGE_FILLABLE_AMOUNTS = [new BigNumber(20), new BigNumber(20), new BigNumber(20)].map(value =>
|
{
|
||||||
value.multipliedBy(ONE_ETH_IN_WEI),
|
takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
);
|
makerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(3).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(5).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PARTIAL_LARGE_PRUNED_SIGNED_ORDERS: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
takerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
makerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableTakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
fillableMakerAssetAmount: new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
describe('swapQuoteConsumerUtils', () => {
|
describe('swapQuoteConsumerUtils', () => {
|
||||||
let contractWrappers: ContractWrappers;
|
let wethContract: WETH9Contract;
|
||||||
let userAddresses: string[];
|
let userAddresses: string[];
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
@ -38,25 +78,48 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
let wethAssetData: string;
|
let wethAssetData: string;
|
||||||
let contractAddresses: ContractAddresses;
|
let contractAddresses: ContractAddresses;
|
||||||
let swapQuoteConsumer: SwapQuoteConsumer;
|
let swapQuoteConsumer: SwapQuoteConsumer;
|
||||||
|
let orderFactory: OrderFactory;
|
||||||
|
let forwarderOrderFactory: OrderFactory;
|
||||||
|
|
||||||
const chainId = TESTRPC_CHAIN_ID;
|
const chainId = TESTRPC_CHAIN_ID;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
contractAddresses = await migrateOnceAsync();
|
contractAddresses = await migrateOnceAsync();
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
const config = {
|
const devUtils = new DevUtilsContract(contractAddresses.devUtils, provider);
|
||||||
chainId,
|
wethContract = new WETH9Contract(contractAddresses.etherToken, provider);
|
||||||
contractAddresses,
|
|
||||||
};
|
|
||||||
contractWrappers = new ContractWrappers(provider, config);
|
|
||||||
[takerAddress, makerAddress] = userAddresses;
|
[takerAddress, makerAddress] = userAddresses;
|
||||||
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
||||||
[makerAssetData, takerAssetData, wethAssetData] = [
|
[makerAssetData, takerAssetData, wethAssetData] = [
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
await devUtils.encodeERC20AssetData(makerTokenAddress).callAsync(),
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
await devUtils.encodeERC20AssetData(takerTokenAddress).callAsync(),
|
||||||
await contractWrappers.devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
await devUtils.encodeERC20AssetData(contractAddresses.etherToken).callAsync(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const defaultOrderParams = {
|
||||||
|
...devConstants.STATIC_ORDER_PARAMS,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress: constants.NULL_ADDRESS,
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData,
|
||||||
|
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
makerFee: constants.ZERO_AMOUNT,
|
||||||
|
takerFee: constants.ZERO_AMOUNT,
|
||||||
|
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||||
|
exchangeAddress: contractAddresses.exchange,
|
||||||
|
chainId,
|
||||||
|
};
|
||||||
|
const defaultForwarderOrderParams = {
|
||||||
|
...defaultOrderParams,
|
||||||
|
...{
|
||||||
|
takerAssetData: wethAssetData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||||
|
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||||
|
forwarderOrderFactory = new OrderFactory(privateKey, defaultForwarderOrderParams);
|
||||||
|
|
||||||
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
|
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
|
||||||
chainId,
|
chainId,
|
||||||
});
|
});
|
||||||
@ -72,46 +135,50 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getConsumerTypeForSwapQuoteAsync', () => {
|
describe('getConsumerTypeForSwapQuoteAsync', () => {
|
||||||
let forwarderOrders: SignedOrder[];
|
let forwarderOrders: PrunedSignedOrder[];
|
||||||
let exchangeOrders: SignedOrder[];
|
let exchangeOrders: PrunedSignedOrder[];
|
||||||
let largeForwarderOrders: SignedOrder[];
|
let largeForwarderOrders: PrunedSignedOrder[];
|
||||||
let forwarderSwapQuote: SwapQuote;
|
let forwarderSwapQuote: SwapQuote;
|
||||||
let exchangeSwapQuote: SwapQuote;
|
let exchangeSwapQuote: SwapQuote;
|
||||||
let largeForwarderSwapQuote: SwapQuote;
|
let largeForwarderSwapQuote: SwapQuote;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
exchangeOrders = await getSignedOrdersWithNoFeesAsync(
|
exchangeOrders = [];
|
||||||
provider,
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS) {
|
||||||
makerAssetData,
|
const order = await orderFactory.newSignedOrderAsync(partialOrder);
|
||||||
takerAssetData,
|
const prunedOrder = {
|
||||||
makerAddress,
|
...order,
|
||||||
takerAddress,
|
...partialOrder,
|
||||||
FILLABLE_AMOUNTS,
|
};
|
||||||
);
|
exchangeOrders.push(prunedOrder as PrunedSignedOrder);
|
||||||
|
}
|
||||||
|
|
||||||
forwarderOrders = await getSignedOrdersWithNoFeesAsync(
|
forwarderOrders = [];
|
||||||
provider,
|
for (const partialOrder of PARTIAL_PRUNED_SIGNED_ORDERS) {
|
||||||
makerAssetData,
|
const order = await forwarderOrderFactory.newSignedOrderAsync(partialOrder);
|
||||||
wethAssetData,
|
const prunedOrder = {
|
||||||
makerAddress,
|
...order,
|
||||||
takerAddress,
|
...partialOrder,
|
||||||
FILLABLE_AMOUNTS,
|
};
|
||||||
);
|
forwarderOrders.push(prunedOrder as PrunedSignedOrder);
|
||||||
|
}
|
||||||
|
|
||||||
largeForwarderOrders = await getSignedOrdersWithNoFeesAsync(
|
largeForwarderOrders = [];
|
||||||
provider,
|
for (const partialOrder of PARTIAL_LARGE_PRUNED_SIGNED_ORDERS) {
|
||||||
makerAssetData,
|
const order = await forwarderOrderFactory.newSignedOrderAsync(partialOrder);
|
||||||
wethAssetData,
|
const prunedOrder = {
|
||||||
makerAddress,
|
...order,
|
||||||
takerAddress,
|
...partialOrder,
|
||||||
LARGE_FILLABLE_AMOUNTS,
|
};
|
||||||
);
|
largeForwarderOrders.push(prunedOrder as PrunedSignedOrder);
|
||||||
|
}
|
||||||
|
|
||||||
forwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
forwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
wethAssetData,
|
wethAssetData,
|
||||||
forwarderOrders,
|
forwarderOrders,
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
largeForwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
largeForwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
@ -119,6 +186,7 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
wethAssetData,
|
wethAssetData,
|
||||||
largeForwarderOrders,
|
largeForwarderOrders,
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
|
|
||||||
exchangeSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
exchangeSwapQuote = getFullyFillableSwapQuoteWithNoFees(
|
||||||
@ -126,6 +194,7 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
takerAssetData,
|
takerAssetData,
|
||||||
exchangeOrders,
|
exchangeOrders,
|
||||||
MarketOperation.Sell,
|
MarketOperation.Sell,
|
||||||
|
GAS_PRICE,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -145,7 +214,7 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
});
|
});
|
||||||
it('should return exchange consumer if takerAsset is wEth and taker has enough weth', async () => {
|
it('should return exchange consumer if takerAsset is wEth and taker has enough weth', async () => {
|
||||||
const etherInWei = new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI);
|
const etherInWei = new BigNumber(20).multipliedBy(ONE_ETH_IN_WEI);
|
||||||
await contractWrappers.weth9.deposit().sendTransactionAsync({ value: etherInWei, from: takerAddress });
|
await wethContract.deposit().sendTransactionAsync({ value: etherInWei, from: takerAddress });
|
||||||
const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
|
const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
|
||||||
forwarderSwapQuote,
|
forwarderSwapQuote,
|
||||||
{ takerAddress },
|
{ takerAddress },
|
||||||
@ -154,7 +223,7 @@ describe('swapQuoteConsumerUtils', () => {
|
|||||||
});
|
});
|
||||||
it('should return forwarder consumer if takerAsset is wEth and takerAddress has no available balance in either weth or eth (defaulting behavior)', async () => {
|
it('should return forwarder consumer if takerAsset is wEth and takerAddress has no available balance in either weth or eth (defaulting behavior)', async () => {
|
||||||
const etherInWei = new BigNumber(50).multipliedBy(ONE_ETH_IN_WEI);
|
const etherInWei = new BigNumber(50).multipliedBy(ONE_ETH_IN_WEI);
|
||||||
await contractWrappers.weth9.deposit().sendTransactionAsync({ value: etherInWei, from: takerAddress });
|
await wethContract.deposit().sendTransactionAsync({ value: etherInWei, from: takerAddress });
|
||||||
const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
|
const extensionContractType = await swapQuoteConsumer.getOptimalExtensionContractTypeAsync(
|
||||||
largeForwarderSwapQuote,
|
largeForwarderSwapQuote,
|
||||||
{ takerAddress },
|
{ takerAddress },
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
|
||||||
import { Orderbook } from '@0x/orderbook';
|
import { Orderbook } from '@0x/orderbook';
|
||||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||||
import { AssetPairsItem, SignedOrder } from '@0x/types';
|
import { AssetPairsItem, SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
import { SwapQuoter } from '../src';
|
import { SwapQuoter } from '../src';
|
||||||
import { constants } from '../src/constants';
|
import { constants } from '../src/constants';
|
||||||
import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../src/types';
|
import { LiquidityForTakerMakerAssetDataPair, PrunedSignedOrder } from '../src/types';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
import { mockAvailableAssetDatas, mockedSwapQuoterWithOrdersAndFillableAmounts, orderbookMock } from './utils/mocks';
|
import { mockAvailableAssetDatas, mockedSwapQuoterWithPrunedSignedOrders, orderbookMock } from './utils/mocks';
|
||||||
|
import { testOrderFactory } from './utils/test_order_factory';
|
||||||
|
import { baseUnitAmount } from './utils/utils';
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
@ -27,10 +27,6 @@ const WETH_ASSET_DATA = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5
|
|||||||
const WETH_DECIMALS = constants.ETHER_TOKEN_DECIMALS;
|
const WETH_DECIMALS = constants.ETHER_TOKEN_DECIMALS;
|
||||||
const ZERO = new BigNumber(0);
|
const ZERO = new BigNumber(0);
|
||||||
|
|
||||||
const baseUnitAmount = (unitAmount: number, decimals = TOKEN_DECIMALS): BigNumber => {
|
|
||||||
return Web3Wrapper.toBaseUnitAmount(new BigNumber(unitAmount), decimals);
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetsToAssetPairItems = (makerAssetData: string, takerAssetData: string): AssetPairsItem[] => {
|
const assetsToAssetPairItems = (makerAssetData: string, takerAssetData: string): AssetPairsItem[] => {
|
||||||
const defaultAssetPairItem = {
|
const defaultAssetPairItem = {
|
||||||
minAmount: ZERO,
|
minAmount: ZERO,
|
||||||
@ -64,15 +60,15 @@ const assetsToAssetPairItems = (makerAssetData: string, takerAssetData: string):
|
|||||||
const expectLiquidityResult = async (
|
const expectLiquidityResult = async (
|
||||||
web3Provider: Web3ProviderEngine,
|
web3Provider: Web3ProviderEngine,
|
||||||
orderbook: Orderbook,
|
orderbook: Orderbook,
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
expectedLiquidityResult: LiquidityForAssetData,
|
expectedLiquidityResult: LiquidityForTakerMakerAssetDataPair,
|
||||||
) => {
|
) => {
|
||||||
const mockedSwapQuoter = mockedSwapQuoterWithOrdersAndFillableAmounts(
|
const mockedSwapQuoter = mockedSwapQuoterWithPrunedSignedOrders(
|
||||||
web3Provider,
|
web3Provider,
|
||||||
orderbook,
|
orderbook,
|
||||||
FAKE_MAKER_ASSET_DATA,
|
FAKE_MAKER_ASSET_DATA,
|
||||||
WETH_ASSET_DATA,
|
WETH_ASSET_DATA,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
);
|
);
|
||||||
const liquidityResult = await mockedSwapQuoter.object.getLiquidityForMakerTakerAssetDataPairAsync(
|
const liquidityResult = await mockedSwapQuoter.object.getLiquidityForMakerTakerAssetDataPairAsync(
|
||||||
FAKE_MAKER_ASSET_DATA,
|
FAKE_MAKER_ASSET_DATA,
|
||||||
@ -130,8 +126,8 @@ describe('SwapQuoter', () => {
|
|||||||
FAKE_TAKER_ASSET_DATA,
|
FAKE_TAKER_ASSET_DATA,
|
||||||
);
|
);
|
||||||
expect(liquidityResult).to.deep.equal({
|
expect(liquidityResult).to.deep.equal({
|
||||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
makerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
takerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,20 +140,15 @@ describe('SwapQuoter', () => {
|
|||||||
FAKE_TAKER_ASSET_DATA,
|
FAKE_TAKER_ASSET_DATA,
|
||||||
);
|
);
|
||||||
expect(liquidityResult).to.deep.equal({
|
expect(liquidityResult).to.deep.equal({
|
||||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
makerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
takerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('assetData is supported', () => {
|
describe('assetData is supported', () => {
|
||||||
// orders
|
// orders
|
||||||
const sellTwoTokensFor1Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
const sellTenTokensFor10Weth: SignedOrder = testOrderFactory.generateTestSignedOrder({
|
||||||
makerAssetAmount: baseUnitAmount(2),
|
|
||||||
takerAssetAmount: baseUnitAmount(1, WETH_DECIMALS),
|
|
||||||
chainId: 42,
|
|
||||||
});
|
|
||||||
const sellTenTokensFor10Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
|
||||||
makerAssetAmount: baseUnitAmount(10),
|
makerAssetAmount: baseUnitAmount(10),
|
||||||
takerAssetAmount: baseUnitAmount(10, WETH_DECIMALS),
|
takerAssetAmount: baseUnitAmount(10, WETH_DECIMALS),
|
||||||
chainId: 42,
|
chainId: 42,
|
||||||
@ -168,98 +159,145 @@ describe('SwapQuoter', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return 0s when no orders available', async () => {
|
it('should return 0s when no orders available', async () => {
|
||||||
const ordersAndFillableAmounts: OrdersAndFillableAmounts = {
|
const prunedOrders: PrunedSignedOrder[] = [];
|
||||||
orders: [],
|
|
||||||
remainingFillableMakerAssetAmounts: [],
|
|
||||||
};
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
makerTokensAvailableInBaseUnits: new BigNumber(0),
|
makerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
takerTokensAvailableInBaseUnits: new BigNumber(0),
|
takerAssetAvailableInBaseUnits: new BigNumber(0),
|
||||||
};
|
};
|
||||||
await expectLiquidityResult(
|
await expectLiquidityResult(
|
||||||
mockWeb3Provider.object,
|
mockWeb3Provider.object,
|
||||||
mockOrderbook.object,
|
mockOrderbook.object,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
expectedResult,
|
expectedResult,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct computed value when orders provided with full fillableAmounts', async () => {
|
it('should return correct computed value when orders provided with full fillableAmounts', async () => {
|
||||||
const orders: SignedOrder[] = [sellTwoTokensFor1Weth, sellTenTokensFor10Weth];
|
const prunedOrders: PrunedSignedOrder[] = [
|
||||||
const ordersAndFillableAmounts = {
|
{
|
||||||
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
|
...sellTenTokensFor10Weth,
|
||||||
remainingFillableMakerAssetAmounts: orders.map(o => o.makerAssetAmount),
|
...{
|
||||||
};
|
fillableMakerAssetAmount: sellTenTokensFor10Weth.makerAssetAmount,
|
||||||
|
fillableTakerAssetAmount: sellTenTokensFor10Weth.takerAssetAmount,
|
||||||
const expectedMakerTokensAvailable = orders[0].makerAssetAmount.plus(orders[1].makerAssetAmount);
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
const expectedTakerTokensAvailable = orders[0].takerAssetAmount.plus(orders[1].takerAssetAmount);
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...sellTenTokensFor10Weth,
|
||||||
|
...{
|
||||||
|
fillableMakerAssetAmount: sellTenTokensFor10Weth.makerAssetAmount,
|
||||||
|
fillableTakerAssetAmount: sellTenTokensFor10Weth.takerAssetAmount,
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const expectedMakerAssetAvailable = prunedOrders[0].makerAssetAmount.plus(
|
||||||
|
prunedOrders[1].makerAssetAmount,
|
||||||
|
);
|
||||||
|
const expectedTakerAssetAvailable = prunedOrders[0].takerAssetAmount.plus(
|
||||||
|
prunedOrders[1].takerAssetAmount,
|
||||||
|
);
|
||||||
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
makerTokensAvailableInBaseUnits: expectedMakerTokensAvailable,
|
makerAssetAvailableInBaseUnits: expectedMakerAssetAvailable,
|
||||||
takerTokensAvailableInBaseUnits: expectedTakerTokensAvailable,
|
takerAssetAvailableInBaseUnits: expectedTakerAssetAvailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
await expectLiquidityResult(
|
await expectLiquidityResult(
|
||||||
mockWeb3Provider.object,
|
mockWeb3Provider.object,
|
||||||
mockOrderbook.object,
|
mockOrderbook.object,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
expectedResult,
|
expectedResult,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct computed value with one partial fillableAmounts', async () => {
|
it('should return correct computed value with one partial fillableAmounts', async () => {
|
||||||
const ordersAndFillableAmounts = {
|
const prunedOrders: PrunedSignedOrder[] = [
|
||||||
orders: [sellTwoTokensFor1Weth],
|
{
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(1)],
|
...sellTenTokensFor10Weth,
|
||||||
};
|
...{
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(1),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(0.5, WETH_DECIMALS),
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
makerTokensAvailableInBaseUnits: baseUnitAmount(1),
|
makerAssetAvailableInBaseUnits: baseUnitAmount(1),
|
||||||
takerTokensAvailableInBaseUnits: baseUnitAmount(0.5, WETH_DECIMALS),
|
takerAssetAvailableInBaseUnits: baseUnitAmount(0.5, WETH_DECIMALS),
|
||||||
};
|
};
|
||||||
|
|
||||||
await expectLiquidityResult(
|
await expectLiquidityResult(
|
||||||
mockWeb3Provider.object,
|
mockWeb3Provider.object,
|
||||||
mockOrderbook.object,
|
mockOrderbook.object,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
expectedResult,
|
expectedResult,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct computed value with multiple orders and fillable amounts', async () => {
|
it('should return correct computed value with multiple orders and fillable amounts', async () => {
|
||||||
const ordersAndFillableAmounts = {
|
const prunedOrders: PrunedSignedOrder[] = [
|
||||||
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
|
{
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(3)],
|
...sellTenTokensFor10Weth,
|
||||||
};
|
...{
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(1),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(0.5, WETH_DECIMALS),
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...sellTenTokensFor10Weth,
|
||||||
|
...{
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(3),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(3, WETH_DECIMALS),
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
makerTokensAvailableInBaseUnits: baseUnitAmount(4),
|
makerAssetAvailableInBaseUnits: baseUnitAmount(4),
|
||||||
takerTokensAvailableInBaseUnits: baseUnitAmount(3.5, WETH_DECIMALS),
|
takerAssetAvailableInBaseUnits: baseUnitAmount(3.5, WETH_DECIMALS),
|
||||||
};
|
};
|
||||||
|
|
||||||
await expectLiquidityResult(
|
await expectLiquidityResult(
|
||||||
mockWeb3Provider.object,
|
mockWeb3Provider.object,
|
||||||
mockOrderbook.object,
|
mockOrderbook.object,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
expectedResult,
|
expectedResult,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 0s when no amounts fillable', async () => {
|
it('should return 0s when no amounts fillable', async () => {
|
||||||
const ordersAndFillableAmounts = {
|
const prunedOrders: PrunedSignedOrder[] = [
|
||||||
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
|
{
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(0)],
|
...sellTenTokensFor10Weth,
|
||||||
};
|
...{
|
||||||
|
fillableMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...sellTenTokensFor10Weth,
|
||||||
|
...{
|
||||||
|
fillableMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const expectedResult = {
|
const expectedResult = {
|
||||||
makerTokensAvailableInBaseUnits: baseUnitAmount(0),
|
makerAssetAvailableInBaseUnits: constants.ZERO_AMOUNT,
|
||||||
takerTokensAvailableInBaseUnits: baseUnitAmount(0, WETH_DECIMALS),
|
takerAssetAvailableInBaseUnits: constants.ZERO_AMOUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
await expectLiquidityResult(
|
await expectLiquidityResult(
|
||||||
mockWeb3Provider.object,
|
mockWeb3Provider.object,
|
||||||
mockOrderbook.object,
|
mockOrderbook.object,
|
||||||
ordersAndFillableAmounts,
|
prunedOrders,
|
||||||
expectedResult,
|
expectedResult,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
0
packages/asset-swapper/test/utils/consumer_utils.ts
Normal file
0
packages/asset-swapper/test/utils/consumer_utils.ts
Normal file
@ -4,7 +4,7 @@ import { APIOrder, AssetPairsItem, SignedOrder } from '@0x/types';
|
|||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
import { SwapQuoter } from '../../src/swap_quoter';
|
import { SwapQuoter } from '../../src/swap_quoter';
|
||||||
import { OrdersAndFillableAmounts } from '../../src/types';
|
import { PrunedSignedOrder } from '../../src/types';
|
||||||
|
|
||||||
class OrderbookClass extends Orderbook {
|
class OrderbookClass extends Orderbook {
|
||||||
// tslint:disable-next-line:prefer-function-over-method
|
// tslint:disable-next-line:prefer-function-over-method
|
||||||
@ -49,26 +49,26 @@ const partiallyMockedSwapQuoter = (provider: Web3ProviderEngine, orderbook: Orde
|
|||||||
return mockedSwapQuoter;
|
return mockedSwapQuoter;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockGetOrdersAndAvailableAmounts = (
|
const mockGetPrunedSignedOrdersAsync = (
|
||||||
mockedSwapQuoter: TypeMoq.IMock<SwapQuoter>,
|
mockedSwapQuoter: TypeMoq.IMock<SwapQuoter>,
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
): void => {
|
): void => {
|
||||||
mockedSwapQuoter
|
mockedSwapQuoter
|
||||||
.setup(async a => a.getOrdersAndFillableAmountsAsync(makerAssetData, takerAssetData))
|
.setup(async a => a.getPrunedSignedOrdersAsync(makerAssetData, takerAssetData))
|
||||||
.returns(async () => Promise.resolve(ordersAndFillableAmounts))
|
.returns(async () => Promise.resolve(prunedOrders))
|
||||||
.verifiable(TypeMoq.Times.once());
|
.verifiable(TypeMoq.Times.once());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockedSwapQuoterWithOrdersAndFillableAmounts = (
|
export const mockedSwapQuoterWithPrunedSignedOrders = (
|
||||||
provider: Web3ProviderEngine,
|
provider: Web3ProviderEngine,
|
||||||
orderbook: Orderbook,
|
orderbook: Orderbook,
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
ordersAndFillableAmounts: OrdersAndFillableAmounts,
|
prunedOrders: PrunedSignedOrder[],
|
||||||
): TypeMoq.IMock<SwapQuoter> => {
|
): TypeMoq.IMock<SwapQuoter> => {
|
||||||
const mockedAssetQuoter = partiallyMockedSwapQuoter(provider, orderbook);
|
const mockedAssetQuoter = partiallyMockedSwapQuoter(provider, orderbook);
|
||||||
mockGetOrdersAndAvailableAmounts(mockedAssetQuoter, makerAssetData, takerAssetData, ordersAndFillableAmounts);
|
mockGetPrunedSignedOrdersAsync(mockedAssetQuoter, makerAssetData, takerAssetData, prunedOrders);
|
||||||
return mockedAssetQuoter;
|
return mockedAssetQuoter;
|
||||||
};
|
};
|
||||||
|
@ -1,134 +1,40 @@
|
|||||||
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
import { SignedOrder } from '@0x/types';
|
||||||
import { MarketOperation, SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { SupportedProvider } from '@0x/web3-wrapper';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants } from '../../src/constants';
|
import { constants } from '../../src/constants';
|
||||||
import { SwapQuote } from '../../src/types';
|
import { MarketOperation, PrunedSignedOrder, SwapQuote } from '../../src/types';
|
||||||
|
import { protocolFeeUtils } from '../../src/utils/protocol_fee_utils';
|
||||||
const ZERO_BIG_NUMBER = new BigNumber(0);
|
|
||||||
|
|
||||||
export const getSignedOrdersWithNoFeesAsync = async (
|
|
||||||
provider: SupportedProvider,
|
|
||||||
makerAssetData: string,
|
|
||||||
takerAssetData: string,
|
|
||||||
makerAddress: string,
|
|
||||||
takerAddress: string,
|
|
||||||
fillableAmounts: BigNumber[],
|
|
||||||
exchangeAddress?: string,
|
|
||||||
): Promise<SignedOrder[]> => {
|
|
||||||
const promises = _.map(fillableAmounts, async (fillableAmount: BigNumber) =>
|
|
||||||
orderFactory.createSignedOrderAsync(
|
|
||||||
provider,
|
|
||||||
makerAddress,
|
|
||||||
fillableAmount,
|
|
||||||
makerAssetData,
|
|
||||||
fillableAmount,
|
|
||||||
takerAssetData,
|
|
||||||
exchangeAddress || constants.NULL_ADDRESS,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return Promise.all(promises);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPartialSignedOrdersWithNoFees = (
|
|
||||||
makerAssetData: string,
|
|
||||||
takerAssetData: string,
|
|
||||||
makerAddress: string,
|
|
||||||
takerAddress: string,
|
|
||||||
fillableAmounts: BigNumber[],
|
|
||||||
): SignedOrder[] => {
|
|
||||||
return _.map(fillableAmounts, (fillableAmount: BigNumber) =>
|
|
||||||
orderFactory.createSignedOrderFromPartial({
|
|
||||||
makerAddress,
|
|
||||||
makerAssetAmount: fillableAmount,
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetAmount: fillableAmount,
|
|
||||||
takerAssetData,
|
|
||||||
chainId: 42,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPartialSignedOrdersWithFees = (
|
|
||||||
makerAssetData: string,
|
|
||||||
takerAssetData: string,
|
|
||||||
makerAddress: string,
|
|
||||||
takerAddress: string,
|
|
||||||
fillableAmounts: BigNumber[],
|
|
||||||
takerFees: BigNumber[],
|
|
||||||
): SignedOrder[] => {
|
|
||||||
const orders = getPartialSignedOrdersWithNoFees(
|
|
||||||
makerAssetData,
|
|
||||||
takerAssetData,
|
|
||||||
makerAddress,
|
|
||||||
takerAddress,
|
|
||||||
fillableAmounts,
|
|
||||||
);
|
|
||||||
return _.map(orders, (order: SignedOrder, index: number) =>
|
|
||||||
orderFactory.createSignedOrderFromPartial({
|
|
||||||
...order,
|
|
||||||
...{ takerFee: takerFees[index] },
|
|
||||||
chainId: 42,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFullyFillableSwapQuoteWithFees = (
|
|
||||||
makerAssetData: string,
|
|
||||||
takerAssetData: string,
|
|
||||||
orders: SignedOrder[],
|
|
||||||
feeOrders: SignedOrder[],
|
|
||||||
operation: MarketOperation,
|
|
||||||
) => {
|
|
||||||
const swapQuote = getFullyFillableSwapQuoteWithNoFees(makerAssetData, takerAssetData, orders, operation);
|
|
||||||
swapQuote.feeOrders = feeOrders;
|
|
||||||
const totalFeeTakerTokenAmount = _.reduce(
|
|
||||||
feeOrders,
|
|
||||||
(a: BigNumber, c: SignedOrder) => a.plus(c.takerAssetAmount),
|
|
||||||
ZERO_BIG_NUMBER,
|
|
||||||
);
|
|
||||||
// Adds fees to the SwapQuoteInfos assuming all feeOrders will be filled
|
|
||||||
swapQuote.bestCaseQuoteInfo.feeTakerTokenAmount = totalFeeTakerTokenAmount;
|
|
||||||
swapQuote.worstCaseQuoteInfo.feeTakerTokenAmount = totalFeeTakerTokenAmount;
|
|
||||||
swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount = swapQuote.bestCaseQuoteInfo.totalTakerTokenAmount.plus(
|
|
||||||
totalFeeTakerTokenAmount,
|
|
||||||
);
|
|
||||||
swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount = swapQuote.worstCaseQuoteInfo.totalTakerTokenAmount.plus(
|
|
||||||
totalFeeTakerTokenAmount,
|
|
||||||
);
|
|
||||||
return swapQuote;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFullyFillableSwapQuoteWithNoFees = (
|
export const getFullyFillableSwapQuoteWithNoFees = (
|
||||||
makerAssetData: string,
|
makerAssetData: string,
|
||||||
takerAssetData: string,
|
takerAssetData: string,
|
||||||
orders: SignedOrder[],
|
orders: PrunedSignedOrder[],
|
||||||
operation: MarketOperation,
|
operation: MarketOperation,
|
||||||
|
gasPrice: BigNumber,
|
||||||
): SwapQuote => {
|
): SwapQuote => {
|
||||||
const makerAssetFillAmount = _.reduce(
|
const makerAssetFillAmount = _.reduce(
|
||||||
orders,
|
orders,
|
||||||
(a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount),
|
(a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount),
|
||||||
ZERO_BIG_NUMBER,
|
constants.ZERO_AMOUNT,
|
||||||
);
|
);
|
||||||
const totalTakerTokenAmount = _.reduce(
|
const totalTakerAssetAmount = _.reduce(
|
||||||
orders,
|
orders,
|
||||||
(a: BigNumber, c: SignedOrder) => a.plus(c.takerAssetAmount),
|
(a: BigNumber, c: SignedOrder) => a.plus(c.takerAssetAmount),
|
||||||
ZERO_BIG_NUMBER,
|
constants.ZERO_AMOUNT,
|
||||||
);
|
);
|
||||||
const quoteInfo = {
|
const quoteInfo = {
|
||||||
makerTokenAmount: makerAssetFillAmount,
|
makerAssetAmount: makerAssetFillAmount,
|
||||||
takerTokenAmount: totalTakerTokenAmount,
|
feeTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
feeTakerTokenAmount: ZERO_BIG_NUMBER,
|
takerAssetAmount: totalTakerAssetAmount,
|
||||||
totalTakerTokenAmount,
|
totalTakerAssetAmount,
|
||||||
|
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(orders, gasPrice),
|
||||||
};
|
};
|
||||||
|
|
||||||
const quoteBase = {
|
const quoteBase = {
|
||||||
makerAssetData,
|
makerAssetData,
|
||||||
takerAssetData,
|
takerAssetData,
|
||||||
orders,
|
orders,
|
||||||
feeOrders: [],
|
|
||||||
bestCaseQuoteInfo: quoteInfo,
|
bestCaseQuoteInfo: quoteInfo,
|
||||||
worstCaseQuoteInfo: quoteInfo,
|
worstCaseQuoteInfo: quoteInfo,
|
||||||
};
|
};
|
||||||
@ -143,7 +49,7 @@ export const getFullyFillableSwapQuoteWithNoFees = (
|
|||||||
return {
|
return {
|
||||||
...quoteBase,
|
...quoteBase,
|
||||||
type: MarketOperation.Sell,
|
type: MarketOperation.Sell,
|
||||||
takerAssetFillAmount: totalTakerTokenAmount,
|
takerAssetFillAmount: totalTakerAssetAmount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
63
packages/asset-swapper/test/utils/test_order_factory.ts
Normal file
63
packages/asset-swapper/test/utils/test_order_factory.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
||||||
|
import { Order, SignedOrder } from '@0x/types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { constants } from '../../src/constants';
|
||||||
|
import { PrunedSignedOrder } from '../../src/types';
|
||||||
|
|
||||||
|
const CHAIN_ID = 1337;
|
||||||
|
const BASE_TEST_ORDER: Order = orderFactory.createOrder(
|
||||||
|
constants.NULL_ADDRESS,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
constants.NULL_ERC20_ASSET_DATA,
|
||||||
|
constants.NULL_ADDRESS,
|
||||||
|
CHAIN_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const BASE_TEST_SIGNED_ORDER: SignedOrder = {
|
||||||
|
...BASE_TEST_ORDER,
|
||||||
|
signature: constants.NULL_BYTES,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_TEST_PRUNED_SIGNED_ORDER: PrunedSignedOrder = {
|
||||||
|
...BASE_TEST_SIGNED_ORDER,
|
||||||
|
fillableMakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerAssetAmount: constants.ZERO_AMOUNT,
|
||||||
|
fillableTakerFeeAmount: constants.ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testOrderFactory = {
|
||||||
|
generateTestSignedOrder(partialOrder: Partial<SignedOrder>): SignedOrder {
|
||||||
|
return transformObject(BASE_TEST_SIGNED_ORDER, partialOrder);
|
||||||
|
},
|
||||||
|
generateIdenticalTestSignedOrders(partialOrder: Partial<SignedOrder>, numOrders: number): SignedOrder[] {
|
||||||
|
const baseTestOrders = _.map(_.range(numOrders), () => BASE_TEST_SIGNED_ORDER);
|
||||||
|
return _.map(baseTestOrders, order => transformObject(order, partialOrder));
|
||||||
|
},
|
||||||
|
generateTestSignedOrders(partialOrders: Array<Partial<SignedOrder>>): SignedOrder[] {
|
||||||
|
return _.map(partialOrders, partialOrder => transformObject(BASE_TEST_SIGNED_ORDER, partialOrder));
|
||||||
|
},
|
||||||
|
generateTestPrunedSignedOrder(partialOrder: Partial<PrunedSignedOrder>): PrunedSignedOrder {
|
||||||
|
return transformObject(BASE_TEST_PRUNED_SIGNED_ORDER, partialOrder);
|
||||||
|
},
|
||||||
|
generateIdenticalTestPrunedSignedOrders(
|
||||||
|
partialOrder: Partial<PrunedSignedOrder>,
|
||||||
|
numOrders: number,
|
||||||
|
): PrunedSignedOrder[] {
|
||||||
|
const baseTestOrders = _.map(_.range(numOrders), () => BASE_TEST_PRUNED_SIGNED_ORDER);
|
||||||
|
return _.map(baseTestOrders, (baseOrder): PrunedSignedOrder => transformObject(baseOrder, partialOrder));
|
||||||
|
},
|
||||||
|
generateTestPrunedSignedOrders(partialOrders: Array<Partial<PrunedSignedOrder>>): PrunedSignedOrder[] {
|
||||||
|
return _.map(
|
||||||
|
partialOrders,
|
||||||
|
(partialOrder): PrunedSignedOrder => transformObject(BASE_TEST_PRUNED_SIGNED_ORDER, partialOrder),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformObject<T>(input: T, transformation: Partial<T>): T {
|
||||||
|
const copy = _.cloneDeep(input);
|
||||||
|
return _.assign(copy, transformation);
|
||||||
|
}
|
146
packages/asset-swapper/test/utils/test_orders.ts
Normal file
146
packages/asset-swapper/test/utils/test_orders.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { PrunedSignedOrder } from '../../src/types';
|
||||||
|
|
||||||
|
import { testOrderFactory } from './test_order_factory';
|
||||||
|
import { baseUnitAmount } from './utils';
|
||||||
|
|
||||||
|
// tslint:disable:custom-no-magic-numbers
|
||||||
|
|
||||||
|
const FAKE_ERC20_TAKER_ASSET_DATA = '0xf47261b22222222222222222222222222222222222222222222222222222222222222222';
|
||||||
|
const FAKE_ERC20_MAKER_ASSET_DATA = '0xf47261b11111111111111111111111111111111111111111111111111111111111111111';
|
||||||
|
|
||||||
|
const PARTIAL_ORDER: Partial<PrunedSignedOrder> = {
|
||||||
|
takerAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
makerAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PARTIAL_ORDER_FEE_IN_TAKER_ASSET: Partial<PrunedSignedOrder> = {
|
||||||
|
...{
|
||||||
|
takerFeeAssetData: FAKE_ERC20_TAKER_ASSET_DATA,
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PARTIAL_ORDER_FEE_IN_MAKER_ASSET: Partial<PrunedSignedOrder> = {
|
||||||
|
...{
|
||||||
|
takerFeeAssetData: FAKE_ERC20_MAKER_ASSET_DATA,
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(1),
|
||||||
|
makerAssetAmount: baseUnitAmount(6),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(1),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(6),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(10),
|
||||||
|
makerAssetAmount: baseUnitAmount(4),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(5),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(6),
|
||||||
|
makerAssetAmount: baseUnitAmount(6),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(3),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PARTIAL_PRUNED_SIGNED_FEE_IN_TAKER_ASSET: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(1),
|
||||||
|
makerAssetAmount: baseUnitAmount(6),
|
||||||
|
takerFee: baseUnitAmount(3),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(1),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(6),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(3),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_TAKER_ASSET,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(10),
|
||||||
|
makerAssetAmount: baseUnitAmount(4),
|
||||||
|
takerFee: baseUnitAmount(2),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(5),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(1),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_TAKER_ASSET,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(6),
|
||||||
|
makerAssetAmount: baseUnitAmount(6),
|
||||||
|
takerFee: baseUnitAmount(4),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(3),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(2),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_TAKER_ASSET,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PARTIAL_PRUNED_SIGNED_FEE_IN_MAKER_ASSET: Array<Partial<PrunedSignedOrder>> = [
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(5),
|
||||||
|
makerAssetAmount: baseUnitAmount(2),
|
||||||
|
takerFee: baseUnitAmount(1),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(5),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(2),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(1),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_MAKER_ASSET,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(2),
|
||||||
|
makerAssetAmount: baseUnitAmount(12),
|
||||||
|
takerFee: baseUnitAmount(6),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(1),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(6),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(3),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_MAKER_ASSET,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...{
|
||||||
|
takerAssetAmount: baseUnitAmount(3),
|
||||||
|
makerAssetAmount: baseUnitAmount(3),
|
||||||
|
takerFee: baseUnitAmount(2),
|
||||||
|
fillableTakerAssetAmount: baseUnitAmount(3),
|
||||||
|
fillableMakerAssetAmount: baseUnitAmount(3),
|
||||||
|
fillableTakerFeeAmount: baseUnitAmount(2),
|
||||||
|
},
|
||||||
|
...PARTIAL_ORDER_FEE_IN_MAKER_ASSET,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PRUNED_SIGNED_ORDERS_FEELESS = testOrderFactory.generateTestPrunedSignedOrders(
|
||||||
|
PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
);
|
||||||
|
const PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET = testOrderFactory.generateTestPrunedSignedOrders(
|
||||||
|
PARTIAL_PRUNED_SIGNED_FEE_IN_TAKER_ASSET,
|
||||||
|
);
|
||||||
|
const PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET = testOrderFactory.generateTestPrunedSignedOrders(
|
||||||
|
PARTIAL_PRUNED_SIGNED_FEE_IN_MAKER_ASSET,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const testOrders = {
|
||||||
|
PRUNED_SIGNED_ORDERS_FEELESS,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
|
||||||
|
PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
|
||||||
|
};
|
9
packages/asset-swapper/test/utils/utils.ts
Normal file
9
packages/asset-swapper/test/utils/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
|
|
||||||
|
const TOKEN_DECIMALS = 18;
|
||||||
|
|
||||||
|
// tslint:disable:custom-no-magic-numbers
|
||||||
|
export const baseUnitAmount = (unitAmount: number, decimals = TOKEN_DECIMALS): BigNumber => {
|
||||||
|
return Web3Wrapper.toBaseUnitAmount(new BigNumber(unitAmount), decimals);
|
||||||
|
};
|
@ -1,82 +0,0 @@
|
|||||||
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
|
||||||
import { SignedOrder } from '@0x/types';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
||||||
import * as chai from 'chai';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { constants } from '../src/constants';
|
|
||||||
import { utils } from '../src/utils/utils';
|
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
|
||||||
|
|
||||||
chaiSetup.configure();
|
|
||||||
const expect = chai.expect;
|
|
||||||
|
|
||||||
const TOKEN_DECIMALS = 18;
|
|
||||||
const WETH_DECIMALS = constants.ETHER_TOKEN_DECIMALS;
|
|
||||||
|
|
||||||
const baseUnitAmount = (unitAmount: number, decimals = TOKEN_DECIMALS): BigNumber => {
|
|
||||||
return Web3Wrapper.toBaseUnitAmount(new BigNumber(unitAmount), decimals);
|
|
||||||
};
|
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
|
||||||
describe('utils', () => {
|
|
||||||
// orders
|
|
||||||
const sellTwoTokensFor1Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
|
||||||
chainId: 42,
|
|
||||||
makerAssetAmount: baseUnitAmount(2),
|
|
||||||
takerAssetAmount: baseUnitAmount(1, WETH_DECIMALS),
|
|
||||||
});
|
|
||||||
const sellTenTokensFor10Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
|
||||||
chainId: 42,
|
|
||||||
makerAssetAmount: baseUnitAmount(10),
|
|
||||||
takerAssetAmount: baseUnitAmount(10, WETH_DECIMALS),
|
|
||||||
});
|
|
||||||
const sellTwoTokensFor1WethWithTwoTokenFee: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
|
||||||
chainId: 42,
|
|
||||||
makerAssetAmount: baseUnitAmount(2),
|
|
||||||
takerAssetAmount: baseUnitAmount(1, WETH_DECIMALS),
|
|
||||||
takerFee: baseUnitAmount(2),
|
|
||||||
});
|
|
||||||
const sellTenTokensFor1WethWithFourTokenFee: SignedOrder = orderFactory.createSignedOrderFromPartial({
|
|
||||||
chainId: 42,
|
|
||||||
makerAssetAmount: baseUnitAmount(2),
|
|
||||||
takerAssetAmount: baseUnitAmount(1, WETH_DECIMALS),
|
|
||||||
takerFee: baseUnitAmount(4),
|
|
||||||
});
|
|
||||||
describe('isFeeOrdersRequiredToFillOrders', async () => {
|
|
||||||
it('should return true if ordersAndFillableAmounts is completed unfilled and has fees', () => {
|
|
||||||
const ordersAndFillableAmounts = {
|
|
||||||
orders: [sellTwoTokensFor1WethWithTwoTokenFee, sellTenTokensFor1WethWithFourTokenFee],
|
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(10)],
|
|
||||||
};
|
|
||||||
const isFeeOrdersRequired = utils.isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts);
|
|
||||||
expect(isFeeOrdersRequired).to.equal(true);
|
|
||||||
});
|
|
||||||
it('should return true if ordersAndFillableAmounts is partially unfilled and has fees', () => {
|
|
||||||
const ordersAndFillableAmounts = {
|
|
||||||
orders: [sellTwoTokensFor1WethWithTwoTokenFee, sellTenTokensFor1WethWithFourTokenFee],
|
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(5)],
|
|
||||||
};
|
|
||||||
const isFeeOrdersRequired = utils.isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts);
|
|
||||||
expect(isFeeOrdersRequired).to.equal(true);
|
|
||||||
});
|
|
||||||
it('should return false if ordersAndFillableAmounts is completed filled and has fees', () => {
|
|
||||||
const ordersAndFillableAmounts = {
|
|
||||||
orders: [sellTwoTokensFor1WethWithTwoTokenFee, sellTenTokensFor1WethWithFourTokenFee],
|
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(0)],
|
|
||||||
};
|
|
||||||
const isFeeOrdersRequired = utils.isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts);
|
|
||||||
expect(isFeeOrdersRequired).to.equal(false);
|
|
||||||
});
|
|
||||||
it("should return false if ordersAndFillableAmounts is completely unfilled and doesn't have fees", () => {
|
|
||||||
const ordersAndFillableAmounts = {
|
|
||||||
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
|
|
||||||
remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(10)],
|
|
||||||
};
|
|
||||||
const isFeeOrdersRequired = utils.isFeeOrdersRequiredToFillOrders(ordersAndFillableAmounts);
|
|
||||||
expect(isFeeOrdersRequired).to.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Deploy Forwarder AFTER staking is hooked up",
|
"note": "Deploy Forwarder AFTER staking is hooked up",
|
||||||
"pr": "TODO"
|
"pr": 2350
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user