protocol/packages/asset-swapper/src/utils/swap_quote_consumer_utils.ts
Lawrence Forman 0571a96cea
Fix asset-swapper bugs and misc improvements. (#2406)
* `@0x/contracts-erc20-bridge-sampler`: Add gas limits to external quote calls.
`@0x/contract-addresses`: Point `erc20BridgeSampler` to new version.

* `@0x/asset-swapper`: Ignore zero sample results from the sampler contract.
`@0x/asset-swapper`: Allow skipping Uniswap when dealing with low precision amounts with `minUniswapDecimals` option.
`@0x/asset-swapper`: Increase default `runLimit` from `1024` to `4096`.
`@0x/asset-swapper`: Increase default `numSamples` from `8` to `10`
`@0x/asset-swapper`: Fix ordering of optimized orders.
`@0x/asset-swapper`: Fix best and worst quotes being reversed sometimes.
`@0x/asset-swapper`: Fix rounding of quoted asset amounts.

* `@0x/contracts-utils`: Add kovan addresses to `DeploymentConstants`.
`@0x/contract-addresses`: Add kovan `ERC20BridgeSampler` address.

* `@0x/asset-swapper`: Change default `minUniswapDecimals` option from 8 to 7.

* `@0x/contracts-erc20-bridge-sampler`: Fix changelog.

* `@0x/asset-swapper`: Revert uniswap decimals fix.

* `@0x/asset-swapper`: Undo bridge slippage when computing best case quote.

* `@0x/asset-swapper`: Take asset data from input orders instead of output orders in quote result calculation.

* `@0x/asset-swapper`: Move `SAMPLER_CONTRACT_GAS_LIMIT` constant to `market_operation_utils/constants`.

* Compare equivalent asset data

* Fix redundant zero check

* Update CHANGELOG

* Set fee amount in fillable amounts test

Co-authored-by: Jacob Evans <dekz@dekz.net>
2020-01-03 23:21:39 -05:00

107 lines
4.8 KiB
TypeScript

import { ContractAddresses } from '@0x/contract-addresses';
import { WETH9Contract } from '@0x/contract-wrappers';
import { assetDataUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from '../constants';
import {
ExtensionContractType,
GetExtensionContractTypeOpts,
SwapQuote,
SwapQuoteConsumerError,
SwapQuoteExecutionOpts,
} from '../types';
import { utils } from '../utils/utils';
import { assert } from './assert';
export const swapQuoteConsumerUtils = {
async getTakerAddressOrThrowAsync(
provider: SupportedProvider,
opts: Partial<SwapQuoteExecutionOpts>,
): Promise<string> {
const takerAddress = await swapQuoteConsumerUtils.getTakerAddressAsync(provider, opts);
if (takerAddress === undefined) {
throw new Error(SwapQuoteConsumerError.NoAddressAvailable);
} else {
return takerAddress;
}
},
async getTakerAddressAsync(
provider: SupportedProvider,
opts: Partial<SwapQuoteExecutionOpts>,
): Promise<string | undefined> {
if (opts.takerAddress !== undefined) {
return opts.takerAddress;
} else {
const web3Wrapper = new Web3Wrapper(provider);
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
const firstAvailableAddress = _.head(availableAddresses);
if (firstAvailableAddress !== undefined) {
return firstAvailableAddress;
} else {
return undefined;
}
}
},
async getEthAndWethBalanceAsync(
provider: SupportedProvider,
contractAddresses: ContractAddresses,
takerAddress: string,
): Promise<[BigNumber, BigNumber]> {
const weth = new WETH9Contract(contractAddresses.etherToken, provider);
const web3Wrapper = new Web3Wrapper(provider);
const ethBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
const wethBalance = await weth.balanceOf(takerAddress).callAsync();
return [ethBalance, wethBalance];
},
isValidForwarderSwapQuote(swapQuote: SwapQuote, wethAssetData: string): boolean {
return swapQuoteConsumerUtils.isValidForwarderSignedOrders(swapQuote.orders, wethAssetData);
},
isValidForwarderSignedOrders(orders: SignedOrder[], wethAssetData: string): boolean {
return _.every(orders, order => swapQuoteConsumerUtils.isValidForwarderSignedOrder(order, wethAssetData));
},
isValidForwarderSignedOrder(order: SignedOrder, wethAssetData: string): boolean {
return utils.isExactAssetData(order.takerAssetData, wethAssetData);
},
async getExtensionContractTypeForSwapQuoteAsync(
quote: SwapQuote,
contractAddresses: ContractAddresses,
provider: Provider,
opts: Partial<GetExtensionContractTypeOpts>,
): Promise<ExtensionContractType> {
const wethAssetData = assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken);
if (swapQuoteConsumerUtils.isValidForwarderSwapQuote(quote, wethAssetData)) {
if (opts.takerAddress !== undefined) {
assert.isETHAddressHex('takerAddress', opts.takerAddress);
}
const ethAmount =
opts.ethAmount ||
quote.worstCaseQuoteInfo.takerAssetAmount.plus(quote.worstCaseQuoteInfo.protocolFeeInWeiAmount);
const takerAddress = await swapQuoteConsumerUtils.getTakerAddressAsync(provider, opts);
const takerEthAndWethBalance =
takerAddress !== undefined
? await swapQuoteConsumerUtils.getEthAndWethBalanceAsync(provider, contractAddresses, takerAddress)
: [constants.ZERO_AMOUNT, constants.ZERO_AMOUNT];
// TODO(david): when considering if there is enough Eth balance, should account for gas costs.
const isEnoughEthAndWethBalance = _.map(takerEthAndWethBalance, (balance: BigNumber) =>
balance.isGreaterThanOrEqualTo(ethAmount),
);
if (isEnoughEthAndWethBalance[1]) {
// should be more gas efficient to use exchange consumer, so if possible use it.
return ExtensionContractType.None;
} else if (isEnoughEthAndWethBalance[0] && !isEnoughEthAndWethBalance[1]) {
return ExtensionContractType.Forwarder;
}
// Note: defaulting to forwarderConsumer if takerAddress is null or not enough balance of either wEth or Eth
return ExtensionContractType.Forwarder;
} else {
return ExtensionContractType.None;
}
},
};