Compare commits
3 Commits
@0x/contra
...
feat/OtcOr
Author | SHA1 | Date | |
---|---|---|---|
|
c82ce2bdf9 | ||
|
1170c90141 | ||
|
cb7916789b |
@@ -1,4 +1,12 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.37.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add support for OTC orders in the FillQuoteTransformer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1660093941,
|
"timestamp": 1660093941,
|
||||||
"version": "0.36.3",
|
"version": "0.36.3",
|
||||||
|
@@ -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 {
|
||||||
@@ -62,6 +64,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;
|
||||||
|
}
|
||||||
|
|
||||||
struct RfqOrderInfo {
|
struct RfqOrderInfo {
|
||||||
LibNativeOrder.RfqOrder order;
|
LibNativeOrder.RfqOrder order;
|
||||||
LibSignature.Signature signature;
|
LibSignature.Signature signature;
|
||||||
@@ -84,6 +93,8 @@ contract FillQuoteTransformer is
|
|||||||
IBridgeAdapter.BridgeOrder[] bridgeOrders;
|
IBridgeAdapter.BridgeOrder[] bridgeOrders;
|
||||||
// Native limit orders. Sorted by fill sequence.
|
// Native limit orders. Sorted by fill sequence.
|
||||||
LimitOrderInfo[] limitOrders;
|
LimitOrderInfo[] limitOrders;
|
||||||
|
// Otc orders. Sorted by fill sequence.
|
||||||
|
OtcOrderInfo[] otcOrders;
|
||||||
// Native RFQ orders. Sorted by fill sequence.
|
// Native RFQ orders. Sorted by fill sequence.
|
||||||
RfqOrderInfo[] rfqOrders;
|
RfqOrderInfo[] rfqOrders;
|
||||||
|
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -172,7 +183,6 @@ contract FillQuoteTransformer is
|
|||||||
{
|
{
|
||||||
TransformData memory data = abi.decode(context.data, (TransformData));
|
TransformData memory data = abi.decode(context.data, (TransformData));
|
||||||
FillState memory state;
|
FillState memory state;
|
||||||
|
|
||||||
// Validate data fields.
|
// Validate data fields.
|
||||||
if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
|
if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
|
||||||
LibTransformERC20RichErrors.InvalidTransformDataError(
|
LibTransformERC20RichErrors.InvalidTransformDataError(
|
||||||
@@ -183,6 +193,7 @@ contract FillQuoteTransformer is
|
|||||||
|
|
||||||
if (data.bridgeOrders.length
|
if (data.bridgeOrders.length
|
||||||
+ data.limitOrders.length
|
+ data.limitOrders.length
|
||||||
|
+ data.otcOrders.length
|
||||||
+ data.rfqOrders.length != data.fillSequence.length
|
+ data.rfqOrders.length != data.fillSequence.length
|
||||||
) {
|
) {
|
||||||
LibTransformERC20RichErrors.InvalidTransformDataError(
|
LibTransformERC20RichErrors.InvalidTransformDataError(
|
||||||
@@ -198,7 +209,7 @@ 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) {
|
||||||
@@ -222,6 +233,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 +242,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 +416,42 @@ contract FillQuoteTransformer is
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill a single OTC 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,
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||||
|
"fqt": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' -g 'sell quotes' --timeout 100000 --bail --exit",
|
||||||
"compile": "sol-compiler",
|
"compile": "sol-compiler",
|
||||||
"watch": "sol-compiler -w",
|
"watch": "sol-compiler -w",
|
||||||
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
|
"clean": "shx rm -rf lib test/generated-artifacts test/generated-wrappers generated-artifacts generated-wrappers",
|
||||||
|
@@ -12,21 +12,26 @@ 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,
|
||||||
} from '@0x/protocol-utils';
|
} from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
import { assert } from 'chai';
|
||||||
import { TransactionReceiptWithDecodedLogs as TxReceipt } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs as TxReceipt } from 'ethereum-types';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
import * as _ from 'lodash';
|
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,
|
||||||
@@ -132,6 +137,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 createBridgeOrder(fillRatio: Numberish = 1.0): BridgeOrder {
|
function createBridgeOrder(fillRatio: Numberish = 1.0): BridgeOrder {
|
||||||
const makerTokenAmount = getRandomInteger('0.1e18', '1e18');
|
const makerTokenAmount = getRandomInteger('0.1e18', '1e18');
|
||||||
return {
|
return {
|
||||||
@@ -191,7 +208,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
let soldAmount = ZERO_AMOUNT;
|
let soldAmount = ZERO_AMOUNT;
|
||||||
let boughtAmount = ZERO_AMOUNT;
|
let boughtAmount = ZERO_AMOUNT;
|
||||||
const fillAmount = normalizeFillAmount(data.fillAmount, state.takerTokenBalance);
|
const fillAmount = normalizeFillAmount(data.fillAmount, state.takerTokenBalance);
|
||||||
const orderIndices = [0, 0, 0];
|
const orderIndices = [0, 0, 0, 0];
|
||||||
|
|
||||||
function computeTakerTokenFillAmount(
|
function computeTakerTokenFillAmount(
|
||||||
orderTakerTokenAmount: BigNumber,
|
orderTakerTokenAmount: BigNumber,
|
||||||
@@ -272,6 +289,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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: prefer-for-of
|
// tslint:disable-next-line: prefer-for-of
|
||||||
for (let i = 0; i < data.fillSequence.length; ++i) {
|
for (let i = 0; i < data.fillSequence.length; ++i) {
|
||||||
const orderType = data.fillSequence[i];
|
const orderType = data.fillSequence[i];
|
||||||
@@ -301,6 +336,11 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
results = fillRfqOrder(data.rfqOrders[orderIndices[orderType]]);
|
results = fillRfqOrder(data.rfqOrders[orderIndices[orderType]]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OrderType.Otc:
|
||||||
|
{
|
||||||
|
results = fillOtcOrder(data.otcOrders[orderIndices[orderType]]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Unknown order type');
|
throw new Error('Unknown order type');
|
||||||
}
|
}
|
||||||
@@ -393,6 +433,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,
|
||||||
@@ -446,7 +487,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
await assertCurrentBalancesAsync(exchange.address, { ...ZERO_BALANCES, ethBalance: qfr.protocolFeePaid });
|
await assertCurrentBalancesAsync(exchange.address, { ...ZERO_BALANCES, ethBalance: qfr.protocolFeePaid });
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('sell quotes', () => {
|
describe.only('sell quotes', () => {
|
||||||
it('can fully sell to a single bridge order with -1 fillAmount', async () => {
|
it('can fully sell to a single bridge order with -1 fillAmount', async () => {
|
||||||
const bridgeOrders = [createBridgeOrder()];
|
const bridgeOrders = [createBridgeOrder()];
|
||||||
const data = createTransformData({
|
const data = createTransformData({
|
||||||
@@ -662,6 +703,44 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
return assertFinalBalancesAsync(qfr);
|
return assertFinalBalancesAsync(qfr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('can fully buy to a single OTC order', async () => {
|
||||||
|
const ethersMakerWallet = ethers.Wallet.createRandom();
|
||||||
|
const _otcOrder = createOtcOrder({maker: ethersMakerWallet.address});
|
||||||
|
await makerToken.mint(ethersMakerWallet.address, MAX_UINT256).awaitTransactionSuccessAsync();
|
||||||
|
await takerToken.mint(taker, MAX_UINT256).awaitTransactionSuccessAsync();
|
||||||
|
let balMaker = await makerToken.balanceOf(ethersMakerWallet.address).callAsync();
|
||||||
|
let balTaker = await takerToken.balanceOf(taker).callAsync();
|
||||||
|
const otcOrders = [_otcOrder];
|
||||||
|
const totalTakerTokens = BigNumber.sum(...otcOrders.map(o => o.takerAmount));
|
||||||
|
const _otcOrderSignature = await ethersMakerWallet.signMessage(ethers.utils.arrayify(_otcOrder.getHash()));
|
||||||
|
const { v, r, s } = ethers.utils.splitSignature(_otcOrderSignature);
|
||||||
|
const _orderSignature : Signature = {
|
||||||
|
v : v!,
|
||||||
|
r,
|
||||||
|
s,
|
||||||
|
signatureType: 3
|
||||||
|
};
|
||||||
|
const data = createTransformData({
|
||||||
|
side: Side.Buy,
|
||||||
|
otcOrders: otcOrders.map(o => ({
|
||||||
|
order: o,
|
||||||
|
maxTakerTokenFillAmount: MAX_UINT256,
|
||||||
|
signature: _orderSignature,
|
||||||
|
})),
|
||||||
|
fillAmount: BigNumber.sum(...otcOrders.map(o => o.makerAmount)),
|
||||||
|
fillSequence: otcOrders.map(() => OrderType.Otc),
|
||||||
|
});
|
||||||
|
const qfr = getExpectedQuoteFillResults(
|
||||||
|
data,
|
||||||
|
createSimulationState({ takerTokenBalance: totalTakerTokens }),
|
||||||
|
);
|
||||||
|
await executeTransformAsync({
|
||||||
|
data,
|
||||||
|
takerTokenBalance: qfr.takerTokensSpent,
|
||||||
|
});
|
||||||
|
return assertFinalBalancesAsync(qfr);
|
||||||
|
});
|
||||||
|
|
||||||
it('can fully sell to one of each order type', async () => {
|
it('can fully sell to one of each order type', async () => {
|
||||||
const rfqOrders = [createRfqOrder()];
|
const rfqOrders = [createRfqOrder()];
|
||||||
const limitOrders = [createLimitOrder()];
|
const limitOrders = [createLimitOrder()];
|
||||||
@@ -1117,6 +1196,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,12 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "16.67.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add Otc order support to the fillQuoteTransformer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "16.66.4",
|
"version": "16.66.4",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -40,12 +40,14 @@ import {
|
|||||||
FinalUniswapV3FillData,
|
FinalUniswapV3FillData,
|
||||||
LiquidityProviderFillData,
|
LiquidityProviderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
|
NativeOtcOrderFillData,
|
||||||
OptimizedMarketBridgeOrder,
|
OptimizedMarketBridgeOrder,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
UniswapV2FillData,
|
UniswapV2FillData,
|
||||||
} from '../utils/market_operation_utils/types';
|
} from '../utils/market_operation_utils/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
multiplexOtcOrder,
|
||||||
multiplexPlpEncoder,
|
multiplexPlpEncoder,
|
||||||
multiplexRfqEncoder,
|
multiplexRfqEncoder,
|
||||||
MultiplexSubcall,
|
MultiplexSubcall,
|
||||||
@@ -312,18 +314,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
|
||||||
@@ -346,6 +337,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(
|
||||||
@@ -539,11 +570,12 @@ 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({
|
subcalls.push({
|
||||||
id: MultiplexSubcall.Rfq,
|
id: MultiplexSubcall.Rfq,
|
||||||
sellAmount: order.takerAmount,
|
sellAmount: order.takerAmount,
|
||||||
@@ -552,6 +584,16 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
|||||||
signature: order.fillData.signature,
|
signature: order.fillData.signature,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
subcalls.push({
|
||||||
|
id: MultiplexSubcall.Otc,
|
||||||
|
sellAmount: order.takerAmount,
|
||||||
|
data: multiplexOtcOrder.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:
|
||||||
@@ -702,6 +744,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' },
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ERC20BridgeSource,
|
ERC20BridgeSource,
|
||||||
NativeLimitOrderFillData,
|
NativeLimitOrderFillData,
|
||||||
|
NativeOtcOrderFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
OptimizedMarketBridgeOrder,
|
OptimizedMarketBridgeOrder,
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
@@ -107,17 +108,25 @@ function isOptimizedRfqOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrder
|
|||||||
return x.type === FillQuoteTransformerOrderType.Rfq;
|
return x.type === FillQuoteTransformerOrderType.Rfq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOptimizedOtcOrder(x: OptimizedMarketOrder): x is OptimizedMarketOrderBase<NativeOtcOrderFillData> {
|
||||||
|
return x.type === FillQuoteTransformerOrderType.Otc;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
* Converts the given `OptimizedMarketOrder`s into bridge, limit, and RFQ orders for
|
||||||
* FillQuoteTransformer.
|
* FillQuoteTransformer.
|
||||||
*/
|
*/
|
||||||
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: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,6 +150,12 @@ export function getFQTTransformerDataFromOptimizedOrders(
|
|||||||
signature: order.fillData.signature,
|
signature: order.fillData.signature,
|
||||||
maxTakerTokenFillAmount: order.takerAmount,
|
maxTakerTokenFillAmount: order.takerAmount,
|
||||||
});
|
});
|
||||||
|
} else if (isOptimizedOtcOrder(order)) {
|
||||||
|
fqtData.otcOrders.push({
|
||||||
|
order: order.fillData.order,
|
||||||
|
signature: order.fillData.signature,
|
||||||
|
maxTakerTokenFillAmount: order.takerAmount,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
throw new Error('Unknown Order type');
|
throw new Error('Unknown Order type');
|
||||||
|
@@ -2,6 +2,7 @@ import { ChainId } from '@0x/contract-addresses';
|
|||||||
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';
|
||||||
@@ -194,7 +195,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> {
|
||||||
@@ -450,7 +452,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;
|
||||||
|
@@ -353,7 +353,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,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, RfqOrder, RfqOrderFields, OtcOrder, OtcOrderFields } 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;
|
||||||
@@ -178,6 +199,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