diff --git a/lib/db/DB.ts b/lib/db/DB.ts index 3d874c937..52f1a7ffa 100644 --- a/lib/db/DB.ts +++ b/lib/db/DB.ts @@ -1,4 +1,3 @@ -import { promises as fs } from 'fs'; import { derivePairId } from '../utils/utils'; import { ModelCtor, Sequelize } from 'sequelize'; import { XuNetwork } from '../constants/enums'; @@ -137,8 +136,6 @@ class DB { * @param initDb whether to intialize a new database with default values if no database exists */ public init = async (network = XuNetwork.SimNet, initDb = false): Promise => { - const shouldInitDb = initDb && await this.isNewDb(); - try { await this.sequelize.authenticate(); this.logger.info(`connected to database ${this.storage ? this.storage : 'in memory'}`); @@ -177,41 +174,29 @@ class DB { await Node.bulkCreate(newNodes); } } - } - - if (shouldInitDb) { // initialize database with the default currencies for the configured network const currencies = defaultCurrencies(network); if (currencies) { - await Currency.bulkCreate(currencies); + const existingCurrencies = await Models.Currency(this.sequelize).findAll(); + const newCurrencies = currencies.filter(currency => (!existingCurrencies.find(n => (n.id === currency.id)))); + + if (newCurrencies.length > 0) { + await Currency.bulkCreate(newCurrencies); + } } // initialize database with the default trading pairs for the configured network const pairs = defaultPairs(network); if (pairs) { - await Pair.bulkCreate(pairs); - } - } - } + const existingPairs = await Models.Pair(this.sequelize).findAll(); + const newPairs = pairs.filter(pair => (!existingPairs.find(n => (n.baseCurrency === pair.baseCurrency && + n.quoteCurrency === pair.quoteCurrency)))); - /** - * Checks whether the database is new, in other words whether we are not - * loading a preexisting database from disk. - */ - private isNewDb = async () => { - if (this.storage && this.storage !== ':memory:') { - // check if database file exists - try { - await fs.access(this.storage); - return false; - } catch (err) { - if (err.code !== 'ENOENT') { - // we ignore errors due to file not existing, otherwise throw - throw err; + if (newPairs.length > 0) { + await Pair.bulkCreate(newPairs); } } } - return true; } public close = () => { diff --git a/lib/lndclient/LndClient.ts b/lib/lndclient/LndClient.ts index e80dd0624..5e359fd5f 100644 --- a/lib/lndclient/LndClient.ts +++ b/lib/lndclient/LndClient.ts @@ -1,4 +1,5 @@ import assert from 'assert'; +import crypto from 'crypto'; import { promises as fs, watch } from 'fs'; import grpc, { ChannelCredentials, ClientReadableStream } from 'grpc'; import path from 'path'; @@ -242,6 +243,10 @@ class LndClient extends SwapClient { private unaryCall = (methodName: Exclude, params: T): Promise => { return new Promise((resolve, reject) => { + if (this.hasNoInvoiceSupport()) { + reject(errors.NO_HOLD_INVOICE_SUPPORT); + return; + } if (!this.isOperational()) { reject(errors.DISABLED); return; @@ -337,7 +342,9 @@ class LndClient extends SwapClient { let version: string | undefined; let alias: string | undefined; let status = 'Ready'; - if (!this.isOperational()) { + if (this.hasNoInvoiceSupport()) { + status = errors.NO_HOLD_INVOICE_SUPPORT(this.currency).message; + } else if (!this.isOperational()) { status = errors.DISABLED(this.currency).message; } else if (this.isDisconnected()) { status = errors.UNAVAILABLE(this.currency, this.status).message; @@ -493,6 +500,18 @@ class LndClient extends SwapClient { } this.invoices = new InvoicesClient(this.uri, this.credentials); + try { + const randomHash = crypto.randomBytes(32).toString('hex'); + this.logger.debug(`checking hold invoice support with hash: ${randomHash}`); + + await this.addInvoice({ rHash: randomHash, units: 1 }); + await this.removeInvoice(randomHash); + } catch (err) { + const errStr = typeof(err) === 'string' ? err : JSON.stringify(err); + + this.logger.error(`could not add hold invoice, error: ${errStr}`); + this.setStatus(ClientStatus.NoHoldInvoiceSupport); + } if (this.walletUnlocker) { // WalletUnlocker service is disabled when the main Lightning service is available diff --git a/lib/lndclient/errors.ts b/lib/lndclient/errors.ts index d8a13f0cc..7d8eaa854 100644 --- a/lib/lndclient/errors.ts +++ b/lib/lndclient/errors.ts @@ -6,6 +6,7 @@ const errorCodes = { DISABLED: codesPrefix.concat('.1'), UNAVAILABLE: codesPrefix.concat('.2'), NO_ACTIVE_CHANNELS: codesPrefix.concat('.3'), + NO_HOLD_INVOICE_SUPPORT: codesPrefix.concat('.4'), }; const errors = { @@ -21,6 +22,10 @@ const errors = { message: `lnd-${currency} has no active channels`, code: errorCodes.NO_ACTIVE_CHANNELS, }), + NO_HOLD_INVOICE_SUPPORT: (currency: string) => ({ + message: `lnd-${currency} has no hold invoice support`, + code: errorCodes.NO_HOLD_INVOICE_SUPPORT, + }), }; export { errorCodes }; diff --git a/lib/service/Service.ts b/lib/service/Service.ts index 75012890a..cf43e0e13 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -626,8 +626,10 @@ class Service { const currency = pairId.split('/')[0]; calculatedQuantity = (await this.getBalance({ currency })).get(currency)?.channelBalance || 0; } else { - // TODO - calculatedQuantity = 0; + const currency = pairId.split('/')[1]; + + const balance = (await this.getBalance({ currency })).get(currency)?.channelBalance || 0; + calculatedQuantity = this.calculateBuyMaxMarketQuantity(pairId, price, balance); } this.logger.debug(`max flag is true to place order, calculated quantity from balance is ${calculatedQuantity}`); @@ -664,6 +666,34 @@ class Service { await this.orderBook.placeMarketOrder(placeOrderRequest); } + private calculateBuyMaxMarketQuantity(pairId: string, price: number, balance: number) { + let result = 0; + let currentBalance = balance; + + this.listOrders({ pairId, owner: Owner.Both, limit: 0, includeAliases: false }).forEach((orderArrays, _) => { + for (const order of orderArrays.sellArray) { + if (order.quantity && order.price) { + if (!price) { + // market buy max calculation + const maxBuyableFromThisPrice = currentBalance / order.price; + const calculatedQuantity = (maxBuyableFromThisPrice > order.quantity) ? order.quantity : maxBuyableFromThisPrice; + result += calculatedQuantity; + currentBalance -= order.price * calculatedQuantity; + + if (currentBalance === 0) { + // we filled our buy quantity with this order + break; + } + } else { + // TODO + } + } + } + }); + + return result; + } + /** Removes a currency. */ public removeCurrency = async (args: { currency: string }) => { argChecks.VALID_CURRENCY(args); diff --git a/lib/swaps/SwapClient.ts b/lib/swaps/SwapClient.ts index ce0f68de4..d2776fc1c 100644 --- a/lib/swaps/SwapClient.ts +++ b/lib/swaps/SwapClient.ts @@ -23,6 +23,8 @@ enum ClientStatus { Unlocked, /** The client could not be initialized due to faulty configuration. */ Misconfigured, + /** The server is reachable but hold invoices are not supported. */ + NoHoldInvoiceSupport, } type ChannelBalance = { @@ -216,6 +218,7 @@ abstract class SwapClient extends EventEmitter { case ClientStatus.Disconnected: case ClientStatus.WaitingUnlock: case ClientStatus.OutOfSync: + case ClientStatus.NoHoldInvoiceSupport: // these statuses can only be set on an operational, initalized client validStatusTransition = this.isOperational(); break; @@ -359,7 +362,7 @@ abstract class SwapClient extends EventEmitter { * Returns `true` if the client is enabled and configured properly. */ public isOperational(): boolean { - return !this.isDisabled() && !this.isMisconfigured() && !this.isNotInitialized(); + return !this.isDisabled() && !this.isMisconfigured() && !this.isNotInitialized() && !this.hasNoInvoiceSupport(); } public isDisconnected(): boolean { return this.status === ClientStatus.Disconnected; @@ -373,6 +376,9 @@ abstract class SwapClient extends EventEmitter { public isOutOfSync(): boolean { return this.status === ClientStatus.OutOfSync; } + public hasNoInvoiceSupport(): boolean { + return this.status === ClientStatus.NoHoldInvoiceSupport; + } /** Ends all connections, subscriptions, and timers for for this client. */ public close() { diff --git a/test/integration/Service.spec.ts b/test/integration/Service.spec.ts index 128511dbc..1b0dc2200 100644 --- a/test/integration/Service.spec.ts +++ b/test/integration/Service.spec.ts @@ -1,10 +1,12 @@ import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; import { OrderSide, Owner, SwapClientType } from '../../lib/constants/enums'; import p2pErrors from '../../lib/p2p/errors'; import Service from '../../lib/service/Service'; import Xud from '../../lib/Xud'; import { getTempDir } from '../utils'; +import { ServiceOrderSidesArrays } from '../../lib/service/types'; chai.use(chaiAsPromised); @@ -186,4 +188,63 @@ describe('API Service', () => { }); await expect(shutdownPromise).to.be.fulfilled; }); + + describe('Max Quantity Calculation', () => { + before(async () => { + const map = new Map(); + map.set('BTC/DAI', { + buyArray: [], + sellArray: [ + { quantity: 0.01, price: 20000, pairId: 'BTC/DAI', id: 'test_1', createdAt: 1, side: OrderSide.Sell, + isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key' } }, + { quantity: 0.01, price: 50000, pairId: 'BTC/DAI', id: 'test_2', createdAt: 1, side: OrderSide.Sell, + isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key2' } }, + { quantity: 0.05, price: 100000, pairId: 'BTC/DAI', id: 'test_2', createdAt: 1, side: OrderSide.Sell, + isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key2' } }, + ], + }); + + sinon.createSandbox().stub(service, 'listOrders').returns(map); + }); + + it('should return `0` for 0 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 0); + await expect(number).to.equal(0); + }); + + it('should return `0.005` for 100 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 100); + await expect(number).to.equal(0.005); + }); + + it('should return `0.01` for 200 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 200); + await expect(number).to.equal(0.01); + }); + + it('should return `0.016` for 500 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 500); + await expect(number).to.equal(0.016); + }); + + it('should return `0.02` for 700 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 700); + await expect(number).to.equal(0.02); + }); + + it('should return `0.021` for 800 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 800); + await expect(number).to.equal(0.021); + }); + + it('should return `0.07` for 5700 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 5700); + await expect(number).to.equal(0.07); + }); + + it('should return `0.07` for 10000 balance mkt', async () => { + const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 10000); + await expect(number).to.equal(0.07); + }); + }); }); diff --git a/test/jest/DB.spec.ts b/test/jest/DB.spec.ts new file mode 100644 index 000000000..7f84f308d --- /dev/null +++ b/test/jest/DB.spec.ts @@ -0,0 +1,264 @@ +import { SwapClientType, SwapPhase, SwapRole, SwapState, XuNetwork } from '../../lib/constants/enums'; +import DB from '../../lib/db/DB'; +import { defaultCurrencies, defaultNodes, defaultPairs } from '../../lib/db/seeds'; +import { TradeCreationAttributes } from '../../lib/db/types'; +import Logger, { Level } from '../../lib/Logger'; +import OrderBookRepository from '../../lib/orderbook/OrderBookRepository'; +import P2PRepository from '../../lib/p2p/P2PRepository'; +import SwapRepository from '../../lib/swaps/SwapRepository'; +import { SwapDeal } from '../../lib/swaps/types'; +import { createOwnOrder } from '../utils'; + +const pairId = 'LTC/BTC'; +const loggers = Logger.createLoggers(Level.Warn); + +const price = 0.005; +const quantity = 10000000; +const peerPubKey = '03029c6a4d80c91da9e40529ec41c93b17cc9d7956b59c7d8334b0318d4a86aef8'; +const rHash = '62c8bbef4587cff4286246e63044dc3e454b5693fb5ebd0171b7e58644bfafe2'; + +const order = createOwnOrder(price, quantity, true); +const orderId = order.id; + +const deal: SwapDeal = { + quantity, + price, + peerPubKey, + rHash, + pairId, + role: SwapRole.Maker, + phase: SwapPhase.PaymentReceived, + state: SwapState.Completed, + orderId: order.id, + localId: order.localId, + isBuy: order.isBuy, + proposedQuantity: quantity, + takerCurrency: 'BTC', + makerCurrency: 'LTC', + takerAmount: 5000, + makerAmount: 1000000, + takerUnits: 5000, + makerUnits: 1000000, + takerCltvDelta: 144, + makerCltvDelta: 144, + rPreimage: '60743C0B6BFA885E30F101705764F43F8EF7E613DD0F07AD5178C7D9B1682B9E', + createTime: 1540716251106, + executeTime: 1540717251106, + completeTime: 1540718251106, +}; + +describe('Database', () => { + let db: DB; + let orderBookRepo: OrderBookRepository; + let p2pRepo: P2PRepository; + let swapRepo: SwapRepository; + + describe('initDb', () => { + const network = XuNetwork.SimNet; + const simnetNodes = defaultNodes(network)!; + const simnetCurrencies = defaultCurrencies(network)!; + const simnetPairs = defaultPairs(network)!; + + beforeEach(() => { + db = new DB(loggers.db); + orderBookRepo = new OrderBookRepository(db.models); + p2pRepo = new P2PRepository(db.models); + swapRepo = new SwapRepository(db.models); + }); + + it('should init the database from scratch', async () => { + await db.init(network, true); + const [nodes, currencies, pairs] = await Promise.all([ + p2pRepo.getNodes(), + orderBookRepo.getCurrencies(), + orderBookRepo.getPairs(), + ]); + + expect(nodes.length).toEqual(simnetNodes.length); + nodes.forEach((node, index) => { + const simnetNode = simnetNodes[index]; + expect(node.nodePubKey).toEqual(simnetNode.nodePubKey); + expect(node.addresses).toEqual(simnetNode.addresses); + }); + + expect(currencies.length).toEqual(simnetCurrencies.length); + currencies.forEach((currency, index) => { + const simnetCurrency = simnetCurrencies[index]; + expect(currency.id).toEqual(simnetCurrency.id); + expect(currency.tokenAddress ?? undefined).toEqual(simnetCurrency.tokenAddress); + }); + + expect(pairs.length).toEqual(simnetPairs.length); + pairs.forEach((pair, index) => { + const simnetPair = simnetPairs[index]; + expect(pair.baseCurrency).toEqual(simnetPair.baseCurrency); + expect(pair.quoteCurrency).toEqual(simnetPair.quoteCurrency); + }); + }); + + it('should add new default values to an existing db', async () => { + await db.init(); + + await Promise.all([ + orderBookRepo.addCurrency({ + id: 'ABC', + swapClient: SwapClientType.Connext, + decimalPlaces: 4, + }), + orderBookRepo.addCurrency({ + id: 'XYZ', + swapClient: SwapClientType.Connext, + decimalPlaces: 18, + }), + p2pRepo.addNodeIfNotExists({ + nodePubKey: peerPubKey, + addresses: [], + }), + ]); + await orderBookRepo.addPair({ + baseCurrency: 'ABC', + quoteCurrency: 'XYZ', + }); + await expect(orderBookRepo.getCurrencies()).resolves.toHaveLength(2); + await expect(orderBookRepo.getPairs()).resolves.toHaveLength(1); + await expect(p2pRepo.getNodes()).resolves.toHaveLength(1); + + await db.init(network, true); + + const [nodes, currencies, pairs] = await Promise.all([ + p2pRepo.getNodes(), + orderBookRepo.getCurrencies(), + orderBookRepo.getPairs(), + ]); + + expect(nodes.length).toEqual(simnetNodes.length + 1); + simnetNodes.forEach((simnetNode, index) => { + const node = nodes[index + 1]; + expect(node.nodePubKey).toEqual(simnetNode.nodePubKey); + expect(node.addresses).toEqual(simnetNode.addresses); + }); + + expect(currencies.length).toEqual(simnetCurrencies.length + 2); + simnetCurrencies.forEach((simnetCurrency, index) => { + const currency = currencies[index + 2]; + expect(currency.id).toEqual(simnetCurrency.id); + expect(currency.tokenAddress ?? undefined).toEqual(simnetCurrency.tokenAddress); + }); + + expect(pairs.length).toEqual(simnetPairs.length + 1); + simnetPairs.forEach((simnetPair, index) => { + const pair = pairs[index + 1]; + expect(pair.baseCurrency).toEqual(simnetPair.baseCurrency); + expect(pair.quoteCurrency).toEqual(simnetPair.quoteCurrency); + }); + }); + + afterEach(async () => { + await db.close(); + }); + }); + + describe('CRUD operations', () => { + beforeAll(async () => { + db = new DB(loggers.db); + await db.init(); + orderBookRepo = new OrderBookRepository(db.models); + p2pRepo = new P2PRepository(db.models); + swapRepo = new SwapRepository(db.models); + }); + + it('should add two currencies', async () => { + const btcPromise = orderBookRepo.addCurrency({ + id: 'BTC', + swapClient: SwapClientType.Lnd, + decimalPlaces: 8, + }); + const ltcPromise = orderBookRepo.addCurrency({ + id: 'LTC', + swapClient: SwapClientType.Lnd, + decimalPlaces: 8, + }); + await Promise.all([btcPromise, ltcPromise]); + await expect(orderBookRepo.getCurrencies()).resolves.toHaveLength(2); + }); + + it('should add a trading pair', async () => { + await orderBookRepo.addPair({ + baseCurrency: 'LTC', + quoteCurrency: 'BTC', + }); + await expect(orderBookRepo.getPairs()).resolves.toHaveLength(1); + }); + + it('should add a node', async () => { + await p2pRepo.addNodeIfNotExists({ + nodePubKey: peerPubKey, + addresses: [], + }); + }); + + it('should add an order', async () => { + await orderBookRepo.addOrderIfNotExists(order); + await expect(db.models.Order.count()).resolves.toEqual(1); + }); + + it('should not add the same order twice', async () => { + await orderBookRepo.addOrderIfNotExists(order); + await expect(db.models.Order.count()).resolves.toEqual(1); + }); + + it('should add a swap and a trade for the order', async () => { + await orderBookRepo.addOrderIfNotExists(order); + const { rHash } = deal; + const trade: TradeCreationAttributes = { rHash, quantity: deal.quantity!, makerOrderId: order.id }; + await orderBookRepo.addTrade(trade); + await swapRepo.saveSwapDeal(deal); + + const swapInstance = await db.models.SwapDeal.findOne({ where: { rHash } }); + expect(swapInstance!.orderId).toEqual(order.id); + const tradeInstance = await db.models.Trade.findOne({ where: { rHash } }); + expect(tradeInstance!.makerOrderId).toEqual(order.id); + }); + + it('should get a swap along with the order for the swap', async () => { + const swap = (await swapRepo.getSwapDeal(rHash))!; + expect(swap.Order!.id).toEqual(orderId); + const order = (await swap.getOrder())!; + expect(order.id).toEqual(orderId); + }); + + it('should get a swap along with its peer node', async () => { + const swap = (await swapRepo.getSwapDeal(rHash))!; + expect(swap.peerPubKey).toEqual(peerPubKey); + const node = (await swap.getNode())!; + expect(node.nodePubKey).toEqual(peerPubKey); + }); + + it('should add market orders and have their price in db be null', async () => { + const buyMarketOrder = createOwnOrder(Number.POSITIVE_INFINITY, quantity, true); + const sellMarketOrder = createOwnOrder(0, quantity, true); + await orderBookRepo.addOrderIfNotExists(buyMarketOrder); + await orderBookRepo.addOrderIfNotExists(sellMarketOrder); + const buyOrder = (await db.models.Order.findByPk(buyMarketOrder.id))!; + const sellOrder = (await db.models.Order.findByPk(sellMarketOrder.id))!; + expect(buyOrder.id).toEqual(buyMarketOrder.id); + expect(sellOrder.id).toEqual(sellMarketOrder.id); + expect(buyOrder.price).toBeNull(); + expect(sellOrder.price).toBeNull(); + }); + + it('should add two own orders and a trade between them', async () => { + const tradeQuantity = 10000000; + const maker = createOwnOrder(price, tradeQuantity, true); + const taker = createOwnOrder(price, tradeQuantity, false); + await Promise.all([orderBookRepo.addOrderIfNotExists(maker), orderBookRepo.addOrderIfNotExists(taker)]); + const trade: TradeCreationAttributes = { quantity: tradeQuantity, makerOrderId: maker.id, takerOrderId: taker.id }; + await orderBookRepo.addTrade(trade); + }); + + afterAll(async () => { + await db.close(); + }); + }); + +}); diff --git a/test/unit/DB.spec.ts b/test/unit/DB.spec.ts deleted file mode 100644 index cf5cc2177..000000000 --- a/test/unit/DB.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -import chai, { expect } from 'chai'; -import { SwapClientType, SwapPhase, SwapRole, SwapState } from '../../lib/constants/enums'; -import DB from '../../lib/db/DB'; -import { TradeCreationAttributes } from '../../lib/db/types'; -import Logger, { Level } from '../../lib/Logger'; -import OrderBookRepository from '../../lib/orderbook/OrderBookRepository'; -import P2PRepository from '../../lib/p2p/P2PRepository'; -import SwapRepository from '../../lib/swaps/SwapRepository'; -import { SwapDeal } from '../../lib/swaps/types'; -import { createOwnOrder, getTempDir } from '../utils'; -import chaiAsPromised = require('chai-as-promised'); - -chai.use(chaiAsPromised); - -const PAIR_ID = 'LTC/BTC'; -const loggers = Logger.createLoggers(Level.Warn); - -const price = 0.005; -const quantity = 10000000; -const peerPubKey = '03029c6a4d80c91da9e40529ec41c93b17cc9d7956b59c7d8334b0318d4a86aef8'; -const rHash = '62c8bbef4587cff4286246e63044dc3e454b5693fb5ebd0171b7e58644bfafe2'; - -const order = createOwnOrder(price, quantity, true); -const orderId = order.id; - -const deal: SwapDeal = { - quantity, - price, - peerPubKey, - rHash, - role: SwapRole.Maker, - phase: SwapPhase.PaymentReceived, - state: SwapState.Completed, - orderId: order.id, - localId: order.localId, - isBuy: order.isBuy, - proposedQuantity: quantity, - pairId: PAIR_ID, - takerCurrency: 'BTC', - makerCurrency: 'LTC', - takerAmount: 5000, - makerAmount: 1000000, - takerUnits: 5000, - makerUnits: 1000000, - takerCltvDelta: 144, - makerCltvDelta: 144, - rPreimage: '60743C0B6BFA885E30F101705764F43F8EF7E613DD0F07AD5178C7D9B1682B9E', - createTime: 1540716251106, - executeTime: 1540717251106, - completeTime: 1540718251106, -}; - -describe('Database', () => { - const db = new DB(loggers.db); - let orderBookRepo: OrderBookRepository; - let p2pRepo: P2PRepository; - let swapRepo: SwapRepository; - - before(async () => { - await db.init(); - orderBookRepo = new OrderBookRepository(db.models); - p2pRepo = new P2PRepository(db.models); - swapRepo = new SwapRepository(db.models); - }); - - it('should add two currencies', async () => { - const btcPromise = orderBookRepo.addCurrency({ - id: 'BTC', - swapClient: SwapClientType.Lnd, - decimalPlaces: 8, - }); - const ltcPromise = orderBookRepo.addCurrency({ - id: 'LTC', - swapClient: SwapClientType.Lnd, - decimalPlaces: 8, - }); - await Promise.all([btcPromise, ltcPromise]); - await expect(orderBookRepo.getCurrencies()).to.eventually.have.lengthOf(2); - }); - - it('should add a trading pair', async () => { - await orderBookRepo.addPair({ - baseCurrency: 'LTC', - quoteCurrency: 'BTC', - }); - await expect(orderBookRepo.getPairs()).to.eventually.have.lengthOf(1); - }); - - it('should add a node', async () => { - await p2pRepo.addNodeIfNotExists({ - nodePubKey: peerPubKey, - addresses: [], - }); - }); - - it('should add an order', async () => { - await orderBookRepo.addOrderIfNotExists(order); - await expect(db.models.Order.count()).to.eventually.equal(1); - }); - - it('should not add the same order twice', async () => { - await orderBookRepo.addOrderIfNotExists(order); - await expect(db.models.Order.count()).to.eventually.equal(1); - }); - - it('should add a swap and a trade for the order', async () => { - await orderBookRepo.addOrderIfNotExists(order); - const { rHash } = deal; - const trade: TradeCreationAttributes = { rHash, quantity: deal.quantity!, makerOrderId: order.id }; - await orderBookRepo.addTrade(trade); - await swapRepo.saveSwapDeal(deal); - - const swapInstance = await db.models.SwapDeal.findOne({ where: { rHash } }); - expect(swapInstance!.orderId).to.equal(order.id); - const tradeInstance = await db.models.Trade.findOne({ where: { rHash } }); - expect(tradeInstance!.makerOrderId).to.equal(order.id); - }); - - it('should get a swap along with the order for the swap', async () => { - const swap = (await swapRepo.getSwapDeal(rHash))!; - expect(swap.Order!.id).to.equal(orderId); - const order = (await swap.getOrder())!; - expect(order.id).to.equal(orderId); - }); - - it('should get a swap along with its peer node', async () => { - const swap = (await swapRepo.getSwapDeal(rHash))!; - expect(swap.peerPubKey).to.equal(peerPubKey); - const node = (await swap.getNode())!; - expect(node.nodePubKey).to.equal(peerPubKey); - }); - - it('should add market orders and have their price in db be null', async () => { - const buyMarketOrder = createOwnOrder(Number.POSITIVE_INFINITY, quantity, true); - const sellMarketOrder = createOwnOrder(0, quantity, true); - await orderBookRepo.addOrderIfNotExists(buyMarketOrder); - await orderBookRepo.addOrderIfNotExists(sellMarketOrder); - const buyOrder = (await db.models.Order.findByPk(buyMarketOrder.id))!; - const sellOrder = (await db.models.Order.findByPk(sellMarketOrder.id))!; - expect(buyOrder.id).to.equal(buyMarketOrder.id); - expect(sellOrder.id).to.equal(sellMarketOrder.id); - expect(buyOrder.price).to.be.null; - expect(sellOrder.price).to.be.null; - }); - - it('should add two own orders and a trade between them', async () => { - const tradeQuantity = 10000000; - const maker = createOwnOrder(price, tradeQuantity, true); - const taker = createOwnOrder(price, tradeQuantity, false); - await Promise.all([orderBookRepo.addOrderIfNotExists(maker), orderBookRepo.addOrderIfNotExists(taker)]); - const trade: TradeCreationAttributes = { quantity: tradeQuantity, makerOrderId: maker.id, takerOrderId: taker.id }; - await orderBookRepo.addTrade(trade); - }); - - after(async () => { - await db.close(); - }); -}); - -describe('isNewDb', () => { - it('should return true for a database in memory', async () => { - const dbInMemory = new DB(loggers.db); - expect(dbInMemory['isNewDb']()).to.eventually.be.true; - await dbInMemory.init(); - expect(dbInMemory['isNewDb']()).to.eventually.be.true; - }); - - it('should return true before a database on disk is created, false afterwards', async () => { - const dbInMemory = new DB(loggers.db, `${getTempDir(true)}/xud.db`); - expect(dbInMemory['isNewDb']()).to.eventually.be.true; - await dbInMemory.init(); - expect(dbInMemory['isNewDb']()).to.eventually.be.false; - }); -});