Skip to content

Commit

Permalink
Merge pull request #921 from ExchangeUnion/pairs-thresholds
Browse files Browse the repository at this point in the history
  • Loading branch information
sangaman authored Jul 5, 2019
2 parents 71a8cb2 + 0464b3c commit affc0bf
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 3 deletions.
12 changes: 12 additions & 0 deletions lib/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RaidenClientConfig } from './raidenclient/types';
import { Level } from './Logger';
import { XuNetwork } from './constants/enums';
import { PoolConfig } from './p2p/types';
import { OrderBookThresholds } from './orderbook/types';

class Config {
public p2p: PoolConfig;
Expand All @@ -20,6 +21,7 @@ class Config {
public http: { port: number };
public lnd: { [currency: string]: LndClientConfig | undefined } = {};
public raiden: RaidenClientConfig;
public orderthresholds: OrderBookThresholds;
public webproxy: { port: number, disable: boolean };
public instanceid = 0;
/** Whether to intialize a new database with default values. */
Expand Down Expand Up @@ -93,6 +95,10 @@ class Config {
disable: true,
port: 8080,
};
// TODO: add dynamic max/min price limits
this.orderthresholds = {
minQuantity: 0, // 0 = disabled
};
this.lnd.BTC = {
disable: false,
certpath: path.join(lndDefaultDatadir, 'tls.cert'),
Expand Down Expand Up @@ -157,6 +163,12 @@ class Config {
this.updateMacaroonPaths();
}

if (props.thresholds) {
this.orderthresholds = {
...this.orderthresholds,
...props.thresholds,
};
}
// merge parsed json properties from config file to the default config
deepMerge(this, props);
} catch (err) {}
Expand Down
1 change: 1 addition & 0 deletions lib/Xud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class Xud extends EventEmitter {
this.orderBook = new OrderBook({
logger: loggers.orderbook,
models: this.db.models,
thresholds: this.config.orderthresholds,
nomatching: this.config.nomatching,
pool: this.pool,
swaps: this.swaps,
Expand Down
1 change: 1 addition & 0 deletions lib/grpc/GrpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class GrpcService {
case serviceErrorCodes.INVALID_ARGUMENT:
case p2pErrorCodes.ATTEMPTED_CONNECTION_TO_SELF:
case p2pErrorCodes.UNEXPECTED_NODE_PUB_KEY:
case orderErrorCodes.MIN_QUANTITY_VIOLATED:
case orderErrorCodes.QUANTITY_DOES_NOT_MATCH:
code = status.INVALID_ARGUMENT;
break;
Expand Down
26 changes: 24 additions & 2 deletions lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Swaps from '../swaps/Swaps';
import { SwapRole, SwapFailureReason, SwapPhase, SwapClientType } from '../constants/enums';
import { CurrencyInstance, PairInstance, CurrencyFactory } from '../db/types';
import { Pair, OrderIdentifier, OwnOrder, OrderPortion, OwnLimitOrder, PeerOrder, Order, PlaceOrderEvent,
PlaceOrderEventType, PlaceOrderResult, OutgoingOrder, OwnMarketOrder, isOwnOrder, IncomingOrder } from './types';
PlaceOrderEventType, PlaceOrderResult, OutgoingOrder, OwnMarketOrder, isOwnOrder, IncomingOrder, OrderBookThresholds } from './types';
import { SwapRequestPacket, SwapFailedPacket } from '../p2p/packets';
import { SwapSuccess, SwapDeal, SwapFailure } from '../swaps/types';
// We add the Bluebird import to ts-ignore because it's actually being used.
Expand Down Expand Up @@ -71,6 +71,7 @@ class OrderBook extends EventEmitter {
/** A map of supported trading pair tickers and pair database instances. */
private pairInstances = new Map<string, PairInstance>();
private repository: OrderBookRepository;
private thresholds: OrderBookThresholds;
private logger: Logger;
private nosanityswaps: boolean;
private nobalancechecks: boolean;
Expand All @@ -91,10 +92,11 @@ class OrderBook extends EventEmitter {
return this.currencyInstances.keys();
}

constructor({ logger, models, pool, swaps, nosanityswaps, nobalancechecks, nomatching = false }:
constructor({ logger, models, thresholds, pool, swaps, nosanityswaps, nobalancechecks, nomatching = false }:
{
logger: Logger,
models: Models,
thresholds: OrderBookThresholds,
pool: Pool,
swaps: Swaps,
nosanityswaps: boolean,
Expand All @@ -109,6 +111,7 @@ class OrderBook extends EventEmitter {
this.nomatching = nomatching;
this.nosanityswaps = nosanityswaps;
this.nobalancechecks = nobalancechecks;
this.thresholds = thresholds;

this.repository = new OrderBookRepository(models);

Expand All @@ -121,6 +124,10 @@ class OrderBook extends EventEmitter {
return outgoingOrder ;
}

private checkThresholdCompliance = (order: OwnOrder | IncomingOrder) => {
const { minQuantity } = this.thresholds;
return order.quantity >= minQuantity;
}
/**
* Checks that a currency advertised by a peer are known to us, have a swap client identifier,
* and that their token identifier matches ours.
Expand Down Expand Up @@ -345,6 +352,13 @@ class OrderBook extends EventEmitter {
onUpdate?: (e: PlaceOrderEvent) => void,
maxTime?: number,
): Promise<PlaceOrderResult> => {
// Check if order complies to thresholds
if (this.thresholds.minQuantity > 0) {
if (!this.checkThresholdCompliance(order)) {
throw errors.MIN_QUANTITY_VIOLATED(order.id);
}
}

// this method can be called recursively on swap failures retries.
// if max time exceeded, don't try to match
if (maxTime && Date.now() > maxTime) {
Expand Down Expand Up @@ -545,6 +559,14 @@ class OrderBook extends EventEmitter {
* @returns `false` if it's a duplicated order or with an invalid pair id, otherwise true
*/
private addPeerOrder = (order: IncomingOrder): boolean => {
if (this.thresholds.minQuantity > 0) {
if (!this.checkThresholdCompliance(order)) {
this.removePeerOrder(order.id, order.pairId, order.peerPubKey, order.quantity);
this.logger.debug('incoming peer order does not comply with configured threshold');
return false;
}
}

const tp = this.tradingPairs.get(order.pairId);
if (!tp) {
// TODO: penalize peer for sending an order for an unsupported pair
Expand Down
5 changes: 5 additions & 0 deletions lib/orderbook/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const errorCodes = {
QUANTITY_DOES_NOT_MATCH: codesPrefix.concat('.10'),
CURRENCY_MISSING_ETHEREUM_CONTRACT_ADDRESS: codesPrefix.concat('.11'),
INSUFFICIENT_OUTBOUND_BALANCE: codesPrefix.concat('.12'),
MIN_QUANTITY_VIOLATED: codesPrefix.concat('.13'),
};

const errors = {
Expand Down Expand Up @@ -65,6 +66,10 @@ const errors = {
message: `${currency} outbound balance of ${availableAmount} is not sufficient for order amount of ${amount}`,
code: errorCodes.INSUFFICIENT_OUTBOUND_BALANCE,
}),
MIN_QUANTITY_VIOLATED: (id: string) => ({
message: `order ${id} has surpassed the minimum quantity`,
code: errorCodes.MIN_QUANTITY_VIOLATED,
}),
};

export { errorCodes };
Expand Down
4 changes: 4 additions & 0 deletions lib/orderbook/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { SwapClientType } from '../constants/enums';
import { SwapSuccess, SwapFailure } from '../swaps/types';

export type OrderBookThresholds = {
minQuantity: number;
};

export type OrderMatch = {
maker: Order;
taker: OwnOrder;
Expand Down
20 changes: 20 additions & 0 deletions test/integration/OrderBook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as orders from '../../lib/orderbook/types';
import { SwapClientType } from '../../lib/constants/enums';
import { createOwnOrder } from '../utils';
import sinon from 'sinon';
import Config from '../../lib/Config';

const PAIR_ID = 'LTC/BTC';
const currencies = PAIR_ID.split('/');
Expand Down Expand Up @@ -62,8 +63,10 @@ describe('OrderBook', () => {
let db: DB;
let swaps: Swaps;
let orderBook: OrderBook;
let config: Config;

before(async () => {
config = new Config();
sandbox = sinon.createSandbox();
db = new DB(loggers.db);
await db.init();
Expand All @@ -76,6 +79,7 @@ describe('OrderBook', () => {
orderBook = new OrderBook({
pool,
swaps,
thresholds: config.orderthresholds,
logger: loggers.orderbook,
models: db.models,
nosanityswaps: true,
Expand Down Expand Up @@ -160,6 +164,19 @@ describe('OrderBook', () => {
await expect(orderBook.placeLimitOrder(order)).to.be.rejected;
});

it('should place order with quantity higher than min quantity', async () => {
orderBook['thresholds'] = { minQuantity : 10000 };
const order: orders.OwnOrder = createOwnOrder(100, 1000000, false);

await expect(orderBook.placeLimitOrder(order)).to.be.fulfilled;
});

it('should throw error if the order quantity exceeds min quantity', async () => {
const order: orders.OwnOrder = createOwnOrder(100, 100, false);

await expect(orderBook.placeLimitOrder(order)).to.be.rejected;
});

after(async () => {
await db.close();
sandbox.restore();
Expand All @@ -172,8 +189,10 @@ describe('nomatching OrderBook', () => {
let pool: Pool;
let swaps: Swaps;
let orderBook: OrderBook;
let config: Config;

before(async () => {
config = new Config();
db = new DB(loggers.db);
await db.init();
sandbox = sinon.createSandbox();
Expand All @@ -186,6 +205,7 @@ describe('nomatching OrderBook', () => {
orderBook = new OrderBook({
pool,
swaps,
thresholds: config.orderthresholds,
logger: loggers.orderbook,
models: db.models,
nomatching: true,
Expand Down
2 changes: 1 addition & 1 deletion test/jest/Orderbook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ jest.mock('../../lib/p2p/Pool', () => {
};
});
});
jest.mock('../../lib/Config');
jest.mock('../../lib/swaps/Swaps');
jest.mock('../../lib/swaps/SwapClientManager');
jest.mock('../../lib/Logger');
Expand Down Expand Up @@ -123,6 +122,7 @@ describe('OrderBook', () => {
orderbook = new Orderbook({
pool,
swaps,
thresholds: config.orderthresholds,
logger: loggers.orderbook,
models: db.models,
nomatching: config.nomatching,
Expand Down

0 comments on commit affc0bf

Please sign in to comment.