Merge pull request #2491 from 0xProject/fix/instant/support-erc721

[FIX] Instant + Asset-swapper support for ERC721
This commit is contained in:
David Sun
2020-03-01 14:57:18 -05:00
committed by GitHub
7 changed files with 93 additions and 14 deletions

View File

@@ -1,4 +1,17 @@
[
{
"version": "4.4.0",
"changes": [
{
"note": "Add support for ERC721 assets",
"pr": 2491
},
{
"note": "Add destroy for gas heartbeat",
"pr": 2492
}
]
},
{
"version": "4.3.2",
"changes": [

View File

@@ -234,6 +234,7 @@ export enum SwapQuoterError {
InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY',
AssetUnavailable = 'ASSET_UNAVAILABLE',
NoGasPriceProvidedOrEstimated = 'NO_GAS_PRICE_PROVIDED_OR_ESTIMATED',
AssetDataUnsupported = 'ASSET_DATA_UNSUPPORTED',
}
/**

View File

@@ -1,5 +1,6 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';
import { constants } from '../../constants';
@@ -20,6 +21,22 @@ const { INFINITE_TIMESTAMP_SEC, WALLET_SIGNATURE } = marketOperationUtilConstant
export class CreateOrderUtils {
private readonly _contractAddress: ContractAddresses;
// utility function for asset-swapper to ignore market operation utils for specific asset types
public static convertNativeOrderToFullyFillableOptimizedOrders(order: SignedOrder): OptimizedMarketOrder {
return {
...order,
fillableMakerAssetAmount: order.makerAssetAmount,
fillableTakerAssetAmount: order.takerAssetAmount,
fillableTakerFeeAmount: order.takerFee,
fill: {
source: ERC20BridgeSource.Native,
totalMakerAssetAmount: order.makerAssetAmount,
totalTakerAssetAmount: order.takerAssetAmount,
subFills: [],
},
};
}
constructor(contractAddress: ContractAddresses) {
this._contractAddress = contractAddress;
}

View File

@@ -1,5 +1,5 @@
import { orderCalculationUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { assetDataUtils, orderCalculationUtils } from '@0x/order-utils';
import { AssetProxyId, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@@ -14,10 +14,12 @@ import {
SwapQuoteBase,
SwapQuoteInfo,
SwapQuoteOrdersBreakdown,
SwapQuoterError,
} from '../types';
import { fillableAmountsUtils } from './fillable_amounts_utils';
import { MarketOperationUtils } from './market_operation_utils';
import { CreateOrderUtils } from './market_operation_utils/create_order';
import { ERC20BridgeSource, OptimizedMarketOrder } from './market_operation_utils/types';
import { ProtocolFeeUtils } from './protocol_fee_utils';
import { utils } from './utils';
@@ -126,6 +128,10 @@ export class SwapQuoteCalculator {
operation: MarketOperation,
opts: CalculateSwapQuoteOpts,
): Promise<SwapQuote> {
// checks if maker asset is ERC721 or ERC20 and taker asset is ERC20
if (!utils.isSupportedAssetDataInOrders(prunedOrders)) {
throw Error(SwapQuoterError.AssetDataUnsupported);
}
// since prunedOrders do not have fillState, we will add a buffer of fillable orders to consider that some native are orders are partially filled
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
@@ -137,18 +143,30 @@ export class SwapQuoteCalculator {
...opts,
fees: _.mapValues(opts.fees, (v, k) => v.times(gasPrice)),
};
if (operation === MarketOperation.Buy) {
resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync(
prunedOrders,
assetFillAmount.plus(slippageBufferAmount),
_opts,
const firstOrderMakerAssetData = !!prunedOrders[0]
? assetDataUtils.decodeAssetDataOrThrow(prunedOrders[0].makerAssetData)
: { assetProxyId: '' };
if (firstOrderMakerAssetData.assetProxyId === AssetProxyId.ERC721) {
// HACK: to conform ERC721 orders to the output of market operation utils, assumes complete fillable
resultOrders = prunedOrders.map(o =>
CreateOrderUtils.convertNativeOrderToFullyFillableOptimizedOrders(o),
);
} else {
resultOrders = await this._marketOperationUtils.getMarketSellOrdersAsync(
prunedOrders,
assetFillAmount.plus(slippageBufferAmount),
_opts,
);
if (operation === MarketOperation.Buy) {
resultOrders = await this._marketOperationUtils.getMarketBuyOrdersAsync(
prunedOrders,
assetFillAmount.plus(slippageBufferAmount),
_opts,
);
} else {
resultOrders = await this._marketOperationUtils.getMarketSellOrdersAsync(
prunedOrders,
assetFillAmount.plus(slippageBufferAmount),
_opts,
);
}
}
}

View File

@@ -1,5 +1,5 @@
import { assetDataUtils } from '@0x/order-utils';
import { AssetData, ERC20AssetData, ERC20BridgeAssetData, Order } from '@0x/types';
import { AssetData, AssetProxyId, ERC20AssetData, ERC20BridgeAssetData, Order, SignedOrder } from '@0x/types';
import { BigNumber, NULL_BYTES } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@@ -7,6 +7,21 @@ import { constants } from '../constants';
// tslint:disable:no-unnecessary-type-assertion
export const utils = {
isSupportedAssetDataInOrders(orders: SignedOrder[]): boolean {
const firstOrderMakerAssetData = !!orders[0]
? assetDataUtils.decodeAssetDataOrThrow(orders[0].makerAssetData)
: { assetProxyId: '' };
return orders.every(o => {
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(o.takerAssetData);
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(o.makerAssetData);
return (
(makerAssetData.assetProxyId === AssetProxyId.ERC20 ||
makerAssetData.assetProxyId === AssetProxyId.ERC721) &&
takerAssetData.assetProxyId === AssetProxyId.ERC20 &&
firstOrderMakerAssetData.assetProxyId === makerAssetData.assetProxyId
); // checks that all native order maker assets are of the same type
});
},
numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber {
return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).multipliedBy(
percentage,

View File

@@ -1,4 +1,17 @@
[
{
"version": "4.2.0",
"changes": [
{
"note": "Clean up heartbeat functions on close",
"pr": 2492
},
{
"note": "Fix ERC721 asset support",
"pr": 2491
}
]
},
{
"version": "4.1.0",
"changes": [

View File

@@ -72,7 +72,9 @@ export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}>
overflow="hidden"
borderRadius="50%"
>
<Image src={asset.metaData.imageUrl} height="100%" objectFit="cover" />
<Flex justify="center" align="center" height="100%">
<Image src={asset.metaData.imageUrl} height="100%" objectFit="cover" />
</Flex>
</Container>
</Flex>
</Container>