Compare commits
6 Commits
@0x/protoc
...
OtcOrdersI
Author | SHA1 | Date | |
---|---|---|---|
|
4a18c1b0f8 | ||
|
3c30c92acf | ||
|
cc1cc69ba5 | ||
|
4f57736c82 | ||
|
f466f8310f | ||
|
8f5e7d7fb5 |
@@ -31,6 +31,7 @@ import "../features/libs/LibNativeOrder.sol";
|
|||||||
import "./bridges/IBridgeAdapter.sol";
|
import "./bridges/IBridgeAdapter.sol";
|
||||||
import "./Transformer.sol";
|
import "./Transformer.sol";
|
||||||
import "./LibERC20Transformer.sol";
|
import "./LibERC20Transformer.sol";
|
||||||
|
import "../IZeroEx.sol";
|
||||||
|
|
||||||
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
||||||
/// This transformer shortcuts bridge orders and fills them directly
|
/// This transformer shortcuts bridge orders and fills them directly
|
||||||
@@ -52,7 +53,8 @@ contract FillQuoteTransformer is
|
|||||||
enum OrderType {
|
enum OrderType {
|
||||||
Bridge,
|
Bridge,
|
||||||
Limit,
|
Limit,
|
||||||
Rfq
|
Rfq,
|
||||||
|
Otc
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LimitOrderInfo {
|
struct LimitOrderInfo {
|
||||||
@@ -69,6 +71,13 @@ contract FillQuoteTransformer is
|
|||||||
uint256 maxTakerTokenFillAmount;
|
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()`.
|
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||||
struct TransformData {
|
struct TransformData {
|
||||||
// Whether we are performing a market sell or buy.
|
// Whether we are performing a market sell or buy.
|
||||||
@@ -86,6 +95,8 @@ contract FillQuoteTransformer is
|
|||||||
LimitOrderInfo[] limitOrders;
|
LimitOrderInfo[] limitOrders;
|
||||||
// Native RFQ orders. Sorted by fill sequence.
|
// Native RFQ orders. Sorted by fill sequence.
|
||||||
RfqOrderInfo[] rfqOrders;
|
RfqOrderInfo[] rfqOrders;
|
||||||
|
// Otc orders. Sorted by fill sequence.
|
||||||
|
OtcOrderInfo[] otcOrders;
|
||||||
|
|
||||||
// The sequence to fill the orders in. Each item will fill the next
|
// The sequence to fill the orders in. Each item will fill the next
|
||||||
// order of that type in either `bridgeOrders`, `limitOrders`,
|
// order of that type in either `bridgeOrders`, `limitOrders`,
|
||||||
@@ -123,7 +134,7 @@ contract FillQuoteTransformer is
|
|||||||
uint256 soldAmount;
|
uint256 soldAmount;
|
||||||
uint256 protocolFee;
|
uint256 protocolFee;
|
||||||
uint256 takerTokenBalanceRemaining;
|
uint256 takerTokenBalanceRemaining;
|
||||||
uint256[3] currentIndices;
|
uint256[4] currentIndices;
|
||||||
OrderType currentOrderType;
|
OrderType currentOrderType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,12 +158,12 @@ contract FillQuoteTransformer is
|
|||||||
IBridgeAdapter public immutable bridgeAdapter;
|
IBridgeAdapter public immutable bridgeAdapter;
|
||||||
|
|
||||||
/// @dev The exchange proxy contract.
|
/// @dev The exchange proxy contract.
|
||||||
INativeOrdersFeature public immutable zeroEx;
|
IZeroEx public immutable zeroEx;
|
||||||
|
|
||||||
/// @dev Create this contract.
|
/// @dev Create this contract.
|
||||||
/// @param bridgeAdapter_ The bridge adapter contract.
|
/// @param bridgeAdapter_ The bridge adapter contract.
|
||||||
/// @param zeroEx_ The Exchange Proxy contract.
|
/// @param zeroEx_ The Exchange Proxy contract.
|
||||||
constructor(IBridgeAdapter bridgeAdapter_, INativeOrdersFeature zeroEx_)
|
constructor(IBridgeAdapter bridgeAdapter_, IZeroEx zeroEx_)
|
||||||
public
|
public
|
||||||
Transformer()
|
Transformer()
|
||||||
{
|
{
|
||||||
@@ -183,7 +194,8 @@ contract FillQuoteTransformer is
|
|||||||
|
|
||||||
if (data.bridgeOrders.length
|
if (data.bridgeOrders.length
|
||||||
+ data.limitOrders.length
|
+ data.limitOrders.length
|
||||||
+ data.rfqOrders.length != data.fillSequence.length
|
+ data.rfqOrders.length
|
||||||
|
+ data.otcOrders.length != data.fillSequence.length
|
||||||
) {
|
) {
|
||||||
LibTransformERC20RichErrors.InvalidTransformDataError(
|
LibTransformERC20RichErrors.InvalidTransformDataError(
|
||||||
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH,
|
LibTransformERC20RichErrors.InvalidTransformDataErrorCode.INVALID_ARRAY_LENGTH,
|
||||||
@@ -198,15 +210,16 @@ contract FillQuoteTransformer is
|
|||||||
|
|
||||||
// Approve the exchange proxy to spend our sell tokens if native orders
|
// Approve the exchange proxy to spend our sell tokens if native orders
|
||||||
// are present.
|
// 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);
|
data.sellToken.approveIfBelow(address(zeroEx), data.fillAmount);
|
||||||
// Compute the protocol fee if a limit order is present.
|
// Compute the protocol fee if a limit order is present.
|
||||||
|
|
||||||
if (data.limitOrders.length != 0) {
|
if (data.limitOrders.length != 0) {
|
||||||
state.protocolFee = uint256(zeroEx.getProtocolFeeMultiplier())
|
state.protocolFee = uint256(zeroEx.getProtocolFeeMultiplier())
|
||||||
.safeMul(tx.gasprice);
|
.safeMul(tx.gasprice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.ethRemaining = address(this).balance;
|
state.ethRemaining = address(this).balance;
|
||||||
|
|
||||||
// Fill the orders.
|
// Fill the orders.
|
||||||
@@ -222,6 +235,7 @@ contract FillQuoteTransformer is
|
|||||||
|
|
||||||
state.currentOrderType = OrderType(data.fillSequence[i]);
|
state.currentOrderType = OrderType(data.fillSequence[i]);
|
||||||
uint256 orderIndex = state.currentIndices[uint256(state.currentOrderType)];
|
uint256 orderIndex = state.currentIndices[uint256(state.currentOrderType)];
|
||||||
|
|
||||||
// Fill the order.
|
// Fill the order.
|
||||||
FillOrderResults memory results;
|
FillOrderResults memory results;
|
||||||
if (state.currentOrderType == OrderType.Bridge) {
|
if (state.currentOrderType == OrderType.Bridge) {
|
||||||
@@ -230,6 +244,8 @@ contract FillQuoteTransformer is
|
|||||||
results = _fillLimitOrder(data.limitOrders[orderIndex], data, state);
|
results = _fillLimitOrder(data.limitOrders[orderIndex], data, state);
|
||||||
} else if (state.currentOrderType == OrderType.Rfq) {
|
} else if (state.currentOrderType == OrderType.Rfq) {
|
||||||
results = _fillRfqOrder(data.rfqOrders[orderIndex], data, state);
|
results = _fillRfqOrder(data.rfqOrders[orderIndex], data, state);
|
||||||
|
} else if (state.currentOrderType == OrderType.Otc) {
|
||||||
|
results = _fillOtcOrder(data.otcOrders[orderIndex], data, state);
|
||||||
} else {
|
} else {
|
||||||
revert("INVALID_ORDER_TYPE");
|
revert("INVALID_ORDER_TYPE");
|
||||||
}
|
}
|
||||||
@@ -402,6 +418,41 @@ contract FillQuoteTransformer is
|
|||||||
} catch {}
|
} 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.
|
// Compute the next taker token fill amount of a generic order.
|
||||||
function _computeTakerTokenFillAmount(
|
function _computeTakerTokenFillAmount(
|
||||||
TransformData memory data,
|
TransformData memory data,
|
||||||
|
@@ -12,10 +12,13 @@ import {
|
|||||||
FillQuoteTransformerData,
|
FillQuoteTransformerData,
|
||||||
FillQuoteTransformerLimitOrderInfo,
|
FillQuoteTransformerLimitOrderInfo,
|
||||||
FillQuoteTransformerOrderType as OrderType,
|
FillQuoteTransformerOrderType as OrderType,
|
||||||
|
FillQuoteTransformerOtcOrderInfo,
|
||||||
FillQuoteTransformerRfqOrderInfo,
|
FillQuoteTransformerRfqOrderInfo,
|
||||||
FillQuoteTransformerSide as Side,
|
FillQuoteTransformerSide as Side,
|
||||||
LimitOrder,
|
LimitOrder,
|
||||||
LimitOrderFields,
|
LimitOrderFields,
|
||||||
|
OtcOrder,
|
||||||
|
OtcOrderFields,
|
||||||
RfqOrder,
|
RfqOrder,
|
||||||
RfqOrderFields,
|
RfqOrderFields,
|
||||||
Signature,
|
Signature,
|
||||||
@@ -26,7 +29,7 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { TestFillQuoteTransformerBridgeContract } from '../generated-wrappers/test_fill_quote_transformer_bridge';
|
import { TestFillQuoteTransformerBridgeContract } from '../generated-wrappers/test_fill_quote_transformer_bridge';
|
||||||
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
|
import { getRandomLimitOrder, getRandomOtcOrder, getRandomRfqOrder } from '../utils/orders';
|
||||||
import {
|
import {
|
||||||
EthereumBridgeAdapterContract,
|
EthereumBridgeAdapterContract,
|
||||||
FillQuoteTransformerContract,
|
FillQuoteTransformerContract,
|
||||||
@@ -142,6 +145,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 {
|
function createOrderSignature(preFilledTakerAmount: Numberish = 0): Signature {
|
||||||
return {
|
return {
|
||||||
// The r field of the signature is the pre-filled amount.
|
// The r field of the signature is the pre-filled amount.
|
||||||
@@ -254,6 +269,24 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fillOtcOrder(oi: FillQuoteTransformerOtcOrderInfo): FillOrderResults {
|
||||||
|
const preFilledTakerAmount = orderSignatureToPreFilledTakerAmount(oi.signature);
|
||||||
|
if (preFilledTakerAmount.gte(oi.order.takerAmount) || preFilledTakerAmount.eq(REVERT_AMOUNT)) {
|
||||||
|
return EMPTY_FILL_ORDER_RESULTS;
|
||||||
|
}
|
||||||
|
const takerTokenFillAmount = BigNumber.min(
|
||||||
|
computeTakerTokenFillAmount(oi.order.takerAmount, oi.order.makerAmount),
|
||||||
|
oi.order.takerAmount.minus(preFilledTakerAmount),
|
||||||
|
oi.maxTakerTokenFillAmount,
|
||||||
|
);
|
||||||
|
const fillRatio = takerTokenFillAmount.div(oi.order.takerAmount);
|
||||||
|
return {
|
||||||
|
...EMPTY_FILL_ORDER_RESULTS,
|
||||||
|
takerTokenSoldAmount: takerTokenFillAmount,
|
||||||
|
makerTokenBoughtAmount: fillRatio.times(oi.order.makerAmount).integerValue(BigNumber.ROUND_DOWN),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function fillRfqOrder(oi: FillQuoteTransformerRfqOrderInfo): FillOrderResults {
|
function fillRfqOrder(oi: FillQuoteTransformerRfqOrderInfo): FillOrderResults {
|
||||||
const preFilledTakerAmount = orderSignatureToPreFilledTakerAmount(oi.signature);
|
const preFilledTakerAmount = orderSignatureToPreFilledTakerAmount(oi.signature);
|
||||||
if (preFilledTakerAmount.gte(oi.order.takerAmount) || preFilledTakerAmount.eq(REVERT_AMOUNT)) {
|
if (preFilledTakerAmount.gte(oi.order.takerAmount) || preFilledTakerAmount.eq(REVERT_AMOUNT)) {
|
||||||
@@ -296,6 +329,11 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
results = fillLimitOrder(data.limitOrders[orderIndices[orderType]]);
|
results = fillLimitOrder(data.limitOrders[orderIndices[orderType]]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OrderType.Otc:
|
||||||
|
{
|
||||||
|
results = fillOtcOrder(data.otcOrders[orderIndices[orderType]]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OrderType.Rfq:
|
case OrderType.Rfq:
|
||||||
{
|
{
|
||||||
results = fillRfqOrder(data.rfqOrders[orderIndices[orderType]]);
|
results = fillRfqOrder(data.rfqOrders[orderIndices[orderType]]);
|
||||||
@@ -393,6 +431,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
buyToken: makerToken.address,
|
buyToken: makerToken.address,
|
||||||
bridgeOrders: [],
|
bridgeOrders: [],
|
||||||
limitOrders: [],
|
limitOrders: [],
|
||||||
|
otcOrders: [],
|
||||||
rfqOrders: [],
|
rfqOrders: [],
|
||||||
fillSequence: [],
|
fillSequence: [],
|
||||||
fillAmount: MAX_UINT256,
|
fillAmount: MAX_UINT256,
|
||||||
@@ -447,7 +486,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('sell quotes', () => {
|
describe('sell quotes', () => {
|
||||||
it('can fully sell to a single bridge order with -1 fillAmount', async () => {
|
it.only('can fully sell to a single bridge order with -1 fillAmount', async () => {
|
||||||
const bridgeOrders = [createBridgeOrder()];
|
const bridgeOrders = [createBridgeOrder()];
|
||||||
const data = createTransformData({
|
const data = createTransformData({
|
||||||
bridgeOrders,
|
bridgeOrders,
|
||||||
@@ -624,6 +663,24 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
return assertFinalBalancesAsync(qfr);
|
return assertFinalBalancesAsync(qfr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can fully sell to a single OTC order', async () => {
|
||||||
|
const otcOrders = [createOtcOrder()];
|
||||||
|
const data = createTransformData({
|
||||||
|
otcOrders: otcOrders.map(o => ({
|
||||||
|
order: o,
|
||||||
|
maxTakerTokenFillAmount: MAX_UINT256,
|
||||||
|
signature: createOrderSignature(),
|
||||||
|
})),
|
||||||
|
fillAmount: BigNumber.sum(...otcOrders.map(o => o.takerAmount)),
|
||||||
|
fillSequence: otcOrders.map(() => OrderType.Rfq),
|
||||||
|
});
|
||||||
|
const qfr = getExpectedQuoteFillResults(data, createSimulationState());
|
||||||
|
await executeTransformAsync({
|
||||||
|
data,
|
||||||
|
takerTokenBalance: qfr.takerTokensSpent,
|
||||||
|
});
|
||||||
|
return assertFinalBalancesAsync(qfr);
|
||||||
|
});
|
||||||
it('can fully sell to a single RFQ order', async () => {
|
it('can fully sell to a single RFQ order', async () => {
|
||||||
const rfqOrders = [createRfqOrder()];
|
const rfqOrders = [createRfqOrder()];
|
||||||
const data = createTransformData({
|
const data = createTransformData({
|
||||||
@@ -1117,6 +1174,30 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
return assertFinalBalancesAsync(qfr);
|
return assertFinalBalancesAsync(qfr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can fully buy to a single OTC order', async () => {
|
||||||
|
const otcOrders = [createOtcOrder()];
|
||||||
|
const totalTakerTokens = BigNumber.sum(...otcOrders.map(o => o.takerAmount));
|
||||||
|
const data = createTransformData({
|
||||||
|
side: Side.Buy,
|
||||||
|
otcOrders: otcOrders.map(o => ({
|
||||||
|
order: o,
|
||||||
|
maxTakerTokenFillAmount: MAX_UINT256,
|
||||||
|
signature: createOrderSignature(),
|
||||||
|
})),
|
||||||
|
fillAmount: BigNumber.sum(...otcOrders.map(o => o.makerAmount)),
|
||||||
|
fillSequence: otcOrders.map(() => OrderType.Rfq),
|
||||||
|
});
|
||||||
|
const qfr = getExpectedQuoteFillResults(
|
||||||
|
data,
|
||||||
|
createSimulationState({ takerTokenBalance: totalTakerTokens }),
|
||||||
|
);
|
||||||
|
await executeTransformAsync({
|
||||||
|
data,
|
||||||
|
takerTokenBalance: qfr.takerTokensSpent,
|
||||||
|
});
|
||||||
|
return assertFinalBalancesAsync(qfr);
|
||||||
|
});
|
||||||
|
|
||||||
it('can fully buy to a single RFQ order', async () => {
|
it('can fully buy to a single RFQ order', async () => {
|
||||||
const rfqOrders = [createRfqOrder()];
|
const rfqOrders = [createRfqOrder()];
|
||||||
const totalTakerTokens = BigNumber.sum(...rfqOrders.map(o => o.takerAmount));
|
const totalTakerTokens = BigNumber.sum(...rfqOrders.map(o => o.takerAmount));
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "16.64.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add support for OTC orders in the fillQuoteTransformer",
|
||||||
|
"pr": 516
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "16.63.1",
|
"version": "16.63.1",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -42,6 +42,7 @@ import {
|
|||||||
FinalUniswapV3FillData,
|
FinalUniswapV3FillData,
|
||||||
LiquidityProviderFillData,
|
LiquidityProviderFillData,
|
||||||
MooniswapFillData,
|
MooniswapFillData,
|
||||||
|
NativeOtcOrderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
OptimizedMarketBridgeOrder,
|
OptimizedMarketBridgeOrder,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
@@ -49,6 +50,7 @@ import {
|
|||||||
} from '../utils/market_operation_utils/types';
|
} from '../utils/market_operation_utils/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
multiplexOtcOrder,
|
||||||
multiplexPlpEncoder,
|
multiplexPlpEncoder,
|
||||||
multiplexRfqEncoder,
|
multiplexRfqEncoder,
|
||||||
MultiplexSubcall,
|
MultiplexSubcall,
|
||||||
@@ -340,18 +342,7 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
!requiresTransformERC20(optsWithDefaults)
|
!requiresTransformERC20(optsWithDefaults)
|
||||||
) {
|
) {
|
||||||
const rfqOrdersData = quote.orders.map(o => o.fillData as NativeRfqOrderFillData);
|
const rfqOrdersData = quote.orders.map(o => o.fillData as NativeRfqOrderFillData);
|
||||||
const fillAmountPerOrder = (() => {
|
const fillAmountPerOrder = generateFillAmounts(sellAmount, quote);
|
||||||
// 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;
|
|
||||||
})();
|
|
||||||
const callData =
|
const callData =
|
||||||
quote.orders.length === 1
|
quote.orders.length === 1
|
||||||
? this._exchangeProxy
|
? this._exchangeProxy
|
||||||
@@ -374,6 +365,46 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OTC orders
|
||||||
|
if (
|
||||||
|
[ChainId.Mainnet, ChainId.Ropsten].includes(this.chainId) && // @todo goerli and polygon?
|
||||||
|
quote.orders.every(o => o.type === FillQuoteTransformerOrderType.Otc) &&
|
||||||
|
!requiresTransformERC20(optsWithDefaults)
|
||||||
|
) {
|
||||||
|
const otcOrdersData = quote.orders.map(o => o.fillData as NativeOtcOrderFillData);
|
||||||
|
const fillAmountPerOrder = generateFillAmounts(sellAmount, quote);
|
||||||
|
// grab the amount to fill on each OtcOrder (if more than 1, fallback to multiplexBatchFill)
|
||||||
|
|
||||||
|
let callData;
|
||||||
|
// if we have more than one otc order we want to batch fill them,
|
||||||
|
if (quote.orders.length === 1) {
|
||||||
|
// if the otc orders takerToken is the native asset
|
||||||
|
if (isFromETH) {
|
||||||
|
callData = this._exchangeProxy
|
||||||
|
.fillOtcOrderWithEth(otcOrdersData[0].order, otcOrdersData[0].signature)
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
}
|
||||||
|
// if the otc orders makerToken is the native asset
|
||||||
|
if (isToETH) {
|
||||||
|
callData = this._exchangeProxy
|
||||||
|
.fillOtcOrderForEth(otcOrdersData[0].order, otcOrdersData[0].signature, fillAmountPerOrder[0])
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
} else {
|
||||||
|
// if the otc order contains 2 erc20 tokens
|
||||||
|
callData = this._exchangeProxy
|
||||||
|
.fillOtcOrder(otcOrdersData[0].order, otcOrdersData[0].signature, fillAmountPerOrder[0])
|
||||||
|
.getABIEncodedTransactionData();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
calldataHexString: callData,
|
||||||
|
ethAmount: isFromETH ? sellAmount : ZERO_AMOUNT,
|
||||||
|
toAddress: this._exchangeProxy.address,
|
||||||
|
allowanceTarget: this._exchangeProxy.address,
|
||||||
|
gasOverhead: ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
if (this.chainId === ChainId.Mainnet && isMultiplexBatchFillCompatible(quote, optsWithDefaults)) {
|
||||||
return {
|
return {
|
||||||
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
calldataHexString: this._encodeMultiplexBatchFillCalldata(
|
||||||
@@ -567,19 +598,30 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
for_loop: for (const [i, order] of quote.orders.entries()) {
|
for_loop: for (const [i, order] of quote.orders.entries()) {
|
||||||
switch_statement: switch (order.source) {
|
switch_statement: switch (order.source) {
|
||||||
case ERC20BridgeSource.Native:
|
case ERC20BridgeSource.Native:
|
||||||
if (order.type !== FillQuoteTransformerOrderType.Rfq) {
|
if (order.type !== FillQuoteTransformerOrderType.Rfq && order.type !== FillQuoteTransformerOrderType.Otc) {
|
||||||
// Should never happen because we check `isMultiplexBatchFillCompatible`
|
// Should never happen because we check `isMultiplexBatchFillCompatible`
|
||||||
// before calling this function.
|
// before calling this function.
|
||||||
throw new Error('Multiplex batch fill only supported for RFQ native orders');
|
throw new Error('Multiplex batch fill only supported for RFQ native orders and OTC Orders');
|
||||||
|
}
|
||||||
|
if (order.type !== FillQuoteTransformerOrderType.Otc) {
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.Rfq,
|
||||||
|
sellAmount: order.takerAmount,
|
||||||
|
data: multiplexRfqEncoder.encode({
|
||||||
|
order: order.fillData.order,
|
||||||
|
signature: order.fillData.signature,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.Otc,
|
||||||
|
sellAmount: order.takerAmount,
|
||||||
|
data: multiplexOtcOrder.encode({
|
||||||
|
order: order.fillData.order,
|
||||||
|
signature: order.fillData.signature,
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
subcalls.push({
|
|
||||||
id: MultiplexSubcall.Rfq,
|
|
||||||
sellAmount: order.takerAmount,
|
|
||||||
data: multiplexRfqEncoder.encode({
|
|
||||||
order: order.fillData.order,
|
|
||||||
signature: order.fillData.signature,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
break switch_statement;
|
break switch_statement;
|
||||||
case ERC20BridgeSource.UniswapV2:
|
case ERC20BridgeSource.UniswapV2:
|
||||||
case ERC20BridgeSource.SushiSwap:
|
case ERC20BridgeSource.SushiSwap:
|
||||||
@@ -730,6 +772,17 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateFillAmounts(sellAmount: BigNumber, quote: MarketBuySwapQuote | MarketSellSwapQuote): BigNumber[] {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
|
function slipNonNativeOrders(quote: MarketSellSwapQuote | MarketBuySwapQuote): OptimizedMarketOrder[] {
|
||||||
const slippage = getMaxQuoteSlippageRate(quote);
|
const slippage = getMaxQuoteSlippageRate(quote);
|
||||||
if (slippage === 0) {
|
if (slippage === 0) {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { RfqOrder, SIGNATURE_ABI } from '@0x/protocol-utils';
|
import { OtcOrder, RfqOrder, SIGNATURE_ABI } from '@0x/protocol-utils';
|
||||||
import { AbiEncoder } from '@0x/utils';
|
import { AbiEncoder } from '@0x/utils';
|
||||||
|
|
||||||
export enum MultiplexSubcall {
|
export enum MultiplexSubcall {
|
||||||
@@ -26,6 +26,10 @@ export const multiplexRfqEncoder = AbiEncoder.create([
|
|||||||
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||||
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||||
]);
|
]);
|
||||||
|
export const multiplexOtcOrder = AbiEncoder.create([
|
||||||
|
{ name: 'order', type: 'tuple', components: OtcOrder.STRUCT_ABI },
|
||||||
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||||
|
]);
|
||||||
export const multiplexUniswapEncoder = AbiEncoder.create([
|
export const multiplexUniswapEncoder = AbiEncoder.create([
|
||||||
{ name: 'tokens', type: 'address[]' },
|
{ name: 'tokens', type: 'address[]' },
|
||||||
{ name: 'isSushi', type: 'bool' },
|
{ name: 'isSushi', type: 'bool' },
|
||||||
|
@@ -113,11 +113,15 @@ function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrder
|
|||||||
*/
|
*/
|
||||||
export function getFQTTransformerDataFromOptimizedOrders(
|
export function getFQTTransformerDataFromOptimizedOrders(
|
||||||
orders: OptimizedMarketOrder[],
|
orders: OptimizedMarketOrder[],
|
||||||
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> {
|
): Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'otcOrders' | 'fillSequence'> {
|
||||||
const fqtData: Pick<FillQuoteTransformerData, 'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'fillSequence'> = {
|
const fqtData: Pick<
|
||||||
|
FillQuoteTransformerData,
|
||||||
|
'bridgeOrders' | 'limitOrders' | 'rfqOrders' | 'otcOrders' | 'fillSequence'
|
||||||
|
> = {
|
||||||
bridgeOrders: [],
|
bridgeOrders: [],
|
||||||
limitOrders: [],
|
limitOrders: [],
|
||||||
rfqOrders: [],
|
rfqOrders: [],
|
||||||
|
otcOrders: [],
|
||||||
fillSequence: [],
|
fillSequence: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
FillQuoteTransformerLimitOrderInfo,
|
FillQuoteTransformerLimitOrderInfo,
|
||||||
FillQuoteTransformerOrderType,
|
FillQuoteTransformerOrderType,
|
||||||
|
FillQuoteTransformerOtcOrderInfo,
|
||||||
FillQuoteTransformerRfqOrderInfo,
|
FillQuoteTransformerRfqOrderInfo,
|
||||||
} from '@0x/protocol-utils';
|
} from '@0x/protocol-utils';
|
||||||
import { MarketOperation } from '@0x/types';
|
import { MarketOperation } from '@0x/types';
|
||||||
@@ -196,7 +197,8 @@ export interface FillData {}
|
|||||||
// `FillData` for native fills. Represents a single native order
|
// `FillData` for native fills. Represents a single native order
|
||||||
export type NativeRfqOrderFillData = FillQuoteTransformerRfqOrderInfo;
|
export type NativeRfqOrderFillData = FillQuoteTransformerRfqOrderInfo;
|
||||||
export type NativeLimitOrderFillData = FillQuoteTransformerLimitOrderInfo;
|
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
|
// Represents an individual DEX sample from the sampler contract
|
||||||
export interface DexSample<TFillData extends FillData = FillData> {
|
export interface DexSample<TFillData extends FillData = FillData> {
|
||||||
@@ -436,7 +438,8 @@ export interface OptimizedRfqOrder extends OptimizedMarketOrderBase<NativeRfqOrd
|
|||||||
export type OptimizedMarketOrder =
|
export type OptimizedMarketOrder =
|
||||||
| OptimizedMarketBridgeOrder<FillData>
|
| OptimizedMarketBridgeOrder<FillData>
|
||||||
| OptimizedMarketOrderBase<NativeLimitOrderFillData>
|
| OptimizedMarketOrderBase<NativeLimitOrderFillData>
|
||||||
| OptimizedMarketOrderBase<NativeRfqOrderFillData>;
|
| OptimizedMarketOrderBase<NativeRfqOrderFillData>
|
||||||
|
| OptimizedMarketOrderBase<NativeOtcOrderFillData>;
|
||||||
|
|
||||||
export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
export interface GetMarketOrdersRfqOpts extends RfqRequestOpts {
|
||||||
rfqClient?: IRfqClient;
|
rfqClient?: IRfqClient;
|
||||||
|
@@ -11,8 +11,6 @@ import {
|
|||||||
FillData,
|
FillData,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeFillData,
|
NativeFillData,
|
||||||
NativeLimitOrderFillData,
|
|
||||||
NativeRfqOrderFillData,
|
|
||||||
RawQuotes,
|
RawQuotes,
|
||||||
} from './market_operation_utils/types';
|
} from './market_operation_utils/types';
|
||||||
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
import { QuoteRequestor, V4RFQIndicativeQuoteMM } from './quote_requestor';
|
||||||
@@ -353,7 +351,7 @@ function _isNativeOrderFromCollapsedFill(cf: Fill): cf is Fill<NativeFillData> {
|
|||||||
*/
|
*/
|
||||||
export function nativeOrderToReportEntry(
|
export function nativeOrderToReportEntry(
|
||||||
type: FillQuoteTransformerOrderType,
|
type: FillQuoteTransformerOrderType,
|
||||||
fillData: NativeLimitOrderFillData | NativeRfqOrderFillData,
|
fillData: NativeFillData,
|
||||||
fillableAmount: BigNumber,
|
fillableAmount: BigNumber,
|
||||||
comparisonPrice?: BigNumber | undefined,
|
comparisonPrice?: BigNumber | undefined,
|
||||||
quoteRequestor?: QuoteRequestor,
|
quoteRequestor?: QuoteRequestor,
|
||||||
|
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "11.16.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add Otc order support to the fillQuoteTransformer",
|
||||||
|
"pr": 516
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "11.15.0",
|
"version": "11.15.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { AbiEncoder, BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
import { AbiEncoder, BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||||
import * as ethjs from 'ethereumjs-util';
|
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';
|
import { Signature, SIGNATURE_ABI } from './signature_utils';
|
||||||
|
|
||||||
const BRIDGE_ORDER_ABI_COMPONENTS = [
|
const BRIDGE_ORDER_ABI_COMPONENTS = [
|
||||||
@@ -39,6 +39,20 @@ const RFQ_ORDER_INFO_ABI_COMPONENTS = [
|
|||||||
{ name: 'maxTakerTokenFillAmount', type: 'uint256' },
|
{ 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`
|
* ABI encoder for `FillQuoteTransformer.TransformData`
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +74,11 @@ export const fillQuoteTransformerDataEncoder = AbiEncoder.create([
|
|||||||
type: 'tuple[]',
|
type: 'tuple[]',
|
||||||
components: LIMIT_ORDER_INFO_ABI_COMPONENTS,
|
components: LIMIT_ORDER_INFO_ABI_COMPONENTS,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'otcOrders',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: OTC_ORDER_INFO_ABI_COMPONENTS,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'rfqOrders',
|
name: 'rfqOrders',
|
||||||
type: 'tuple[]',
|
type: 'tuple[]',
|
||||||
@@ -87,6 +106,7 @@ export enum FillQuoteTransformerOrderType {
|
|||||||
Bridge,
|
Bridge,
|
||||||
Limit,
|
Limit,
|
||||||
Rfq,
|
Rfq,
|
||||||
|
Otc,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +119,7 @@ export interface FillQuoteTransformerData {
|
|||||||
bridgeOrders: FillQuoteTransformerBridgeOrder[];
|
bridgeOrders: FillQuoteTransformerBridgeOrder[];
|
||||||
limitOrders: FillQuoteTransformerLimitOrderInfo[];
|
limitOrders: FillQuoteTransformerLimitOrderInfo[];
|
||||||
rfqOrders: FillQuoteTransformerRfqOrderInfo[];
|
rfqOrders: FillQuoteTransformerRfqOrderInfo[];
|
||||||
|
otcOrders: FillQuoteTransformerOtcOrderInfo[];
|
||||||
fillSequence: FillQuoteTransformerOrderType[];
|
fillSequence: FillQuoteTransformerOrderType[];
|
||||||
fillAmount: BigNumber;
|
fillAmount: BigNumber;
|
||||||
refundReceiver: string;
|
refundReceiver: string;
|
||||||
@@ -176,6 +197,11 @@ export type FillQuoteTransformerLimitOrderInfo = FillQuoteTransformerNativeOrder
|
|||||||
*/
|
*/
|
||||||
export type FillQuoteTransformerRfqOrderInfo = FillQuoteTransformerNativeOrderInfo<RfqOrderFields>;
|
export type FillQuoteTransformerRfqOrderInfo = FillQuoteTransformerNativeOrderInfo<RfqOrderFields>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `FillQuoteTransformer.OtcOrderInfo`
|
||||||
|
*/
|
||||||
|
export type FillQuoteTransformerOtcOrderInfo = FillQuoteTransformerNativeOrderInfo<OtcOrderFields>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ABI-encode a `FillQuoteTransformer.TransformData` type.
|
* ABI-encode a `FillQuoteTransformer.TransformData` type.
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user