add forwarder logic for market sell
This commit is contained in:
parent
288a7d4cea
commit
64a0080616
@ -8,8 +8,11 @@ import { constants } from '../constants';
|
||||
import {
|
||||
CalldataInfo,
|
||||
ForwarderMarketBuySmartContractParams,
|
||||
ForwarderMarketSellSmartContractParams,
|
||||
ForwarderSwapQuoteExecutionOpts,
|
||||
ForwarderSwapQuoteGetOutputOpts,
|
||||
MarketBuySwapQuote,
|
||||
MarketSellSwapQuote,
|
||||
SmartContractParamsInfo,
|
||||
SwapQuote,
|
||||
SwapQuoteConsumer,
|
||||
@ -22,7 +25,8 @@ import { assetDataUtils } from '../utils/asset_data_utils';
|
||||
import { swapQuoteConsumerUtils } from '../utils/swap_quote_consumer_utils';
|
||||
import { utils } from '../utils/utils';
|
||||
|
||||
export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMarketBuySmartContractParams> {
|
||||
export class ForwarderSwapQuoteConsumer
|
||||
implements SwapQuoteConsumer<ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams> {
|
||||
public readonly provider: ZeroExProvider;
|
||||
public readonly networkId: number;
|
||||
|
||||
@ -51,17 +55,35 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
): Promise<CalldataInfo> {
|
||||
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
|
||||
|
||||
const { params, to, ethAmount, methodAbi } = await this.getSmartContractParamsOrThrowAsync(quote, opts);
|
||||
const consumableQuote = (quote as any) as (MarketBuySwapQuote | MarketSellSwapQuote);
|
||||
const smartContractParamsInfo = await this.getSmartContractParamsOrThrowAsync(consumableQuote, opts);
|
||||
const { to, methodAbi, ethAmount } = smartContractParamsInfo;
|
||||
|
||||
const abiEncoder = new AbiEncoder.Method(methodAbi);
|
||||
const args = [
|
||||
params.orders,
|
||||
params.makerAssetFillAmount,
|
||||
params.signatures,
|
||||
params.feeOrders,
|
||||
params.feeSignatures,
|
||||
params.feePercentage,
|
||||
params.feeRecipient,
|
||||
|
||||
let args: any[];
|
||||
if (utils.isSwapQuoteMarketBuy(consumableQuote)) {
|
||||
const marketBuyParams = (smartContractParamsInfo.params as any) as ForwarderMarketBuySmartContractParams;
|
||||
args = [
|
||||
marketBuyParams.orders,
|
||||
marketBuyParams.makerAssetFillAmount,
|
||||
marketBuyParams.signatures,
|
||||
marketBuyParams.feeOrders,
|
||||
marketBuyParams.feeSignatures,
|
||||
marketBuyParams.feePercentage,
|
||||
marketBuyParams.feeRecipient,
|
||||
];
|
||||
} else {
|
||||
const marketSellParams = (smartContractParamsInfo.params as any) as ForwarderMarketSellSmartContractParams;
|
||||
args = [
|
||||
marketSellParams.orders,
|
||||
marketSellParams.signatures,
|
||||
marketSellParams.feeOrders,
|
||||
marketSellParams.feeSignatures,
|
||||
marketSellParams.feePercentage,
|
||||
marketSellParams.feeRecipient,
|
||||
];
|
||||
}
|
||||
const calldataHexString = abiEncoder.encode(args);
|
||||
return {
|
||||
calldataHexString,
|
||||
@ -79,7 +101,9 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
public async getSmartContractParamsOrThrowAsync(
|
||||
quote: SwapQuote,
|
||||
opts: Partial<ForwarderSwapQuoteGetOutputOpts>,
|
||||
): Promise<SmartContractParamsInfo<ForwarderMarketBuySmartContractParams>> {
|
||||
): Promise<
|
||||
SmartContractParamsInfo<ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams>
|
||||
> {
|
||||
assert.isValidForwarderSwapQuote('quote', quote, this._getEtherTokenAssetDataOrThrow());
|
||||
|
||||
const { ethAmount, feeRecipient, feePercentage: unFormattedFeePercentage } = _.merge(
|
||||
@ -99,14 +123,24 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
unFormattedFeePercentage,
|
||||
);
|
||||
|
||||
const { orders, feeOrders, makerAssetFillAmount, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee;
|
||||
const consumableQuoteWithAffiliateFee = (swapQuoteWithAffiliateFee as any) as (
|
||||
| MarketBuySwapQuote
|
||||
| MarketSellSwapQuote);
|
||||
|
||||
const { orders, feeOrders, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee;
|
||||
|
||||
const signatures = _.map(orders, o => o.signature);
|
||||
const feeSignatures = _.map(feeOrders, o => o.signature);
|
||||
|
||||
const feePercentage = utils.numberPercentageToEtherTokenAmountPercentage(unFormattedFeePercentage);
|
||||
|
||||
const params: ForwarderMarketBuySmartContractParams = {
|
||||
let params: ForwarderMarketBuySmartContractParams | ForwarderMarketSellSmartContractParams;
|
||||
let methodName: string;
|
||||
|
||||
if (utils.isSwapQuoteMarketBuy(consumableQuoteWithAffiliateFee)) {
|
||||
const { makerAssetFillAmount } = consumableQuoteWithAffiliateFee;
|
||||
|
||||
params = {
|
||||
orders,
|
||||
makerAssetFillAmount,
|
||||
signatures,
|
||||
@ -116,9 +150,24 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
feeRecipient,
|
||||
};
|
||||
|
||||
methodName = 'marketBuyOrdersWithEth';
|
||||
} else {
|
||||
const { takerAssetFillAmount } = consumableQuoteWithAffiliateFee;
|
||||
|
||||
params = {
|
||||
orders,
|
||||
takerAssetFillAmount,
|
||||
signatures,
|
||||
feeOrders,
|
||||
feeSignatures,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
};
|
||||
methodName = 'marketSellOrdersWithEth';
|
||||
}
|
||||
const methodAbi = utils.getMethodAbiFromContractAbi(
|
||||
this._contractWrappers.forwarder.abi,
|
||||
'marketBuyOrdersWithEth',
|
||||
methodName,
|
||||
) as MethodAbi;
|
||||
|
||||
return {
|
||||
@ -163,12 +212,19 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
|
||||
const swapQuoteWithAffiliateFee = affiliateFeeUtils.getSwapQuoteWithAffiliateFee(quote, feePercentage);
|
||||
|
||||
const { orders, feeOrders, makerAssetFillAmount, worstCaseQuoteInfo } = swapQuoteWithAffiliateFee;
|
||||
const consumableQuoteWithAffiliateFee = (swapQuoteWithAffiliateFee as any) as (
|
||||
| MarketBuySwapQuote
|
||||
| MarketSellSwapQuote);
|
||||
|
||||
const { orders, feeOrders, worstCaseQuoteInfo } = consumableQuoteWithAffiliateFee;
|
||||
|
||||
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
|
||||
|
||||
try {
|
||||
const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
let txHash: string;
|
||||
if (utils.isSwapQuoteMarketBuy(consumableQuoteWithAffiliateFee)) {
|
||||
const { makerAssetFillAmount } = consumableQuoteWithAffiliateFee;
|
||||
txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
|
||||
orders,
|
||||
makerAssetFillAmount,
|
||||
finalTakerAddress,
|
||||
@ -182,6 +238,21 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumer<ForwarderMa
|
||||
shouldValidate: true,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
txHash = await this._contractWrappers.forwarder.marketSellOrdersWithEthAsync(
|
||||
orders,
|
||||
finalTakerAddress,
|
||||
ethAmount || worstCaseQuoteInfo.totalTakerTokenAmount,
|
||||
feeOrders,
|
||||
feePercentage,
|
||||
feeRecipient,
|
||||
{
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
shouldValidate: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
return txHash;
|
||||
} catch (err) {
|
||||
if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) {
|
||||
|
@ -276,7 +276,12 @@ export class SwapQuoter {
|
||||
assert.isBigNumber('takerAssetSellAmount', takerAssetSellAmount);
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenAddress);
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenAddress);
|
||||
const swapQuote = this.getMarketSellSwapQuoteAsync(makerAssetData, takerAssetData, takerAssetSellAmount, options);
|
||||
const swapQuote = this.getMarketSellSwapQuoteAsync(
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
takerAssetSellAmount,
|
||||
options,
|
||||
);
|
||||
return swapQuote;
|
||||
}
|
||||
/**
|
||||
|
@ -94,12 +94,25 @@ export interface ForwarderMarketBuySmartContractParams extends ExchangeMarketBuy
|
||||
feeRecipient: string;
|
||||
}
|
||||
|
||||
export interface ForwarderMarketSellSmartContractParams extends ExchangeMarketSellSmartContractParams {
|
||||
feeOrders: SignedOrder[];
|
||||
feeSignatures: string[];
|
||||
feePercentage: BigNumber;
|
||||
feeRecipient: string;
|
||||
}
|
||||
|
||||
export interface ExchangeMarketBuySmartContractParams {
|
||||
orders: SignedOrder[];
|
||||
makerAssetFillAmount: BigNumber;
|
||||
signatures: string[];
|
||||
}
|
||||
|
||||
export interface ExchangeMarketSellSmartContractParams {
|
||||
orders: SignedOrder[];
|
||||
takerAssetFillAmount: BigNumber;
|
||||
signatures: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that varying SwapQuoteConsumers adhere to (exchange consumer, router consumer, forwarder consumer, coordinator consumer)
|
||||
* getCalldataOrThrow: Get CalldataInfo to swap for tokens with provided SwapQuote. Throws if invalid SwapQuote is provided.
|
||||
@ -236,6 +249,7 @@ export interface SwapQuoterOpts {
|
||||
* Possible error messages thrown by an SwapQuoterConsumer instance or associated static methods.
|
||||
*/
|
||||
export enum SwapQuoteConsumerError {
|
||||
InvalidMarketSellOrMarketBuySwapQuote = 'INVALID_MARKET_BUY_SELL_SWAP_QUOTE',
|
||||
InvalidForwarderSwapQuote = 'INVALID_FORWARDER_SWAP_QUOTE_PROVIDED',
|
||||
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
|
||||
SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
|
||||
|
@ -3,7 +3,8 @@ import { schemas } from '@0x/json-schemas';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { OrderProvider, OrderProviderRequest, SwapQuote, SwapQuoteInfo } from '../types';
|
||||
import { OrderProvider, OrderProviderRequest, SwapQuote, SwapQuoteConsumerError, SwapQuoteInfo } from '../types';
|
||||
import { utils } from '../utils/utils';
|
||||
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
@ -14,7 +15,13 @@ export const assert = {
|
||||
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, swapQuote.feeOrders, schemas.signedOrdersSchema);
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.bestCaseQuoteInfo`, swapQuote.bestCaseQuoteInfo);
|
||||
assert.isValidSwapQuoteInfo(`${variableName}.worstCaseQuoteInfo`, swapQuote.worstCaseQuoteInfo);
|
||||
if (utils.isSwapQuoteMarketBuy(swapQuote)) {
|
||||
sharedAssert.isBigNumber(`${variableName}.makerAssetFillAmount`, swapQuote.makerAssetFillAmount);
|
||||
} else if (utils.isSwapQuoteMarketSell(swapQuote)) {
|
||||
sharedAssert.isBigNumber(`${variableName}.takerAssetFillAmount`, swapQuote.takerAssetFillAmount);
|
||||
} else {
|
||||
throw new Error(SwapQuoteConsumerError.InvalidMarketSellOrMarketBuySwapQuote);
|
||||
}
|
||||
},
|
||||
isValidForwarderSwapQuote(variableName: string, swapQuote: SwapQuote, wethAssetData: string): void {
|
||||
assert.isValidSwapQuote(variableName, swapQuote);
|
||||
@ -33,9 +40,15 @@ export const assert = {
|
||||
);
|
||||
},
|
||||
isValidSwapQuoteInfo(variableName: string, swapQuoteInfo: SwapQuoteInfo): void {
|
||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.takerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.feeTakerTokenAmount`, swapQuoteInfo.feeTakerTokenAmount);
|
||||
sharedAssert.isBigNumber(`${variableName}.totalTakerTokenAmount`, swapQuoteInfo.totalTakerTokenAmount);
|
||||
if (utils.isSwapQuoteInfoMarketBuy(swapQuoteInfo)) {
|
||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.takerTokenAmount);
|
||||
} else if (utils.isSwapQuoteInfoMarketSell(swapQuoteInfo)) {
|
||||
sharedAssert.isBigNumber(`${variableName}.takerTokenAmount`, swapQuoteInfo.makerTokenAmount);
|
||||
} else {
|
||||
throw new Error(SwapQuoteConsumerError.InvalidMarketSellOrMarketBuySwapQuote);
|
||||
}
|
||||
},
|
||||
isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void {
|
||||
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
|
||||
|
@ -4,7 +4,14 @@ import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import { InsufficientAssetLiquidityError } from '../errors';
|
||||
import { MarketBuySwapQuote, MarketBuySwapQuoteInfo, MarketSellSwapQuote, MarketSellSwapQuoteInfo, OrdersAndFillableAmounts, SwapQuoterError } from '../types';
|
||||
import {
|
||||
MarketBuySwapQuote,
|
||||
MarketBuySwapQuoteInfo,
|
||||
MarketSellSwapQuote,
|
||||
MarketSellSwapQuoteInfo,
|
||||
OrdersAndFillableAmounts,
|
||||
SwapQuoterError,
|
||||
} from '../types';
|
||||
|
||||
// Calculates a swap quote for orders
|
||||
export const swapQuoteCalculator = {
|
||||
@ -17,9 +24,11 @@ export const swapQuoteCalculator = {
|
||||
): MarketSellSwapQuote {
|
||||
const orders = ordersAndFillableAmounts.orders;
|
||||
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
||||
const remainingFillableTakerAssetAmounts = remainingFillableMakerAssetAmounts.map((makerAssetAmount: BigNumber, index: number) => {
|
||||
const remainingFillableTakerAssetAmounts = remainingFillableMakerAssetAmounts.map(
|
||||
(makerAssetAmount: BigNumber, index: number) => {
|
||||
return orderCalculationUtils.getTakerFillAmount(orders[index], makerAssetAmount);
|
||||
});
|
||||
},
|
||||
);
|
||||
const feeOrders = feeOrdersAndFillableAmounts.orders;
|
||||
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
|
||||
const slippageBufferAmount = takerAssetFillAmount.multipliedBy(slippagePercentage).integerValue();
|
||||
@ -32,9 +41,12 @@ export const swapQuoteCalculator = {
|
||||
remainingFillableTakerAssetAmounts,
|
||||
slippageBufferAmount,
|
||||
});
|
||||
const ordersRemainingFillableMakerAssetAmounts = _.map(ordersRemainingFillableTakerAssetAmounts, (takerAssetAmount: BigNumber, index: number) => {
|
||||
const 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 (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
|
||||
// We needed the amount they requested to buy, plus the amount for slippage
|
||||
@ -260,7 +272,10 @@ function calculateMarketSellQuoteInfo(
|
||||
let makerTokenAmount = constants.ZERO_AMOUNT;
|
||||
let zrxTakerTokenAmount = constants.ZERO_AMOUNT;
|
||||
if (isMakerAssetZrxToken) {
|
||||
makerTokenAmount = findZrxTokenAmountFromSellingTakerTokenAmount(ordersAndFillableAmounts, takerAssetSellAmount);
|
||||
makerTokenAmount = findZrxTokenAmountFromSellingTakerTokenAmount(
|
||||
ordersAndFillableAmounts,
|
||||
takerAssetSellAmount,
|
||||
);
|
||||
} else {
|
||||
// find eth and zrx amounts needed to buy
|
||||
const takerTokenAndZrxAmountToBuyAsset = findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset(
|
||||
@ -304,7 +319,10 @@ function findZrxTokenAmountFromSellingTakerTokenAmount(
|
||||
(acc, order, index) => {
|
||||
const { totalZrxTokenAmount, remainingTakerAssetFillAmount } = acc;
|
||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(order, remainingFillableMakerAssetAmount);
|
||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(
|
||||
order,
|
||||
remainingFillableMakerAssetAmount,
|
||||
);
|
||||
const takerFillAmount = BigNumber.min(remainingTakerAssetFillAmount, remainingFillableTakerAssetAmount);
|
||||
const makerFillAmount = orderCalculationUtils.getMakerFillAmount(order, takerFillAmount);
|
||||
const feeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
||||
@ -400,7 +418,10 @@ function findMakerTokenAmountReceivedAndZrxAmountNeededToSellAsset(
|
||||
(acc, order, index) => {
|
||||
const { totalMakerTokenAmount, totalZrxAmount, remainingTakerAssetFillAmount } = acc;
|
||||
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
|
||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(order, remainingFillableMakerAssetAmount);
|
||||
const remainingFillableTakerAssetAmount = orderCalculationUtils.getTakerFillAmount(
|
||||
order,
|
||||
remainingFillableMakerAssetAmount,
|
||||
);
|
||||
const takerFillAmount = BigNumber.min(acc.remainingTakerAssetFillAmount, remainingFillableTakerAssetAmount);
|
||||
const makerFillAmount = orderCalculationUtils.getMakerFillAmount(order, takerFillAmount);
|
||||
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
|
||||
|
@ -4,6 +4,14 @@ import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from '../constants';
|
||||
import {
|
||||
MarketBuySwapQuote,
|
||||
MarketBuySwapQuoteInfo,
|
||||
MarketSellSwapQuote,
|
||||
MarketSellSwapQuoteInfo,
|
||||
SwapQuote,
|
||||
SwapQuoteInfo,
|
||||
} from '../types';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
export const utils = {
|
||||
@ -25,4 +33,16 @@ export const utils = {
|
||||
},
|
||||
) as MethodAbi | undefined;
|
||||
},
|
||||
isSwapQuoteMarketBuy(quote: SwapQuote): quote is MarketBuySwapQuote {
|
||||
return (quote as MarketSellSwapQuote).takerAssetFillAmount !== undefined;
|
||||
},
|
||||
isSwapQuoteMarketSell(quote: SwapQuote): quote is MarketSellSwapQuote {
|
||||
return (quote as MarketBuySwapQuote).makerAssetFillAmount !== undefined;
|
||||
},
|
||||
isSwapQuoteInfoMarketBuy(quote: SwapQuoteInfo): quote is MarketBuySwapQuoteInfo {
|
||||
return (quote as MarketBuySwapQuoteInfo).takerTokenAmount !== undefined;
|
||||
},
|
||||
isSwapQuoteInfoMarketSell(quote: SwapQuoteInfo): quote is MarketSellSwapQuoteInfo {
|
||||
return (quote as MarketSellSwapQuoteInfo).makerTokenAmount !== undefined;
|
||||
},
|
||||
};
|
||||
|
@ -5,9 +5,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { assert } from './assert';
|
||||
import { constants } from './constants';
|
||||
import {
|
||||
orderCalculationUtils,
|
||||
} from './order_calculation_utils';
|
||||
import { orderCalculationUtils } from './order_calculation_utils';
|
||||
import {
|
||||
FeeOrdersAndRemainingFeeAmount,
|
||||
FindFeeOrdersThatCoverFeesForTargetOrdersOpts,
|
||||
|
Loading…
x
Reference in New Issue
Block a user