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

108 lines
5.3 KiB
TypeScript

import { assetDataUtils } from '@0x/order-utils';
import { AssetData, ERC20AssetData, ERC20BridgeAssetData, Order } from '@0x/types';
import { BigNumber, NULL_BYTES } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from '../constants';
// tslint:disable:no-unnecessary-type-assertion
export const utils = {
numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber {
return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).multipliedBy(
percentage,
);
},
getMethodAbiFromContractAbi(abi: ContractAbi, name: string): MethodAbi | undefined {
return _.find(
abi,
(def: AbiDefinition): boolean => {
if (def.type === 'function') {
const methodDef = def as MethodAbi;
return methodDef.name === name;
} else {
return false;
}
},
) as MethodAbi | undefined;
},
isOrderTakerFeePayableWithMakerAsset<T extends Order>(order: T): boolean {
return !order.takerFee.isZero() && utils.isAssetDataEquivalent(order.takerFeeAssetData, order.makerAssetData);
},
isOrderTakerFeePayableWithTakerAsset<T extends Order>(order: T): boolean {
return !order.takerFee.isZero() && utils.isAssetDataEquivalent(order.takerFeeAssetData, order.takerAssetData);
},
getAdjustedMakerAndTakerAmountsFromTakerFees<T extends Order>(order: T): [BigNumber, BigNumber] {
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];
},
isExactAssetData(expectedAssetData: string, actualAssetData: string): boolean {
return expectedAssetData === actualAssetData;
},
/**
* Compare the Asset Data for equivalency. Expected is the asset data the user provided (wanted),
* actual is the asset data found or created.
*/
isAssetDataEquivalent(expectedAssetData: string, actualAssetData: string): boolean {
if (utils.isExactAssetData(expectedAssetData, actualAssetData)) {
return true;
}
const decodedExpectedAssetData = assetDataUtils.decodeAssetDataOrThrow(expectedAssetData);
const decodedActualAssetData = assetDataUtils.decodeAssetDataOrThrow(actualAssetData);
// ERC20 === ERC20, ERC20 === ERC20Bridge
if (
utils.isERC20EquivalentAssetData(decodedExpectedAssetData) &&
utils.isERC20EquivalentAssetData(decodedActualAssetData)
) {
const doesTokenAddressMatch = decodedExpectedAssetData.tokenAddress === decodedActualAssetData.tokenAddress;
return doesTokenAddressMatch;
}
// ERC1155 === ERC1155
if (
assetDataUtils.isERC1155TokenAssetData(decodedExpectedAssetData) &&
assetDataUtils.isERC1155TokenAssetData(decodedActualAssetData)
) {
const doesTokenAddressMatch = decodedExpectedAssetData.tokenAddress === decodedActualAssetData.tokenAddress;
// IDs may be out of order yet still equivalent
// i.e (["a", "b"], [1,2]) === (["b", "a"], [2, 1])
// (["a", "b"], [2,1]) !== (["b", "a"], [2, 1])
const hasAllIds = decodedExpectedAssetData.tokenIds.every(
id => decodedActualAssetData.tokenIds.findIndex(v => id.eq(v)) !== -1,
);
const hasAllValues = decodedExpectedAssetData.tokenIds.every((id, i) =>
decodedExpectedAssetData.tokenValues[i].eq(
decodedActualAssetData.tokenValues[decodedActualAssetData.tokenIds.findIndex(v => id.eq(v))],
),
);
// If expected contains callback data, ensure it is present
// if actual has callbackdata and expected provided none then ignore it
const hasEquivalentCallback =
decodedExpectedAssetData.callbackData === NULL_BYTES ||
decodedExpectedAssetData.callbackData === decodedActualAssetData.callbackData;
return doesTokenAddressMatch && hasAllIds && hasAllValues && hasEquivalentCallback;
}
// ERC721 === ERC721
if (
assetDataUtils.isERC721TokenAssetData(decodedExpectedAssetData) ||
assetDataUtils.isERC721TokenAssetData(decodedActualAssetData)
) {
// Asset Data should exactly match for ERC721
return utils.isExactAssetData(expectedAssetData, actualAssetData);
}
// TODO(dekz): Unsupported cases
// ERCXX(token) === MAP(token, staticCall)
// MAP(a, b) === MAP(b, a) === MAP(b, a, staticCall)
return false;
},
isERC20EquivalentAssetData(assetData: AssetData): assetData is ERC20AssetData | ERC20BridgeAssetData {
return assetDataUtils.isERC20TokenAssetData(assetData) || assetDataUtils.isERC20BridgeAssetData(assetData);
},
};