Merge branch 'master' into addEventSubscriptions
# Conflicts: # src/contract_wrappers/exchange_wrapper.ts # src/types.ts # test/exchange_wrapper_test.ts
This commit is contained in:
@@ -135,7 +135,7 @@ export class ZeroEx {
|
|||||||
return senderAccountIfExists;
|
return senderAccountIfExists;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Computes the orderHash given the order parameters and returns it as a hex encoded string.
|
* Computes the orderHash for a given order and returns it as a hex encoded string.
|
||||||
*/
|
*/
|
||||||
public async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> {
|
public async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> {
|
||||||
const exchangeContractAddr = await this.getExchangeAddressAsync();
|
const exchangeContractAddr = await this.getExchangeAddressAsync();
|
||||||
|
@@ -7,7 +7,6 @@ import {
|
|||||||
ExchangeContract,
|
ExchangeContract,
|
||||||
ExchangeContractErrCodes,
|
ExchangeContractErrCodes,
|
||||||
ExchangeContractErrs,
|
ExchangeContractErrs,
|
||||||
FillOrderValidationErrs,
|
|
||||||
OrderValues,
|
OrderValues,
|
||||||
OrderAddresses,
|
OrderAddresses,
|
||||||
SignedOrder,
|
SignedOrder,
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
CreateContractEvent,
|
CreateContractEvent,
|
||||||
ContractEventObj,
|
ContractEventObj,
|
||||||
EventCallback,
|
EventCallback,
|
||||||
|
ContractResponse,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {assert} from '../utils/assert';
|
import {assert} from '../utils/assert';
|
||||||
import {utils} from '../utils/utils';
|
import {utils} from '../utils/utils';
|
||||||
@@ -27,18 +27,17 @@ import * as ExchangeArtifacts from '../artifacts/Exchange.json';
|
|||||||
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
|
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
|
||||||
import {signedOrderSchema} from '../schemas/order_schemas';
|
import {signedOrderSchema} from '../schemas/order_schemas';
|
||||||
import {SchemaValidator} from '../utils/schema_validator';
|
import {SchemaValidator} from '../utils/schema_validator';
|
||||||
import {ContractResponse} from '../types';
|
|
||||||
import {constants} from '../utils/constants';
|
import {constants} from '../utils/constants';
|
||||||
import {TokenWrapper} from './token_wrapper';
|
import {TokenWrapper} from './token_wrapper';
|
||||||
|
|
||||||
export class ExchangeWrapper extends ContractWrapper {
|
export class ExchangeWrapper extends ContractWrapper {
|
||||||
private exchangeContractErrCodesToMsg = {
|
private exchangeContractErrCodesToMsg = {
|
||||||
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.ORDER_EXPIRED,
|
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.ORDER_FILL_EXPIRED,
|
||||||
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.ORDER_EXPIRED,
|
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.ORDER_FILL_EXPIRED,
|
||||||
[ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
[ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
||||||
[ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
[ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
||||||
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.ORDER_ROUNDING_ERROR,
|
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR,
|
||||||
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.ORDER_BALANCE_ALLOWANCE_ERROR,
|
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FILL_BALANCE_ALLOWANCE_ERROR,
|
||||||
};
|
};
|
||||||
private exchangeContractIfExists?: ExchangeContract;
|
private exchangeContractIfExists?: ExchangeContract;
|
||||||
private exchangeLogEventObjs: ContractEventObj[];
|
private exchangeLogEventObjs: ContractEventObj[];
|
||||||
@@ -112,22 +111,24 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
return cancelledAmountInBaseUnits;
|
return cancelledAmountInBaseUnits;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Fills a signed order with a fillAmount denominated in baseUnits of the taker token. The caller can
|
* Fills a signed order with a fillAmount denominated in baseUnits of the taker token.
|
||||||
* decide whether they want the call to throw if the balance/allowance checks fail by setting
|
* Since the order in which transactions are included in the next block is indeterminate, race-conditions
|
||||||
* shouldCheckTransfer to false. If set to true, the call will fail without throwing, preserving gas costs.
|
* could arise where a users balance or allowance changes before the fillOrder executes. Because of this,
|
||||||
|
* we allow you to specify `shouldCheckTransfer`. If true, the smart contract will not throw if while
|
||||||
|
* executing, the parties do not have sufficient balances/allowances, preserving gas costs. Setting it to
|
||||||
|
* false forgoes this check and causes the smart contract to throw instead.
|
||||||
*/
|
*/
|
||||||
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerAmountInBaseUnits: BigNumber.BigNumber,
|
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber,
|
||||||
shouldCheckTransfer: boolean): Promise<void> {
|
shouldCheckTransfer: boolean): Promise<void> {
|
||||||
assert.doesConformToSchema('signedOrder',
|
assert.doesConformToSchema('signedOrder',
|
||||||
SchemaValidator.convertToJSONSchemaCompatibleObject(signedOrder as object),
|
SchemaValidator.convertToJSONSchemaCompatibleObject(signedOrder as object),
|
||||||
signedOrderSchema);
|
signedOrderSchema);
|
||||||
assert.isBigNumber('fillTakerAmountInBaseUnits', fillTakerAmountInBaseUnits);
|
assert.isBigNumber('fillTakerAmount', fillTakerAmount);
|
||||||
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
||||||
|
|
||||||
const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync();
|
const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync();
|
||||||
await this.validateFillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, senderAddress);
|
|
||||||
|
|
||||||
const exchangeInstance = await this.getExchangeContractAsync();
|
const exchangeInstance = await this.getExchangeContractAsync();
|
||||||
|
await this.validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerAmount, senderAddress);
|
||||||
|
|
||||||
const orderAddresses: OrderAddresses = [
|
const orderAddresses: OrderAddresses = [
|
||||||
signedOrder.maker,
|
signedOrder.maker,
|
||||||
@@ -147,7 +148,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
const gas = await exchangeInstance.fill.estimateGas(
|
const gas = await exchangeInstance.fill.estimateGas(
|
||||||
orderAddresses,
|
orderAddresses,
|
||||||
orderValues,
|
orderValues,
|
||||||
fillTakerAmountInBaseUnits,
|
fillTakerAmount,
|
||||||
shouldCheckTransfer,
|
shouldCheckTransfer,
|
||||||
signedOrder.ecSignature.v,
|
signedOrder.ecSignature.v,
|
||||||
signedOrder.ecSignature.r,
|
signedOrder.ecSignature.r,
|
||||||
@@ -159,7 +160,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
const response: ContractResponse = await exchangeInstance.fill(
|
const response: ContractResponse = await exchangeInstance.fill(
|
||||||
orderAddresses,
|
orderAddresses,
|
||||||
orderValues,
|
orderValues,
|
||||||
fillTakerAmountInBaseUnits,
|
fillTakerAmount,
|
||||||
shouldCheckTransfer,
|
shouldCheckTransfer,
|
||||||
signedOrder.ecSignature.v,
|
signedOrder.ecSignature.v,
|
||||||
signedOrder.ecSignature.r,
|
signedOrder.ecSignature.r,
|
||||||
@@ -203,39 +204,89 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
}
|
}
|
||||||
this.exchangeLogEventObjs = [];
|
this.exchangeLogEventObjs = [];
|
||||||
}
|
}
|
||||||
private async validateFillOrderAsync(signedOrder: SignedOrder, fillTakerAmountInBaseUnits: BigNumber.BigNumber,
|
private async validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||||
senderAddress: string) {
|
fillTakerAmount: BigNumber.BigNumber,
|
||||||
if (fillTakerAmountInBaseUnits.eq(0)) {
|
senderAddress: string): Promise<void> {
|
||||||
throw new Error(FillOrderValidationErrs.FILL_AMOUNT_IS_ZERO);
|
if (fillTakerAmount.eq(0)) {
|
||||||
|
throw new Error(ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO);
|
||||||
}
|
}
|
||||||
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== senderAddress) {
|
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== senderAddress) {
|
||||||
throw new Error(FillOrderValidationErrs.NOT_A_TAKER);
|
throw new Error(ExchangeContractErrs.TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER);
|
||||||
}
|
}
|
||||||
if (signedOrder.expirationUnixTimestampSec.lessThan(Date.now() / 1000)) {
|
const currentUnixTimestampSec = Date.now() / 1000;
|
||||||
throw new Error(FillOrderValidationErrs.EXPIRED);
|
if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||||
|
throw new Error(ExchangeContractErrs.ORDER_FILL_EXPIRED);
|
||||||
}
|
}
|
||||||
|
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||||
|
await this.validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync(signedOrder, fillTakerAmount,
|
||||||
|
senderAddress, zrxTokenAddress);
|
||||||
|
|
||||||
|
const wouldRoundingErrorOccur = await this.isRoundingErrorAsync(
|
||||||
|
signedOrder.takerTokenAmount, fillTakerAmount, signedOrder.makerTokenAmount,
|
||||||
|
);
|
||||||
|
if (wouldRoundingErrorOccur) {
|
||||||
|
throw new Error(ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method does not currently validate the edge-case where the makerToken or takerToken is also the token used
|
||||||
|
* to pay fees (ZRX). It is possible for them to have enough for fees and the transfer but not both.
|
||||||
|
* Handling the edge-cases that arise when this happens would require making sure that the user has sufficient
|
||||||
|
* funds to pay both the fees and the transfer amount. We decided to punt on this for now as the contracts
|
||||||
|
* will throw for these edge-cases.
|
||||||
|
* TODO: Throw errors before calling the smart contract for these edge-cases
|
||||||
|
* TODO: in order to minimize the callers gas costs.
|
||||||
|
*/
|
||||||
|
private async validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||||
|
fillTakerAmount: BigNumber.BigNumber,
|
||||||
|
senderAddress: string,
|
||||||
|
zrxTokenAddress: string): Promise<void> {
|
||||||
|
|
||||||
const makerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.makerTokenAddress,
|
const makerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.makerTokenAddress,
|
||||||
signedOrder.maker);
|
signedOrder.maker);
|
||||||
const takerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.takerTokenAddress, senderAddress);
|
const takerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.takerTokenAddress, senderAddress);
|
||||||
const makerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(signedOrder.makerTokenAddress,
|
const makerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(signedOrder.makerTokenAddress,
|
||||||
signedOrder.maker);
|
signedOrder.maker);
|
||||||
const takerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(signedOrder.takerTokenAddress,
|
const takerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(signedOrder.takerTokenAddress,
|
||||||
senderAddress);
|
senderAddress);
|
||||||
// How many taker tokens would you get for 1 maker token;
|
|
||||||
const exchangeRate = signedOrder.takerTokenAmount.div(signedOrder.makerTokenAmount);
|
|
||||||
const fillMakerAmountInBaseUnits = fillTakerAmountInBaseUnits.div(exchangeRate);
|
|
||||||
|
|
||||||
if (fillTakerAmountInBaseUnits.greaterThan(takerBalance)) {
|
// exchangeRate is the price of one maker token denominated in taker tokens
|
||||||
throw new Error(FillOrderValidationErrs.NOT_ENOUGH_TAKER_BALANCE);
|
const exchangeRate = signedOrder.takerTokenAmount.div(signedOrder.makerTokenAmount);
|
||||||
|
const fillMakerAmountInBaseUnits = fillTakerAmount.div(exchangeRate);
|
||||||
|
|
||||||
|
if (fillTakerAmount.greaterThan(takerBalance)) {
|
||||||
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_BALANCE);
|
||||||
}
|
}
|
||||||
if (fillTakerAmountInBaseUnits.greaterThan(takerAllowance)) {
|
if (fillTakerAmount.greaterThan(takerAllowance)) {
|
||||||
throw new Error(FillOrderValidationErrs.NOT_ENOUGH_TAKER_ALLOWANCE);
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_ALLOWANCE);
|
||||||
}
|
}
|
||||||
if (fillMakerAmountInBaseUnits.greaterThan(makerBalance)) {
|
if (fillMakerAmountInBaseUnits.greaterThan(makerBalance)) {
|
||||||
throw new Error(FillOrderValidationErrs.NOT_ENOUGH_MAKER_BALANCE);
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_BALANCE);
|
||||||
}
|
}
|
||||||
if (fillMakerAmountInBaseUnits.greaterThan(makerAllowance)) {
|
if (fillMakerAmountInBaseUnits.greaterThan(makerAllowance)) {
|
||||||
throw new Error(FillOrderValidationErrs.NOT_ENOUGH_MAKER_ALLOWANCE);
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_ALLOWANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const makerFeeBalance = await this.tokenWrapper.getBalanceAsync(zrxTokenAddress,
|
||||||
|
signedOrder.maker);
|
||||||
|
const takerFeeBalance = await this.tokenWrapper.getBalanceAsync(zrxTokenAddress, senderAddress);
|
||||||
|
const makerFeeAllowance = await this.tokenWrapper.getProxyAllowanceAsync(zrxTokenAddress,
|
||||||
|
signedOrder.maker);
|
||||||
|
const takerFeeAllowance = await this.tokenWrapper.getProxyAllowanceAsync(zrxTokenAddress,
|
||||||
|
senderAddress);
|
||||||
|
|
||||||
|
if (signedOrder.takerFee.greaterThan(takerFeeBalance)) {
|
||||||
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_BALANCE);
|
||||||
|
}
|
||||||
|
if (signedOrder.takerFee.greaterThan(takerFeeAllowance)) {
|
||||||
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_ALLOWANCE);
|
||||||
|
}
|
||||||
|
if (signedOrder.makerFee.greaterThan(makerFeeBalance)) {
|
||||||
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_BALANCE);
|
||||||
|
}
|
||||||
|
if (signedOrder.makerFee.greaterThan(makerFeeAllowance)) {
|
||||||
|
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_ALLOWANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private throwErrorLogsAsErrors(logs: ContractEvent[]): void {
|
private throwErrorLogsAsErrors(logs: ContractEvent[]): void {
|
||||||
@@ -246,6 +297,18 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
throw new Error(errMessage);
|
throw new Error(errMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private async isRoundingErrorAsync(takerTokenAmount: BigNumber.BigNumber,
|
||||||
|
fillTakerAmount: BigNumber.BigNumber,
|
||||||
|
makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
|
||||||
|
const exchangeInstance = await this.getExchangeContractAsync();
|
||||||
|
const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync();
|
||||||
|
const isRoundingError = await exchangeInstance.isRoundingError.call(
|
||||||
|
takerTokenAmount, fillTakerAmount, makerTokenAmount, {
|
||||||
|
from: senderAddress,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return isRoundingError;
|
||||||
|
}
|
||||||
private async getExchangeContractAsync(): Promise<ExchangeContract> {
|
private async getExchangeContractAsync(): Promise<ExchangeContract> {
|
||||||
if (!_.isUndefined(this.exchangeContractIfExists)) {
|
if (!_.isUndefined(this.exchangeContractIfExists)) {
|
||||||
return this.exchangeContractIfExists;
|
return this.exchangeContractIfExists;
|
||||||
@@ -254,4 +317,8 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
this.exchangeContractIfExists = contractInstance as ExchangeContract;
|
this.exchangeContractIfExists = contractInstance as ExchangeContract;
|
||||||
return this.exchangeContractIfExists;
|
return this.exchangeContractIfExists;
|
||||||
}
|
}
|
||||||
|
private async getZRXTokenAddressAsync(): Promise<string> {
|
||||||
|
const exchangeInstance = await this.getExchangeContractAsync();
|
||||||
|
return exchangeInstance.ZRX.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
45
src/types.ts
45
src/types.ts
@@ -10,11 +10,12 @@ function strEnum(values: string[]): {[key: string]: string} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ZeroExError = strEnum([
|
export const ZeroExError = strEnum([
|
||||||
'CONTRACT_DOES_NOT_EXIST',
|
'CONTRACT_DOES_NOT_EXIST',
|
||||||
'UNHANDLED_ERROR',
|
'UNHANDLED_ERROR',
|
||||||
'USER_HAS_NO_ASSOCIATED_ADDRESSES',
|
'USER_HAS_NO_ASSOCIATED_ADDRESSES',
|
||||||
'INVALID_SIGNATURE',
|
'INVALID_SIGNATURE',
|
||||||
'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
||||||
|
'ZRX_NOT_IN_TOKEN_REGISTRY',
|
||||||
]);
|
]);
|
||||||
export type ZeroExError = keyof typeof ZeroExError;
|
export type ZeroExError = keyof typeof ZeroExError;
|
||||||
|
|
||||||
@@ -37,6 +38,10 @@ export interface ExchangeContract {
|
|||||||
getUnavailableValueT: {
|
getUnavailableValueT: {
|
||||||
call: (orderHash: string) => BigNumber.BigNumber;
|
call: (orderHash: string) => BigNumber.BigNumber;
|
||||||
};
|
};
|
||||||
|
isRoundingError: {
|
||||||
|
call: (takerTokenAmount: BigNumber.BigNumber, fillTakerAmount: BigNumber.BigNumber,
|
||||||
|
makerTokenAmount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<boolean>;
|
||||||
|
};
|
||||||
fill: {
|
fill: {
|
||||||
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
||||||
shouldCheckTransfer: boolean, v: number, r: string, s: string, txOpts: TxOpts): ContractResponse;
|
shouldCheckTransfer: boolean, v: number, r: string, s: string, txOpts: TxOpts): ContractResponse;
|
||||||
@@ -65,6 +70,9 @@ export interface ExchangeContract {
|
|||||||
LogFill: CreateContractEvent;
|
LogFill: CreateContractEvent;
|
||||||
LogCancel: CreateContractEvent;
|
LogCancel: CreateContractEvent;
|
||||||
LogError: CreateContractEvent;
|
LogError: CreateContractEvent;
|
||||||
|
ZRX: {
|
||||||
|
call: () => Promise<string>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenContract {
|
export interface TokenContract {
|
||||||
@@ -103,24 +111,23 @@ export enum ExchangeContractErrCodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ExchangeContractErrs = strEnum([
|
export const ExchangeContractErrs = strEnum([
|
||||||
'ORDER_EXPIRED',
|
'ORDER_FILL_EXPIRED',
|
||||||
'ORDER_REMAINING_FILL_AMOUNT_ZERO',
|
'ORDER_REMAINING_FILL_AMOUNT_ZERO',
|
||||||
'ORDER_ROUNDING_ERROR',
|
'ORDER_FILL_ROUNDING_ERROR',
|
||||||
'ORDER_BALANCE_ALLOWANCE_ERROR',
|
'FILL_BALANCE_ALLOWANCE_ERROR',
|
||||||
|
'INSUFFICIENT_TAKER_BALANCE',
|
||||||
|
'INSUFFICIENT_TAKER_ALLOWANCE',
|
||||||
|
'INSUFFICIENT_MAKER_BALANCE',
|
||||||
|
'INSUFFICIENT_MAKER_ALLOWANCE',
|
||||||
|
'INSUFFICIENT_TAKER_FEE_BALANCE',
|
||||||
|
'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
|
||||||
|
'INSUFFICIENT_MAKER_FEE_BALANCE',
|
||||||
|
'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
|
||||||
|
'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
|
||||||
|
|
||||||
]);
|
]);
|
||||||
export type ExchangeContractErrs = keyof typeof ExchangeContractErrs;
|
export type ExchangeContractErrs = keyof typeof ExchangeContractErrs;
|
||||||
|
|
||||||
export const FillOrderValidationErrs = strEnum([
|
|
||||||
'FILL_AMOUNT_IS_ZERO',
|
|
||||||
'NOT_A_TAKER',
|
|
||||||
'EXPIRED',
|
|
||||||
'NOT_ENOUGH_TAKER_BALANCE',
|
|
||||||
'NOT_ENOUGH_TAKER_ALLOWANCE',
|
|
||||||
'NOT_ENOUGH_MAKER_BALANCE',
|
|
||||||
'NOT_ENOUGH_MAKER_ALLOWANCE',
|
|
||||||
]);
|
|
||||||
export type FillOrderValidationErrs = keyof typeof FillOrderValidationErrs;
|
|
||||||
|
|
||||||
export interface ContractResponse {
|
export interface ContractResponse {
|
||||||
logs: ContractEvent[];
|
logs: ContractEvent[];
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import {tokenSchema} from '../schemas/token_schema';
|
|||||||
export class SchemaValidator {
|
export class SchemaValidator {
|
||||||
private validator: Validator;
|
private validator: Validator;
|
||||||
// In order to validate a complex JS object using jsonschema, we must replace any complex
|
// In order to validate a complex JS object using jsonschema, we must replace any complex
|
||||||
// sub-types (e.g BigNumber) with a simpler string represenation. Since BigNumber and other
|
// sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other
|
||||||
// complex types implement the `toString` method, we can stringify the object and
|
// complex types implement the `toString` method, we can stringify the object and
|
||||||
// then parse it. The resultant object can then be checked using jsonschema.
|
// then parse it. The resultant object can then be checked using jsonschema.
|
||||||
public static convertToJSONSchemaCompatibleObject(obj: object): object {
|
public static convertToJSONSchemaCompatibleObject(obj: object): object {
|
||||||
|
@@ -9,17 +9,17 @@ import promisify = require('es6-promisify');
|
|||||||
import {web3Factory} from './utils/web3_factory';
|
import {web3Factory} from './utils/web3_factory';
|
||||||
import {ZeroEx} from '../src/0x.js';
|
import {ZeroEx} from '../src/0x.js';
|
||||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||||
import {orderFactory} from './utils/order_factory';
|
|
||||||
import {
|
import {
|
||||||
FillOrderValidationErrs,
|
|
||||||
Token,
|
Token,
|
||||||
SignedOrder,
|
SignedOrder,
|
||||||
SubscriptionOpts,
|
SubscriptionOpts,
|
||||||
ExchangeEvents,
|
ExchangeEvents,
|
||||||
ContractEvent,
|
ContractEvent,
|
||||||
DoneCallback,
|
DoneCallback,
|
||||||
|
ExchangeContractErrs,
|
||||||
} from '../src/types';
|
} from '../src/types';
|
||||||
import {FillScenarios} from './utils/fill_scenarios';
|
import {FillScenarios} from './utils/fill_scenarios';
|
||||||
|
import {TokenUtils} from './utils/token_utils';
|
||||||
|
|
||||||
chai.use(dirtyChai);
|
chai.use(dirtyChai);
|
||||||
chai.use(ChaiBigNumber());
|
chai.use(ChaiBigNumber());
|
||||||
@@ -29,17 +29,21 @@ const blockchainLifecycle = new BlockchainLifecycle();
|
|||||||
const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777';
|
const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777';
|
||||||
|
|
||||||
describe('ExchangeWrapper', () => {
|
describe('ExchangeWrapper', () => {
|
||||||
|
let web3: Web3;
|
||||||
let zeroEx: ZeroEx;
|
let zeroEx: ZeroEx;
|
||||||
let userAddresses: string[];
|
let userAddresses: string[];
|
||||||
let web3: Web3;
|
let tokenUtils: TokenUtils;
|
||||||
let tokens: Token[];
|
let tokens: Token[];
|
||||||
let fillScenarios: FillScenarios;
|
let fillScenarios: FillScenarios;
|
||||||
|
let zrxTokenAddress: string;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
web3 = web3Factory.create();
|
web3 = web3Factory.create();
|
||||||
zeroEx = new ZeroEx(web3);
|
zeroEx = new ZeroEx(web3);
|
||||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens);
|
tokenUtils = new TokenUtils(tokens);
|
||||||
|
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||||
|
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress);
|
||||||
});
|
});
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await blockchainLifecycle.startAsync();
|
await blockchainLifecycle.startAsync();
|
||||||
@@ -120,14 +124,16 @@ describe('ExchangeWrapper', () => {
|
|||||||
describe('#fillOrderAsync', () => {
|
describe('#fillOrderAsync', () => {
|
||||||
let makerTokenAddress: string;
|
let makerTokenAddress: string;
|
||||||
let takerTokenAddress: string;
|
let takerTokenAddress: string;
|
||||||
let coinBase: string;
|
let coinbase: string;
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
const fillTakerAmountInBaseUnits = new BigNumber(5);
|
let feeRecipient: string;
|
||||||
|
const fillTakerAmount = new BigNumber(5);
|
||||||
const shouldCheckTransfer = false;
|
const shouldCheckTransfer = false;
|
||||||
before('fetch tokens', async () => {
|
before(async () => {
|
||||||
[coinBase, makerAddress, takerAddress] = userAddresses;
|
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||||
const [makerToken, takerToken] = tokens;
|
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||||
|
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||||
makerTokenAddress = makerToken.address;
|
makerTokenAddress = makerToken.address;
|
||||||
takerTokenAddress = takerToken.address;
|
takerTokenAddress = takerToken.address;
|
||||||
});
|
});
|
||||||
@@ -137,92 +143,149 @@ describe('ExchangeWrapper', () => {
|
|||||||
describe('failed fills', () => {
|
describe('failed fills', () => {
|
||||||
it('should throw when the fill amount is zero', async () => {
|
it('should throw when the fill amount is zero', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
);
|
);
|
||||||
const zeroFillAmount = new BigNumber(0);
|
const zeroFillAmount = new BigNumber(0);
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
signedOrder, zeroFillAmount, shouldCheckTransfer,
|
signedOrder, zeroFillAmount, shouldCheckTransfer,
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.FILL_AMOUNT_IS_ZERO);
|
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO);
|
||||||
});
|
});
|
||||||
it('should throw when sender is not a taker', async () => {
|
it('should throw when sender is not a taker', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
);
|
);
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer,
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.NOT_A_TAKER);
|
)).to.be.rejectedWith(ExchangeContractErrs.TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER);
|
||||||
});
|
});
|
||||||
it('should throw when order is expired', async () => {
|
it('should throw when order is expired', async () => {
|
||||||
const expirationInPast = new BigNumber(42);
|
const expirationInPast = new BigNumber(42);
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationInPast,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationInPast,
|
||||||
);
|
);
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer,
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.EXPIRED);
|
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_EXPIRED);
|
||||||
});
|
});
|
||||||
it('should throw when taker balance is less than fill amount', async () => {
|
describe('should throw when not enough balance or allowance to fulfill the order', () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const balanceToSubtractFromMaker = new BigNumber(3);
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
const lackingAllowance = new BigNumber(3);
|
||||||
);
|
let signedOrder: SignedOrder;
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
beforeEach('create fillable signed order', async () => {
|
||||||
const moreThanTheBalance = new BigNumber(6);
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
signedOrder, moreThanTheBalance, shouldCheckTransfer,
|
);
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.NOT_ENOUGH_TAKER_BALANCE);
|
});
|
||||||
|
it('should throw when taker balance is less than fill amount', async () => {
|
||||||
|
await zeroEx.token.transferAsync(
|
||||||
|
takerTokenAddress, takerAddress, coinbase, balanceToSubtractFromMaker,
|
||||||
|
);
|
||||||
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_BALANCE);
|
||||||
|
});
|
||||||
|
it('should throw when taker allowance is less than fill amount', async () => {
|
||||||
|
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
|
||||||
|
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress,
|
||||||
|
newAllowanceWhichIsLessThanFillAmount);
|
||||||
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_ALLOWANCE);
|
||||||
|
});
|
||||||
|
it('should throw when maker balance is less than maker fill amount', async () => {
|
||||||
|
await zeroEx.token.transferAsync(
|
||||||
|
makerTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
|
||||||
|
);
|
||||||
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_BALANCE);
|
||||||
|
});
|
||||||
|
it('should throw when maker allowance is less than maker fill amount', async () => {
|
||||||
|
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
|
||||||
|
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress,
|
||||||
|
newAllowanceWhichIsLessThanFillAmount);
|
||||||
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_ALLOWANCE);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should throw when taker allowance is less than fill amount', async () => {
|
it('should throw when there a rounding error would have occurred', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const makerAmount = new BigNumber(3);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const takerAmount = new BigNumber(5);
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||||
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||||
|
makerAmount, takerAmount,
|
||||||
);
|
);
|
||||||
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmountInBaseUnits.minus(1);
|
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
|
||||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress,
|
|
||||||
newAllowanceWhichIsLessThanFillAmount);
|
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer,
|
signedOrder, fillTakerAmountThatCausesRoundingError, shouldCheckTransfer,
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.NOT_ENOUGH_TAKER_ALLOWANCE);
|
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR);
|
||||||
});
|
});
|
||||||
it('should throw when maker balance is less than maker fill amount', async () => {
|
describe('should throw when not enough balance or allowance to pay fees', () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const makerFee = new BigNumber(2);
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
const takerFee = new BigNumber(2);
|
||||||
);
|
let signedOrder: SignedOrder;
|
||||||
const lackingMakerBalance = new BigNumber(3);
|
beforeEach('setup', async () => {
|
||||||
await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinBase, lackingMakerBalance);
|
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
makerAddress, takerAddress, fillableAmount, feeRecipient,
|
||||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer,
|
);
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.NOT_ENOUGH_MAKER_BALANCE);
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
});
|
});
|
||||||
it('should throw when maker allowance is less than maker fill amount', async () => {
|
it('should throw when maker doesn\'t have enough balance to pay fees', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const balanceToSubtractFromMaker = new BigNumber(1);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
await zeroEx.token.transferAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
|
||||||
);
|
);
|
||||||
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmountInBaseUnits.minus(1);
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress,
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
newAllowanceWhichIsLessThanFillAmount);
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_BALANCE);
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
});
|
||||||
return expect(zeroEx.exchange.fillOrderAsync(
|
it('should throw when maker doesn\'t have enough allowance to pay fees', async () => {
|
||||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer,
|
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
|
||||||
)).to.be.rejectedWith(FillOrderValidationErrs.NOT_ENOUGH_MAKER_ALLOWANCE);
|
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress,
|
||||||
|
newAllowanceWhichIsLessThanFees);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_ALLOWANCE);
|
||||||
|
});
|
||||||
|
it('should throw when taker doesn\'t have enough balance to pay fees', async () => {
|
||||||
|
const balanceToSubtractFromTaker = new BigNumber(1);
|
||||||
|
await zeroEx.token.transferAsync(
|
||||||
|
zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
|
||||||
|
);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_BALANCE);
|
||||||
|
});
|
||||||
|
it('should throw when taker doesn\'t have enough allowance to pay fees', async () => {
|
||||||
|
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
|
||||||
|
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, takerAddress,
|
||||||
|
newAllowanceWhichIsLessThanFees);
|
||||||
|
return expect(zeroEx.exchange.fillOrderAsync(
|
||||||
|
signedOrder, fillTakerAmount, shouldCheckTransfer,
|
||||||
|
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_ALLOWANCE);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('successful fills', () => {
|
describe('successful fills', () => {
|
||||||
it('should fill the valid order', async () => {
|
it('should fill a valid order', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||||
.to.be.bignumber.equal(fillableAmount);
|
.to.be.bignumber.equal(fillableAmount);
|
||||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||||
@@ -232,19 +295,19 @@ describe('ExchangeWrapper', () => {
|
|||||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||||
.to.be.bignumber.equal(fillableAmount);
|
.to.be.bignumber.equal(fillableAmount);
|
||||||
zeroEx.setTransactionSenderAccount(takerAddress);
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer);
|
await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmount, shouldCheckTransfer);
|
||||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmountInBaseUnits));
|
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||||
.to.be.bignumber.equal(fillTakerAmountInBaseUnits);
|
.to.be.bignumber.equal(fillTakerAmount);
|
||||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
|
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
|
||||||
.to.be.bignumber.equal(fillTakerAmountInBaseUnits);
|
.to.be.bignumber.equal(fillTakerAmount);
|
||||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmountInBaseUnits));
|
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||||
});
|
});
|
||||||
it('should partially fill the valid order', async () => {
|
it('should partially fill the valid order', async () => {
|
||||||
const fillableAmount = new BigNumber(5);
|
const fillableAmount = new BigNumber(5);
|
||||||
const signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
);
|
);
|
||||||
const partialFillAmount = new BigNumber(3);
|
const partialFillAmount = new BigNumber(3);
|
||||||
@@ -259,6 +322,19 @@ describe('ExchangeWrapper', () => {
|
|||||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||||
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
||||||
});
|
});
|
||||||
|
it('should fill the valid orders with fees', async () => {
|
||||||
|
const fillableAmount = new BigNumber(5);
|
||||||
|
const makerFee = new BigNumber(1);
|
||||||
|
const takerFee = new BigNumber(2);
|
||||||
|
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||||
|
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
|
||||||
|
makerAddress, takerAddress, fillableAmount, feeRecipient,
|
||||||
|
);
|
||||||
|
zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
|
await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmount, shouldCheckTransfer);
|
||||||
|
expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
|
||||||
|
.to.be.bignumber.equal(makerFee.plus(takerFee));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('tests that require partially filled order', () => {
|
describe('tests that require partially filled order', () => {
|
||||||
@@ -332,20 +408,20 @@ describe('ExchangeWrapper', () => {
|
|||||||
const shouldCheckTransfer = false;
|
const shouldCheckTransfer = false;
|
||||||
let makerTokenAddress: string;
|
let makerTokenAddress: string;
|
||||||
let takerTokenAddress: string;
|
let takerTokenAddress: string;
|
||||||
let coinBase: string;
|
let coinbase: string;
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
let fillableAmount: BigNumber.BigNumber;
|
let fillableAmount: BigNumber.BigNumber;
|
||||||
let signedOrder: SignedOrder;
|
let signedOrder: SignedOrder;
|
||||||
before(() => {
|
before(() => {
|
||||||
[coinBase, makerAddress, takerAddress] = userAddresses;
|
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||||
const [makerToken, takerToken] = tokens;
|
const [makerToken, takerToken] = tokens;
|
||||||
makerTokenAddress = makerToken.address;
|
makerTokenAddress = makerToken.address;
|
||||||
takerTokenAddress = takerToken.address;
|
takerTokenAddress = takerToken.address;
|
||||||
});
|
});
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
fillableAmount = new BigNumber(5);
|
fillableAmount = new BigNumber(5);
|
||||||
signedOrder = await fillScenarios.createAFillableSignedOrderAsync(
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@@ -2,35 +2,54 @@ import * as BigNumber from 'bignumber.js';
|
|||||||
import {ZeroEx} from '../../src/0x.js';
|
import {ZeroEx} from '../../src/0x.js';
|
||||||
import {Token, SignedOrder} from '../../src/types';
|
import {Token, SignedOrder} from '../../src/types';
|
||||||
import {orderFactory} from '../utils/order_factory';
|
import {orderFactory} from '../utils/order_factory';
|
||||||
|
import {constants} from './constants';
|
||||||
|
|
||||||
export class FillScenarios {
|
export class FillScenarios {
|
||||||
private zeroEx: ZeroEx;
|
private zeroEx: ZeroEx;
|
||||||
private userAddresses: string[];
|
private userAddresses: string[];
|
||||||
private tokens: Token[];
|
private tokens: Token[];
|
||||||
private coinBase: string;
|
private coinbase: string;
|
||||||
constructor(zeroEx: ZeroEx, userAddresses: string[], tokens: Token[]) {
|
private zrxTokenAddress: string;
|
||||||
|
constructor(zeroEx: ZeroEx, userAddresses: string[], tokens: Token[], zrxTokenAddress: string) {
|
||||||
this.zeroEx = zeroEx;
|
this.zeroEx = zeroEx;
|
||||||
this.userAddresses = userAddresses;
|
this.userAddresses = userAddresses;
|
||||||
this.tokens = tokens;
|
this.tokens = tokens;
|
||||||
this.coinBase = userAddresses[0];
|
this.coinbase = userAddresses[0];
|
||||||
|
this.zrxTokenAddress = zrxTokenAddress;
|
||||||
}
|
}
|
||||||
public async createAFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
public async createFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
||||||
makerAddress: string, takerAddress: string,
|
makerAddress: string, takerAddress: string,
|
||||||
fillableAmount: BigNumber.BigNumber,
|
fillableAmount: BigNumber.BigNumber,
|
||||||
expirationUnixTimestampSec?: BigNumber.BigNumber):
|
expirationUnixTimestampSec?: BigNumber.BigNumber):
|
||||||
Promise<SignedOrder> {
|
Promise<SignedOrder> {
|
||||||
await this.zeroEx.token.transferAsync(makerTokenAddress, this.coinBase, makerAddress, fillableAmount);
|
return this.createAsymmetricFillableSignedOrderAsync(
|
||||||
await this.zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, fillableAmount);
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||||
await this.zeroEx.token.transferAsync(takerTokenAddress, this.coinBase, takerAddress, fillableAmount);
|
fillableAmount, fillableAmount, expirationUnixTimestampSec,
|
||||||
await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, fillableAmount);
|
);
|
||||||
|
}
|
||||||
const transactionSenderAccount = await this.zeroEx.getTransactionSenderAccountIfExistsAsync();
|
public async createFillableSignedOrderWithFeesAsync(
|
||||||
this.zeroEx.setTransactionSenderAccount(makerAddress);
|
makerTokenAddress: string, takerTokenAddress: string,
|
||||||
const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx, makerAddress,
|
makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
|
||||||
takerAddress, fillableAmount, makerTokenAddress, fillableAmount, takerTokenAddress,
|
makerAddress: string, takerAddress: string,
|
||||||
expirationUnixTimestampSec);
|
fillableAmount: BigNumber.BigNumber,
|
||||||
this.zeroEx.setTransactionSenderAccount(transactionSenderAccount as string);
|
feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber,
|
||||||
return signedOrder;
|
): Promise<SignedOrder> {
|
||||||
|
return this.createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||||
|
makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress,
|
||||||
|
fillableAmount, fillableAmount, feeRecepient, expirationUnixTimestampSec,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public async createAsymmetricFillableSignedOrderAsync(
|
||||||
|
makerTokenAddress: string, takerTokenAddress: string, makerAddress: string, takerAddress: string,
|
||||||
|
makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
|
||||||
|
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||||
|
const makerFee = new BigNumber(0);
|
||||||
|
const takerFee = new BigNumber(0);
|
||||||
|
const feeRecepient = constants.NULL_ADDRESS;
|
||||||
|
return this.createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||||
|
makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress,
|
||||||
|
makerFillableAmount, takerFillableAmount, feeRecepient, expirationUnixTimestampSec,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
public async createPartiallyFilledSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
public async createPartiallyFilledSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
||||||
takerAddress: string, fillableAmount: BigNumber.BigNumber,
|
takerAddress: string, fillableAmount: BigNumber.BigNumber,
|
||||||
@@ -41,8 +60,10 @@ export class FillScenarios {
|
|||||||
await this.zeroEx.token.transferAsync(takerTokenAddress, makerAddress, takerAddress, fillableAmount);
|
await this.zeroEx.token.transferAsync(takerTokenAddress, makerAddress, takerAddress, fillableAmount);
|
||||||
await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, fillableAmount);
|
await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, fillableAmount);
|
||||||
|
|
||||||
const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx, makerAddress,
|
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
|
||||||
takerAddress, fillableAmount, makerTokenAddress, fillableAmount, takerTokenAddress);
|
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||||
|
fillableAmount, fillableAmount,
|
||||||
|
);
|
||||||
|
|
||||||
this.zeroEx.setTransactionSenderAccount(takerAddress);
|
this.zeroEx.setTransactionSenderAccount(takerAddress);
|
||||||
const shouldCheckTransfer = false;
|
const shouldCheckTransfer = false;
|
||||||
@@ -52,4 +73,34 @@ export class FillScenarios {
|
|||||||
this.zeroEx.setTransactionSenderAccount(prevSenderAccount as string);
|
this.zeroEx.setTransactionSenderAccount(prevSenderAccount as string);
|
||||||
return signedOrder;
|
return signedOrder;
|
||||||
}
|
}
|
||||||
|
private async createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||||
|
makerTokenAddress: string, takerTokenAddress: string,
|
||||||
|
makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
|
||||||
|
makerAddress: string, takerAddress: string,
|
||||||
|
makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
|
||||||
|
feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||||
|
await this.zeroEx.token.transferAsync(makerTokenAddress, this.coinbase, makerAddress, makerFillableAmount);
|
||||||
|
await this.zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount);
|
||||||
|
await this.zeroEx.token.transferAsync(takerTokenAddress, this.coinbase, takerAddress, takerFillableAmount);
|
||||||
|
await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount);
|
||||||
|
|
||||||
|
if (!makerFee.isZero()) {
|
||||||
|
await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, makerAddress, makerFee);
|
||||||
|
await this.zeroEx.token.setProxyAllowanceAsync(this.zrxTokenAddress, makerAddress, makerFee);
|
||||||
|
}
|
||||||
|
if (!takerFee.isZero()) {
|
||||||
|
await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, takerAddress, takerFee);
|
||||||
|
await this.zeroEx.token.setProxyAllowanceAsync(this.zrxTokenAddress, takerAddress, takerFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevTransactionSenderAccount = await this.zeroEx.getTransactionSenderAccountIfExistsAsync();
|
||||||
|
this.zeroEx.setTransactionSenderAccount(makerAddress);
|
||||||
|
const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx,
|
||||||
|
makerAddress, takerAddress, makerFee, takerFee,
|
||||||
|
makerFillableAmount, makerTokenAddress, takerFillableAmount, takerTokenAddress,
|
||||||
|
feeRecepient, expirationUnixTimestampSec);
|
||||||
|
// We re-set the transactionSender to avoid introducing side-effects
|
||||||
|
this.zeroEx.setTransactionSenderAccount(prevTransactionSenderAccount as string);
|
||||||
|
return signedOrder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,10 +10,13 @@ export const orderFactory = {
|
|||||||
zeroEx: ZeroEx,
|
zeroEx: ZeroEx,
|
||||||
maker: string,
|
maker: string,
|
||||||
taker: string,
|
taker: string,
|
||||||
makerTokenAmount: BigNumber.BigNumber|number,
|
makerFee: BigNumber.BigNumber,
|
||||||
|
takerFee: BigNumber.BigNumber,
|
||||||
|
makerTokenAmount: BigNumber.BigNumber,
|
||||||
makerTokenAddress: string,
|
makerTokenAddress: string,
|
||||||
takerTokenAmount: BigNumber.BigNumber|number,
|
takerTokenAmount: BigNumber.BigNumber,
|
||||||
takerTokenAddress: string,
|
takerTokenAddress: string,
|
||||||
|
feeRecipient: string,
|
||||||
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||||
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
|
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
|
||||||
expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ?
|
expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ?
|
||||||
@@ -22,14 +25,14 @@ export const orderFactory = {
|
|||||||
const order = {
|
const order = {
|
||||||
maker,
|
maker,
|
||||||
taker,
|
taker,
|
||||||
makerFee: new BigNumber(0),
|
makerFee,
|
||||||
takerFee: new BigNumber(0),
|
takerFee,
|
||||||
makerTokenAmount: _.isNumber(makerTokenAmount) ? new BigNumber(makerTokenAmount) : makerTokenAmount,
|
makerTokenAmount,
|
||||||
takerTokenAmount: _.isNumber(takerTokenAmount) ? new BigNumber(takerTokenAmount) : takerTokenAmount,
|
takerTokenAmount,
|
||||||
makerTokenAddress,
|
makerTokenAddress,
|
||||||
takerTokenAddress,
|
takerTokenAddress,
|
||||||
salt: ZeroEx.generatePseudoRandomSalt(),
|
salt: ZeroEx.generatePseudoRandomSalt(),
|
||||||
feeRecipient: constants.NULL_ADDRESS,
|
feeRecipient,
|
||||||
expirationUnixTimestampSec,
|
expirationUnixTimestampSec,
|
||||||
};
|
};
|
||||||
const orderHash = await zeroEx.getOrderHashHexAsync(order);
|
const orderHash = await zeroEx.getOrderHashHexAsync(order);
|
||||||
|
24
test/utils/token_utils.ts
Normal file
24
test/utils/token_utils.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as _ from 'lodash';
|
||||||
|
import {Token, ZeroExError} from '../../src/types';
|
||||||
|
|
||||||
|
const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
|
||||||
|
|
||||||
|
export class TokenUtils {
|
||||||
|
private tokens: Token[];
|
||||||
|
constructor(tokens: Token[]) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
}
|
||||||
|
public getProtocolTokenOrThrow(): Token {
|
||||||
|
const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL});
|
||||||
|
if (_.isUndefined(zrxToken)) {
|
||||||
|
throw new Error(ZeroExError.ZRX_NOT_IN_TOKEN_REGISTRY);
|
||||||
|
}
|
||||||
|
return zrxToken;
|
||||||
|
}
|
||||||
|
public getNonProtocolTokens(): Token[] {
|
||||||
|
const nonProtocolTokens = _.filter(this.tokens, token => {
|
||||||
|
return token.symbol !== PROTOCOL_TOKEN_SYMBOL;
|
||||||
|
});
|
||||||
|
return nonProtocolTokens;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user