Add back batchFillOrders

This commit is contained in:
Amir Bandeali 2019-09-03 11:06:45 -07:00
parent ea8669439f
commit cb8cf1f107
9 changed files with 220 additions and 3 deletions

View File

@ -48,6 +48,8 @@ contract LibTransactionDecoder {
if (functionSelector == IExchange(address(0)).batchCancelOrders.selector) {
functionName = "batchCancelOrders";
} else if (functionSelector == IExchange(address(0)).batchFillOrders.selector) {
functionName = "batchFillOrders";
} else if (functionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector) {
functionName = "batchFillOrdersNoThrow";
} else if (functionSelector == IExchange(address(0)).batchFillOrKillOrders.selector) {
@ -84,6 +86,7 @@ contract LibTransactionDecoder {
signatures = new bytes[](0);
} else if (
functionSelector == IExchange(address(0)).batchFillOrKillOrders.selector ||
functionSelector == IExchange(address(0)).batchFillOrders.selector ||
functionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector
) {
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForBatchFill(transactionData);

View File

@ -56,7 +56,7 @@ describe('LibTransactionDecoder', () => {
]);
});
for (const func of ['batchFillOrdersNoThrow', 'batchFillOrKillOrders']) {
for (const func of ['batchFillOrders', 'batchFillOrdersNoThrow', 'batchFillOrKillOrders']) {
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
[order, order],
[takerAssetFillAmount, takerAssetFillAmount],

View File

@ -59,6 +59,34 @@ contract MixinWrapperFunctions is
return fillResults;
}
/// @dev Executes multiple calls of fillOrder.
/// @param orders Array of order specifications.
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
/// @param signatures Proofs that orders have been created by makers.
/// @return Array of amounts filled and fees paid by makers and taker.
function batchFillOrders(
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
public
payable
nonReentrant
refundFinalBalance
returns (LibFillResults.FillResults[] memory fillResults)
{
uint256 ordersLength = orders.length;
fillResults = new LibFillResults.FillResults[](ordersLength);
for (uint256 i = 0; i != ordersLength; i++) {
fillResults[i] = _fillOrder(
orders[i],
takerAssetFillAmounts[i],
signatures[i]
);
}
return fillResults;
}
/// @dev Executes multiple calls of fillOrKill.
/// @param orders Array of order specifications.
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.

View File

@ -38,6 +38,20 @@ contract IWrapperFunctions {
payable
returns (LibFillResults.FillResults memory fillResults);
/// @dev Executes multiple calls of fillOrder.
/// @param orders Array of order specifications.
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
/// @param signatures Proofs that orders have been created by makers.
/// @return Array of amounts filled and fees paid by makers and taker.
function batchFillOrders(
LibOrder.Order[] memory orders,
uint256[] memory takerAssetFillAmounts,
bytes[] memory signatures
)
public
payable
returns (LibFillResults.FillResults[] memory fillResults);
/// @dev Synchronously executes multiple calls of fillOrKill.
/// @param orders Array of order specifications.
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.

View File

@ -84,7 +84,7 @@ contract TestProtocolFeesReceiver {
}
// Forward all of the value sent to the contract to `batchFillOrders()`.
testProtocolFees.batchFillOrKillOrders.value(msg.value)(orders, takerAssetFilledAmounts, signatures);
testProtocolFees.batchFillOrders.value(msg.value)(orders, takerAssetFilledAmounts, signatures);
// If the `protocolFeeCollector` was set, ensure that the protocol fees were paid correctly.
// Otherwise, the protocol fees should not have been paid.

View File

@ -13,7 +13,11 @@ export const constants = {
ExchangeFunctionName.SetProtocolFeeCollectorAddress,
],
SINGLE_FILL_FN_NAMES: [ExchangeFunctionName.FillOrder, ExchangeFunctionName.FillOrKillOrder],
BATCH_FILL_FN_NAMES: [ExchangeFunctionName.BatchFillOrKillOrders, ExchangeFunctionName.BatchFillOrdersNoThrow],
BATCH_FILL_FN_NAMES: [
ExchangeFunctionName.BatchFillOrders,
ExchangeFunctionName.BatchFillOrKillOrders,
ExchangeFunctionName.BatchFillOrdersNoThrow,
],
MARKET_FILL_FN_NAMES: [
ExchangeFunctionName.MarketBuyOrdersFillOrKill,
ExchangeFunctionName.MarketSellOrdersFillOrKill,

View File

@ -54,6 +54,20 @@ export class ExchangeWrapper {
);
return txReceipt;
}
public async batchFillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[]; gasPrice?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
return this._exchange.batchFillOrders.awaitTransactionSuccessAsync(
orders,
opts.takerAssetFillAmounts === undefined
? orders.map(signedOrder => signedOrder.takerAssetAmount)
: opts.takerAssetFillAmounts,
orders.map(signedOrder => signedOrder.signature),
{ from, gasPrice: opts.gasPrice },
);
}
public async batchFillOrKillOrdersAsync(
orders: SignedOrder[],
from: string,

View File

@ -216,6 +216,74 @@ blockchainTests.resets('Exchange wrappers', env => {
];
});
describe('batchFillOrders', () => {
it('should transfer the correct amounts', async () => {
const makerAssetAddress = erc20TokenA.address;
const takerAssetAddress = erc20TokenB.address;
const takerAssetFillAmounts: BigNumber[] = [];
const expectedFillResults: FillResults[] = [];
_.forEach(signedOrders, signedOrder => {
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
const makerAssetFilledAmount = takerAssetFillAmount
.times(signedOrder.makerAssetAmount)
.dividedToIntegerBy(signedOrder.takerAssetAmount);
const makerFee = signedOrder.makerFee
.times(makerAssetFilledAmount)
.dividedToIntegerBy(signedOrder.makerAssetAmount);
const takerFee = signedOrder.takerFee
.times(makerAssetFilledAmount)
.dividedToIntegerBy(signedOrder.makerAssetAmount);
takerAssetFillAmounts.push(takerAssetFillAmount);
expectedFillResults.push({
takerAssetFilledAmount: takerAssetFillAmount,
makerAssetFilledAmount,
makerFeePaid: makerFee,
takerFeePaid: takerFee,
protocolFeePaid: constants.ZERO_AMOUNT,
});
erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][
makerAssetAddress
].minus(makerAssetFilledAmount);
erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][
takerAssetAddress
].plus(takerAssetFillAmount);
erc20Balances[makerAddress][feeToken.address] = erc20Balances[makerAddress][feeToken.address].minus(
makerFee,
);
erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][
makerAssetAddress
].plus(makerAssetFilledAmount);
erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][
takerAssetAddress
].minus(takerAssetFillAmount);
erc20Balances[takerAddress][feeToken.address] = erc20Balances[takerAddress][feeToken.address].minus(
takerFee,
);
erc20Balances[feeRecipientAddress][feeToken.address] = erc20Balances[feeRecipientAddress][
feeToken.address
].plus(makerFee.plus(takerFee));
});
const fillResults = await exchange.batchFillOrders.callAsync(
signedOrders,
takerAssetFillAmounts,
signedOrders.map(signedOrder => signedOrder.signature),
{ from: takerAddress },
);
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmounts,
});
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(fillResults).to.deep.equal(expectedFillResults);
expect(newBalances).to.be.deep.equal(erc20Balances);
});
});
describe('batchFillOrKillOrders', () => {
it('should transfer the correct amounts', async () => {
const makerAssetAddress = erc20TokenA.address;

View File

@ -251,6 +251,92 @@ blockchainTests('Exchange wrapper functions unit tests.', env => {
});
});
describe('batchFillOrders', () => {
it('works with no fills', async () => {
const [actualResult, receipt] = await txHelper.getResultAndReceiptAsync(
testContract.batchFillOrders,
[],
[],
[],
);
expect(actualResult).to.deep.eq([]);
assertFillOrderCallsFromLogs(receipt.logs, []);
});
it('works with one fill', async () => {
const COUNT = 1;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const [actualResult, receipt] = await txHelper.getResultAndReceiptAsync(
testContract.batchFillOrders,
orders,
fillAmounts,
signatures,
);
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with many fills', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const [actualResult, receipt] = await txHelper.getResultAndReceiptAsync(
testContract.batchFillOrders,
orders,
fillAmounts,
signatures,
);
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('works with duplicate orders', async () => {
const NUM_UNIQUE_ORDERS = 2;
const COUNT = 4;
const uniqueOrders = _.times(NUM_UNIQUE_ORDERS, () => randomOrder());
const orders = _.shuffle(_.flatten(_.times(COUNT / NUM_UNIQUE_ORDERS, () => uniqueOrders)));
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount.dividedToIntegerBy(COUNT));
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedResult = _.times(COUNT, i => getExpectedFillResults(orders[i], signatures[i]));
const expectedCalls = _.zip(orders, fillAmounts, signatures);
const [actualResult, receipt] = await txHelper.getResultAndReceiptAsync(
testContract.batchFillOrders,
orders,
fillAmounts,
signatures,
);
expect(actualResult).to.deep.eq(expectedResult);
assertFillOrderCallsFromLogs(receipt.logs, expectedCalls as any);
});
it('reverts if there are more orders than fill amounts', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT - 1, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = txHelper.getResultAndReceiptAsync(testContract.batchFillOrders, orders, fillAmounts, signatures);
return expect(tx).to.revertWith(expectedError);
});
it('reverts if there are more orders than signatures', async () => {
const COUNT = 8;
const orders = _.times(COUNT, () => randomOrder());
const fillAmounts = _.times(COUNT, i => orders[i].takerAssetAmount);
const signatures = _.times(COUNT - 1, i => createOrderSignature(orders[i]));
const expectedError = new AnyRevertError(); // Just a generic revert.
const tx = txHelper.getResultAndReceiptAsync(testContract.batchFillOrders, orders, fillAmounts, signatures);
return expect(tx).to.revertWith(expectedError);
});
});
describe('batchFillOrKillOrders', () => {
it('works with no fills', async () => {
const [actualResult, receipt] = await txHelper.getResultAndReceiptAsync(