Merge branch 'master' into schema-tests
This commit is contained in:
@@ -28,6 +28,7 @@ import {utils} from '../utils/utils';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
|
||||
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
|
||||
import {signedOrdersSchema} from '../schemas/signed_orders_schema';
|
||||
import {orderFillRequestsSchema} from '../schemas/order_fill_requests_schema';
|
||||
import {orderCancellationRequestsSchema} from '../schemas/order_cancel_schema';
|
||||
import {orderFillOrKillRequestsSchema} from '../schemas/order_fill_or_kill_requests_schema';
|
||||
@@ -162,6 +163,72 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
);
|
||||
this.throwErrorLogsAsErrors(response.logs);
|
||||
}
|
||||
/**
|
||||
* Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
|
||||
* If the fill amount is reached - it succeeds and does not fill the rest of the orders.
|
||||
* If fill amount is not reached - it fills as much of the fill amount as possible and succeeds.
|
||||
*/
|
||||
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], takerTokenFillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, takerAddress: string): Promise<void> {
|
||||
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
|
||||
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
|
||||
ExchangeContractErrs.MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED);
|
||||
assert.isBigNumber('takerTokenFillAmount', takerTokenFillAmount);
|
||||
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
||||
assert.doesConformToSchema(
|
||||
'signedOrders', SchemaValidator.convertToJSONSchemaCompatibleObject(signedOrders), signedOrdersSchema
|
||||
);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this.web3Wrapper);
|
||||
_.forEach(signedOrders,
|
||||
async (signedOrder: SignedOrder, i: number) => {
|
||||
await this.validateFillOrderAndThrowIfInvalidAsync(
|
||||
signedOrder, takerTokenFillAmount, takerAddress);
|
||||
});
|
||||
if (_.isEmpty(signedOrders)) {
|
||||
return; // no-op
|
||||
}
|
||||
|
||||
const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
|
||||
return [
|
||||
...ExchangeWrapper.getOrderAddressesAndValues(signedOrder),
|
||||
signedOrder.ecSignature.v,
|
||||
signedOrder.ecSignature.r,
|
||||
signedOrder.ecSignature.s,
|
||||
];
|
||||
});
|
||||
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
|
||||
const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>(
|
||||
orderAddressesValuesAndSignatureArray,
|
||||
);
|
||||
|
||||
const exchangeInstance = await this.getExchangeContractAsync();
|
||||
const gas = await exchangeInstance.fillUpTo.estimateGas(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
{
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillUpTo(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
{
|
||||
from: takerAddress,
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this.throwErrorLogsAsErrors(response.logs);
|
||||
}
|
||||
/**
|
||||
* Batch version of fillOrderAsync.
|
||||
* Executes multiple fills atomically in a single transaction.
|
||||
@@ -366,11 +433,8 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* All orders must be from the same maker.
|
||||
*/
|
||||
public async batchCancelOrderAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
|
||||
if (_.isEmpty(orderCancellationRequests)) {
|
||||
return; // no-op
|
||||
}
|
||||
const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker);
|
||||
assert.assert(_.uniq(makers).length === 1, ExchangeContractErrs.MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH);
|
||||
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED);
|
||||
const maker = makers[0];
|
||||
await assert.isSenderAddressAsync('maker', maker, this.web3Wrapper);
|
||||
assert.doesConformToSchema('orderCancellationRequests',
|
||||
@@ -381,6 +445,9 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
|
||||
);
|
||||
});
|
||||
if (_.isEmpty(orderCancellationRequests)) {
|
||||
return; // no-op
|
||||
}
|
||||
const exchangeInstance = await this.getExchangeContractAsync();
|
||||
const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => {
|
||||
return [
|
||||
|
||||
5
src/schemas/signed_orders_schema.ts
Normal file
5
src/schemas/signed_orders_schema.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const signedOrdersSchema = {
|
||||
id: '/signedOrdersSchema',
|
||||
type: 'array',
|
||||
items: {$ref: '/signedOrderSchema'},
|
||||
};
|
||||
@@ -74,6 +74,12 @@ export interface ExchangeContract extends ContractInstance {
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmounts: BigNumber.BigNumber[],
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts) => number;
|
||||
};
|
||||
fillUpTo: {
|
||||
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts) => number;
|
||||
};
|
||||
cancel: {
|
||||
(orderAddresses: OrderAddresses, orderValues: OrderValues, cancelAmount: BigNumber.BigNumber,
|
||||
txOpts?: TxOpts): ContractResponse;
|
||||
@@ -163,8 +169,9 @@ export const ExchangeContractErrs = strEnum([
|
||||
'INSUFFICIENT_MAKER_FEE_BALANCE',
|
||||
'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
|
||||
'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
|
||||
'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH',
|
||||
'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
|
||||
'INSUFFICIENT_REMAINING_FILL_AMOUNT',
|
||||
'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
|
||||
]);
|
||||
export type ExchangeContractErrs = keyof typeof ExchangeContractErrs;
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ export const assert = {
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 instance');
|
||||
},
|
||||
hasAtMostOneUniqueValue(value: any[], errMsg: string): void {
|
||||
this.assert(_.uniq(value).length <= 1, errMsg);
|
||||
},
|
||||
isNumber(variableName: string, value: number): void {
|
||||
this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value));
|
||||
},
|
||||
|
||||
@@ -392,6 +392,40 @@ describe('ExchangeWrapper', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#fillOrdersUpTo', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let signedOrderHashHex: string;
|
||||
let anotherSignedOrder: SignedOrder;
|
||||
let anotherOrderHashHex: string;
|
||||
let signedOrders: SignedOrder[];
|
||||
const fillUpToAmount = fillableAmount.plus(fillableAmount).minus(1);
|
||||
beforeEach(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
signedOrderHashHex = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
anotherOrderHashHex = await zeroEx.getOrderHashHexAsync(anotherSignedOrder);
|
||||
signedOrders = [signedOrder, anotherSignedOrder];
|
||||
});
|
||||
describe('successful batch fills', () => {
|
||||
it('should no-op for an empty batch', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync([], fillUpToAmount, shouldCheckTransfer, takerAddress);
|
||||
});
|
||||
it('should successfully fill up to specified amount', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, fillUpToAmount, shouldCheckTransfer, takerAddress,
|
||||
);
|
||||
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
|
||||
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
|
||||
expect(filledAmount).to.be.bignumber.equal(fillableAmount);
|
||||
const remainingFillAmount = fillableAmount.minus(1);
|
||||
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('cancel order(s)', () => {
|
||||
let makerTokenAddress: string;
|
||||
|
||||
Reference in New Issue
Block a user