add new fqt and support for otc orders in asset-swapper exchange proxy swap qutoe consumer
This commit is contained in:
@@ -31,6 +31,7 @@ import "../features/libs/LibNativeOrder.sol";
|
||||
import "./bridges/IBridgeAdapter.sol";
|
||||
import "./Transformer.sol";
|
||||
import "./LibERC20Transformer.sol";
|
||||
import "../IZeroEx.sol";
|
||||
|
||||
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
||||
/// This transformer shortcuts bridge orders and fills them directly
|
||||
@@ -52,7 +53,8 @@ contract FillQuoteTransformer is
|
||||
enum OrderType {
|
||||
Bridge,
|
||||
Limit,
|
||||
Rfq
|
||||
Rfq,
|
||||
Otc
|
||||
}
|
||||
|
||||
struct LimitOrderInfo {
|
||||
@@ -69,6 +71,13 @@ contract FillQuoteTransformer is
|
||||
uint256 maxTakerTokenFillAmount;
|
||||
}
|
||||
|
||||
struct OtcOrderInfo {
|
||||
LibNativeOrder.OtcOrder order;
|
||||
LibSignature.Signature signature;
|
||||
// Maximum taker token amount of this limit order to fill.
|
||||
uint256 maxTakerTokenFillAmount;
|
||||
}
|
||||
|
||||
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||
struct TransformData {
|
||||
// Whether we are performing a market sell or buy.
|
||||
@@ -86,6 +95,8 @@ contract FillQuoteTransformer is
|
||||
LimitOrderInfo[] limitOrders;
|
||||
// Native RFQ orders. Sorted by fill sequence.
|
||||
RfqOrderInfo[] rfqOrders;
|
||||
// Otc orders. Sorted by fill sequence.
|
||||
OtcOrderInfo[] otcOrders;
|
||||
|
||||
// The sequence to fill the orders in. Each item will fill the next
|
||||
// order of that type in either `bridgeOrders`, `limitOrders`,
|
||||
@@ -123,7 +134,7 @@ contract FillQuoteTransformer is
|
||||
uint256 soldAmount;
|
||||
uint256 protocolFee;
|
||||
uint256 takerTokenBalanceRemaining;
|
||||
uint256[3] currentIndices;
|
||||
uint256[4] currentIndices;
|
||||
OrderType currentOrderType;
|
||||
}
|
||||
|
||||
@@ -147,12 +158,12 @@ contract FillQuoteTransformer is
|
||||
IBridgeAdapter public immutable bridgeAdapter;
|
||||
|
||||
/// @dev The exchange proxy contract.
|
||||
INativeOrdersFeature public immutable zeroEx;
|
||||
IZeroEx public immutable zeroEx;
|
||||
|
||||
/// @dev Create this contract.
|
||||
/// @param bridgeAdapter_ The bridge adapter contract.
|
||||
/// @param zeroEx_ The Exchange Proxy contract.
|
||||
constructor(IBridgeAdapter bridgeAdapter_, INativeOrdersFeature zeroEx_)
|
||||
constructor(IBridgeAdapter bridgeAdapter_, IZeroEx zeroEx_)
|
||||
public
|
||||
Transformer()
|
||||
{
|
||||
@@ -183,7 +194,8 @@ contract FillQuoteTransformer is
|
||||
|
||||
if (data.bridgeOrders.length
|
||||
+ data.limitOrders.length
|
||||
+ data.rfqOrders.length != data.fillSequence.length
|
||||
+ data.rfqOrders.length
|
||||
+ data.otcOrders.length != data.fillSequence.length
|
||||
) {
|
||||
LibTransformERC20RichErrors.InvalidTransformDataError(
|
||||
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH,
|
||||
@@ -198,15 +210,16 @@ contract FillQuoteTransformer is
|
||||
|
||||
// Approve the exchange proxy to spend our sell tokens if native orders
|
||||
// are present.
|
||||
if (data.limitOrders.length + data.rfqOrders.length != 0) {
|
||||
if (data.limitOrders.length + data.rfqOrders.length + data.otcOrders.length != 0) {
|
||||
data.sellToken.approveIfBelow(address(zeroEx), data.fillAmount);
|
||||
// Compute the protocol fee if a limit order is present.
|
||||
|
||||
if (data.limitOrders.length != 0) {
|
||||
state.protocolFee = uint256(zeroEx.getProtocolFeeMultiplier())
|
||||
.safeMul(tx.gasprice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
state.ethRemaining = address(this).balance;
|
||||
|
||||
// Fill the orders.
|
||||
@@ -222,6 +235,7 @@ contract FillQuoteTransformer is
|
||||
|
||||
state.currentOrderType = OrderType(data.fillSequence[i]);
|
||||
uint256 orderIndex = state.currentIndices[uint256(state.currentOrderType)];
|
||||
|
||||
// Fill the order.
|
||||
FillOrderResults memory results;
|
||||
if (state.currentOrderType == OrderType.Bridge) {
|
||||
@@ -230,6 +244,8 @@ contract FillQuoteTransformer is
|
||||
results = _fillLimitOrder(data.limitOrders[orderIndex], data, state);
|
||||
} else if (state.currentOrderType == OrderType.Rfq) {
|
||||
results = _fillRfqOrder(data.rfqOrders[orderIndex], data, state);
|
||||
} else if (state.currentOrderType == OrderType.Otc) {
|
||||
results = _fillOtcOrder(data.otcOrders[orderIndex], data, state);
|
||||
} else {
|
||||
revert("INVALID_ORDER_TYPE");
|
||||
}
|
||||
@@ -402,6 +418,41 @@ contract FillQuoteTransformer is
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Fill a single RFQ order.
|
||||
function _fillOtcOrder(
|
||||
OtcOrderInfo memory orderInfo,
|
||||
TransformData memory data,
|
||||
FillState memory state
|
||||
)
|
||||
private
|
||||
returns (FillOrderResults memory results)
|
||||
{
|
||||
uint256 takerTokenFillAmount = LibSafeMathV06.min256(
|
||||
_computeTakerTokenFillAmount(
|
||||
data,
|
||||
state,
|
||||
orderInfo.order.takerAmount,
|
||||
orderInfo.order.makerAmount,
|
||||
0
|
||||
),
|
||||
orderInfo.maxTakerTokenFillAmount
|
||||
);
|
||||
try
|
||||
zeroEx.fillOtcOrder
|
||||
(
|
||||
orderInfo.order,
|
||||
orderInfo.signature,
|
||||
takerTokenFillAmount.safeDowncastToUint128()
|
||||
)
|
||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||
{
|
||||
results.takerTokenSoldAmount = takerTokenFilledAmount;
|
||||
results.makerTokenBoughtAmount = makerTokenFilledAmount;
|
||||
} catch {
|
||||
revert("FillQuoteTransformer/OTC_ORDER_FILL_FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the next taker token fill amount of a generic order.
|
||||
function _computeTakerTokenFillAmount(
|
||||
TransformData memory data,
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
FillQuoteTransformerSide as Side,
|
||||
LimitOrder,
|
||||
LimitOrderFields,
|
||||
OtcOrder,
|
||||
OtcOrderFields,
|
||||
RfqOrder,
|
||||
RfqOrderFields,
|
||||
Signature,
|
||||
@@ -26,7 +28,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import { TestFillQuoteTransformerBridgeContract } from '../generated-wrappers/test_fill_quote_transformer_bridge';
|
||||
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
|
||||
import { getRandomLimitOrder, getRandomRfqOrder, getRandomOtcOrder } from '../utils/orders';
|
||||
import {
|
||||
EthereumBridgeAdapterContract,
|
||||
FillQuoteTransformerContract,
|
||||
@@ -142,6 +144,18 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
||||
};
|
||||
}
|
||||
|
||||
function createOtcOrder(fields: Partial<OtcOrderFields> = {}): OtcOrder {
|
||||
return getRandomOtcOrder({
|
||||
makerToken: makerToken.address,
|
||||
takerToken: takerToken.address,
|
||||
makerAmount: getRandomInteger('0.1e18', '1e18'),
|
||||
takerAmount: getRandomInteger('0.1e18', '1e18'),
|
||||
maker,
|
||||
taker,
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
function createOrderSignature(preFilledTakerAmount: Numberish = 0): Signature {
|
||||
return {
|
||||
// The r field of the signature is the pre-filled amount.
|
||||
@@ -394,6 +408,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
||||
bridgeOrders: [],
|
||||
limitOrders: [],
|
||||
rfqOrders: [],
|
||||
otcOrders: [],
|
||||
fillSequence: [],
|
||||
fillAmount: MAX_UINT256,
|
||||
refundReceiver: NULL_ADDRESS,
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
FinalUniswapV3FillData,
|
||||
LiquidityProviderFillData,
|
||||
MooniswapFillData,
|
||||
NativeOtcOrderFillData,
|
||||
NativeRfqOrderFillData,
|
||||
OptimizedMarketBridgeOrder,
|
||||
OptimizedMarketOrder,
|
||||
@@ -374,6 +375,56 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
};
|
||||
}
|
||||
|
||||
//OTC orders
|
||||
|
||||
if(
|
||||
[ChainId.Mainnet, ChainId.Polygon].includes(this.chainId) &&
|
||||
!isToETH &&
|
||||
!isFromETH &&
|
||||
quote.orders.every(o => o.type === FillQuoteTransformerOrderType.Otc) &&
|
||||
!requiresTransformERC20(optsWithDefaults)
|
||||
){
|
||||
const otcOrdersData = quote.orders.map(o => o.fillData as NativeOtcOrderFillData);
|
||||
const fillAmountPerOrder = (() => {
|
||||
// Don't think order taker amounts are clipped to actual sell amount
|
||||
// (the last one might be too large) so figure them out manually.
|
||||
let remaining = sellAmount;
|
||||
const fillAmounts = [];
|
||||
for (const o of quote.orders) {
|
||||
const fillAmount = BigNumber.min(o.takerAmount, remaining);
|
||||
fillAmounts.push(fillAmount);
|
||||
remaining = remaining.minus(fillAmount);
|
||||
}
|
||||
return fillAmounts;
|
||||
})();
|
||||
|
||||
// grab the amount to fill on each OtcOrder (if more than 1)
|
||||
let callData;
|
||||
|
||||
if(isFromETH){
|
||||
callData = this._exchangeProxy.fillOtcOrderWithEth(
|
||||
otcOrdersData[0].order, otcOrdersData[0].signature
|
||||
).getABIEncodedTransactionData();
|
||||
}
|
||||
if(isToETH){
|
||||
callData = this._exchangeProxy.fillOtcOrderForEth(
|
||||
otcOrdersData[0].order, otcOrdersData[0].signature, fillAmountPerOrder[0]
|
||||
).getABIEncodedTransactionData();
|
||||
}
|
||||
else{
|
||||
callData = this._exchangeProxy.fillOtcOrder(
|
||||
otcOrdersData[0].order, otcOrdersData[0].signature, fillAmountPerOrder[0]
|
||||
).getABIEncodedTransactionData();
|
||||
}
|
||||
return {
|
||||
calldataHexString: callData,
|
||||
ethAmount: ZERO_AMOUNT,
|
||||
toAddress: this._exchangeProxy.address,
|
||||
allowanceTarget: this._exchangeProxy.address,
|
||||
gasOverhead: ZERO_AMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||
return {
|
||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||
|
||||
@@ -113,11 +113,12 @@ function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrder
|
||||
*/
|
||||
export function getFQTTransformerDataFromOptimizedOrders(
|
||||
orders: OptimizedMarketOrder[],
|
||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'otcOrders' | 'fillSequence'> {
|
||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'otcOrders' | 'fillSequence'> = {
|
||||
bridgeOrders: [],
|
||||
limitOrders: [],
|
||||
rfqOrders: [],
|
||||
otcOrders: [],
|
||||
fillSequence: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
FillQuoteTransformerLimitOrderInfo,
|
||||
FillQuoteTransformerOrderType,
|
||||
FillQuoteTransformerOtcOrderInfo,
|
||||
FillQuoteTransformerRfqOrderInfo,
|
||||
} from '@0x/protocol-utils';
|
||||
import { MarketOperation } from '@0x/types';
|
||||
@@ -196,7 +197,8 @@ export interface FillData {}
|
||||
// `FillData` for native fills. Represents a single native order
|
||||
export type NativeRfqOrderFillData = FillQuoteTransformerRfqOrderInfo;
|
||||
export type NativeLimitOrderFillData = FillQuoteTransformerLimitOrderInfo;
|
||||
export type NativeFillData = NativeRfqOrderFillData | NativeLimitOrderFillData;
|
||||
export type NativeOtcOrderFillData = FillQuoteTransformerOtcOrderInfo;
|
||||
export type NativeFillData = NativeRfqOrderFillData | NativeLimitOrderFillData | NativeOtcOrderFillData;
|
||||
|
||||
// Represents an individual DEX sample from the sampler contract
|
||||
export interface DexSample<TFillData extends FillData = FillData> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbiEncoder, BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
import { LimitOrder, LimitOrderFields, RfqOrder, RfqOrderFields } from './orders';
|
||||
import { LimitOrder, LimitOrderFields, OtcOrder, OtcOrderFields, RfqOrder, RfqOrderFields } from './orders';
|
||||
import { Signature, SIGNATURE_ABI } from './signature_utils';
|
||||
|
||||
const BRIDGE_ORDER_ABI_COMPONENTS = [
|
||||
@@ -39,6 +39,21 @@ const RFQ_ORDER_INFO_ABI_COMPONENTS = [
|
||||
{ name: 'maxTakerTokenFillAmount', type: 'uint256' },
|
||||
];
|
||||
|
||||
const OTC_ORDER_INFO_ABI_COMPONENTS = [
|
||||
{
|
||||
name: 'order',
|
||||
type: 'tuple',
|
||||
components: OtcOrder.STRUCT_ABI,
|
||||
},
|
||||
{
|
||||
name: 'signature',
|
||||
type: 'tuple',
|
||||
components: SIGNATURE_ABI,
|
||||
},
|
||||
{ name: 'maxTakerTokenFillAmount', type: 'uint256' },
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* ABI encoder for `FillQuoteTransformer.TransformData`
|
||||
*/
|
||||
@@ -65,6 +80,11 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([
|
||||
type: 'tuple[]',
|
||||
components: RFQ_ORDER_INFO_ABI_COMPONENTS,
|
||||
},
|
||||
{
|
||||
name: 'otcOrders',
|
||||
type: 'tuple[]',
|
||||
components: OTC_ORDER_INFO_ABI_COMPONENTS,
|
||||
},
|
||||
{ name: 'fillSequence', type: 'uint8[]' },
|
||||
{ name: 'fillAmount', type: 'uint256' },
|
||||
{ name: 'refundReceiver', type: 'address' },
|
||||
@@ -87,6 +107,7 @@ export enum FillQuoteTransformerOrderType {
|
||||
Bridge,
|
||||
Limit,
|
||||
Rfq,
|
||||
Otc
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +120,7 @@ export interface FillQuoteTransformerData {
|
||||
bridgeOrders: FillQuoteTransformerBridgeOrder[];
|
||||
limitOrders: FillQuoteTransformerLimitOrderInfo[];
|
||||
rfqOrders: FillQuoteTransformerRfqOrderInfo[];
|
||||
otcOrders: FillQuoteTransformerOtcOrderInfo[];
|
||||
fillSequence: FillQuoteTransformerOrderType[];
|
||||
fillAmount: BigNumber;
|
||||
refundReceiver: string;
|
||||
@@ -176,6 +198,11 @@ export type FillQuoteTransformerLimitOrderInfo = FillQuoteTransformerNativeOrder
|
||||
*/
|
||||
export type FillQuoteTransformerRfqOrderInfo = FillQuoteTransformerNativeOrderInfo<RfqOrderFields>;
|
||||
|
||||
/**
|
||||
* `FillQuoteTransformer.RfqOrderInfo`
|
||||
*/
|
||||
export type FillQuoteTransformerOtcOrderInfo = FillQuoteTransformerNativeOrderInfo<OtcOrderFields>;
|
||||
|
||||
/**
|
||||
* ABI-encode a `FillQuoteTransformer.TransformData` type.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user