From 2aa4eded5cdcbf9c22b5dc3a983d6f54e8a39fea Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Wed, 26 Jun 2019 18:26:33 -0400 Subject: [PATCH] fix: swap sent/received amounts in satoshis This scales the `amountReceived`, `amountSent`, `makerAmount`, and `takerAmount` fields to always be denominated in satoshis, even for currencies where the smallest unit is smaller than a satoshi. In their place this adds `takerUnits` and `makerUnits` to the `SwapDeal` type to track the amounts in each currency's base unit for sending & receiving. This prevents errors on the rpc layer, where amounts in wei can easily exceed the limits of `int64`. It also prevents issues in storing these values in the database, where these fields are likewise represented as 64 bit integers. This also renames several enum values to use the word "Payment" instead of "Amount" (as in "PaymentReceived") to avoid any possible confusion since "amount" is typically used to refer to a number. Fixes #1063. --- docs/api.md | 4 +- lib/constants/enums.ts | 4 +- lib/db/types.ts | 3 +- lib/lndclient/LndClient.ts | 4 +- lib/orderbook/OrderBook.ts | 8 ++-- lib/proto/xudrpc.swagger.json | 8 ++-- lib/proto/xudrpc_pb.js | 12 +++--- lib/raidenclient/RaidenClient.ts | 4 +- lib/swaps/Swaps.ts | 61 +++++++++++++++++------------- lib/swaps/types.ts | 12 ++++-- proto/xudrpc.proto | 8 ++-- test/integration/Swaps.spec.ts | 2 + test/jest/Orderbook.spec.ts | 2 + test/jest/RaidenClient.spec.ts | 2 + test/unit/DB.spec.ts | 2 + test/unit/Swaps.spec.ts | 65 +++++++++++++++++++++++--------- 16 files changed, 126 insertions(+), 75 deletions(-) diff --git a/docs/api.md b/docs/api.md index b830c4bbb..42bbafc98 100644 --- a/docs/api.md +++ b/docs/api.md @@ -951,8 +951,8 @@ | pair_id | [string](#string) | | The trading pair that the swap is for. | | quantity | [uint64](#uint64) | | The order quantity that was swapped. | | r_hash | [string](#string) | | The hex-encoded payment hash for the swap. | -| amount_received | [int64](#int64) | | The amount of the smallest base unit of the currency (like satoshis or wei) received. | -| amount_sent | [int64](#int64) | | The amount of the smallest base unit of the currency (like satoshis or wei) sent. | +| amount_received | [uint64](#uint64) | | The amount received denominated in satoshis. | +| amount_sent | [uint64](#uint64) | | The amount sent denominated in satoshis. | | peer_pub_key | [string](#string) | | The node pub key of the peer that executed this order. | | role | [SwapSuccess.Role](#xudrpc.SwapSuccess.Role) | | Our role in the swap, either MAKER or TAKER. | | currency_received | [string](#string) | | The ticker symbol of the currency received. | diff --git a/lib/constants/enums.ts b/lib/constants/enums.ts index 02f7f7420..1b2c11265 100644 --- a/lib/constants/enums.ts +++ b/lib/constants/enums.ts @@ -66,9 +66,9 @@ export enum SwapPhase { * could still fail due to no route with sufficient capacity, lack of cooperation from the * receiver or any intermediary node along the route, or an unexpected error from swap client. */ - SendingAmount = 3, + SendingPayment = 3, /** We have received the agreed amount of the swap, and the preimage is now known to both sides. */ - AmountReceived = 4, + PaymentReceived = 4, /** The swap has been formally completed and both sides have confirmed they've received payment. */ SwapCompleted = 5, } diff --git a/lib/db/types.ts b/lib/db/types.ts index a01f2c88b..40d54b2f1 100644 --- a/lib/db/types.ts +++ b/lib/db/types.ts @@ -28,7 +28,8 @@ export type CurrencyAttributes = CurrencyFactory & { export type CurrencyInstance = CurrencyAttributes & Sequelize.Instance; /* SwapDeal */ -export type SwapDealFactory = Pick>; +export type SwapDealFactory = Pick>; export type SwapDealAttributes = SwapDealFactory & { /** The internal db node id of the counterparty peer for this swap deal. */ diff --git a/lib/lndclient/LndClient.ts b/lib/lndclient/LndClient.ts index ba582eccc..aa716be46 100644 --- a/lib/lndclient/LndClient.ts +++ b/lib/lndclient/LndClient.ts @@ -312,11 +312,11 @@ class LndClient extends SwapClient { if (deal.role === SwapRole.Taker) { // we are the taker paying the maker request.setFinalCltvDelta(deal.makerCltvDelta!); - request.setAmt(deal.makerAmount); + request.setAmt(deal.makerUnits); } else { // we are the maker paying the taker request.setFinalCltvDelta(deal.takerCltvDelta); - request.setAmt(deal.takerAmount); + request.setAmt(deal.takerUnits); } try { diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index e938e277a..715e720c5 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -173,7 +173,7 @@ class OrderBook extends EventEmitter { } }); this.swaps.on('swap.failed', (deal) => { - if (deal.role === SwapRole.Maker && (deal.phase === SwapPhase.SwapAgreed || deal.phase === SwapPhase.SendingAmount)) { + if (deal.role === SwapRole.Maker && (deal.phase === SwapPhase.SwapAgreed || deal.phase === SwapPhase.SendingPayment)) { // if our order is the maker and the swap failed after it was agreed to but before it was executed // we must release the hold on the order that we set when we agreed to the deal this.removeOrderHold(deal.orderId, deal.pairId, deal.quantity!); @@ -376,13 +376,13 @@ class OrderBook extends EventEmitter { if (!this.nobalancechecks) { // check if sufficient outbound channel capacity exists - const { outboundCurrency, outboundAmount } = Swaps.calculateInboundOutboundAmounts(order.quantity, order.price, order.isBuy, order.pairId); + const { outboundCurrency, outboundUnits } = Swaps.calculateInboundOutboundAmounts(order.quantity, order.price, order.isBuy, order.pairId); const swapClient = this.swaps.swapClientManager.get(outboundCurrency); if (!swapClient) { throw swapsErrors.SWAP_CLIENT_NOT_FOUND(outboundCurrency); } - if (outboundAmount > swapClient.maximumOutboundCapacity) { - throw errors.INSUFFICIENT_OUTBOUND_BALANCE(outboundCurrency, outboundAmount, swapClient.maximumOutboundCapacity); + if (outboundUnits > swapClient.maximumOutboundCapacity) { + throw errors.INSUFFICIENT_OUTBOUND_BALANCE(outboundCurrency, outboundUnits, swapClient.maximumOutboundCapacity); } } diff --git a/lib/proto/xudrpc.swagger.json b/lib/proto/xudrpc.swagger.json index 67135d01b..8cf5f6f67 100644 --- a/lib/proto/xudrpc.swagger.json +++ b/lib/proto/xudrpc.swagger.json @@ -1321,13 +1321,13 @@ }, "amount_received": { "type": "string", - "format": "int64", - "description": "The amount of the smallest base unit of the currency (like satoshis or wei) received." + "format": "uint64", + "description": "The amount received denominated in satoshis." }, "amount_sent": { "type": "string", - "format": "int64", - "description": "The amount of the smallest base unit of the currency (like satoshis or wei) sent." + "format": "uint64", + "description": "The amount sent denominated in satoshis." }, "peer_pub_key": { "type": "string", diff --git a/lib/proto/xudrpc_pb.js b/lib/proto/xudrpc_pb.js index f05adc5d8..7d9f1d5e3 100644 --- a/lib/proto/xudrpc_pb.js +++ b/lib/proto/xudrpc_pb.js @@ -9943,11 +9943,11 @@ proto.xudrpc.SwapSuccess.deserializeBinaryFromReader = function(msg, reader) { msg.setRHash(value); break; case 8: - var value = /** @type {number} */ (reader.readInt64()); + var value = /** @type {number} */ (reader.readUint64()); msg.setAmountReceived(value); break; case 9: - var value = /** @type {number} */ (reader.readInt64()); + var value = /** @type {number} */ (reader.readUint64()); msg.setAmountSent(value); break; case 10: @@ -10040,14 +10040,14 @@ proto.xudrpc.SwapSuccess.serializeBinaryToWriter = function(message, writer) { } f = message.getAmountReceived(); if (f !== 0) { - writer.writeInt64( + writer.writeUint64( 8, f ); } f = message.getAmountSent(); if (f !== 0) { - writer.writeInt64( + writer.writeUint64( 9, f ); @@ -10181,7 +10181,7 @@ proto.xudrpc.SwapSuccess.prototype.setRHash = function(value) { /** - * optional int64 amount_received = 8; + * optional uint64 amount_received = 8; * @return {number} */ proto.xudrpc.SwapSuccess.prototype.getAmountReceived = function() { @@ -10196,7 +10196,7 @@ proto.xudrpc.SwapSuccess.prototype.setAmountReceived = function(value) { /** - * optional int64 amount_sent = 9; + * optional uint64 amount_sent = 9; * @return {number} */ proto.xudrpc.SwapSuccess.prototype.getAmountSent = function() { diff --git a/lib/raidenclient/RaidenClient.ts b/lib/raidenclient/RaidenClient.ts index 54450d18c..8c5fd92a4 100644 --- a/lib/raidenclient/RaidenClient.ts +++ b/lib/raidenclient/RaidenClient.ts @@ -110,11 +110,11 @@ class RaidenClient extends SwapClient { let tokenAddress; if (deal.role === SwapRole.Maker) { // we are the maker paying the taker - amount = deal.takerAmount; + amount = deal.takerUnits; tokenAddress = this.tokenAddresses.get(deal.takerCurrency); } else { // we are the taker paying the maker - amount = deal.makerAmount; + amount = deal.makerUnits; tokenAddress = this.tokenAddresses.get(deal.makerCurrency); } if (!tokenAddress) { diff --git a/lib/swaps/Swaps.ts b/lib/swaps/Swaps.ts index 944227e0f..5b1072efb 100644 --- a/lib/swaps/Swaps.ts +++ b/lib/swaps/Swaps.ts @@ -82,13 +82,15 @@ class Swaps extends EventEmitter { * @returns An object with the calculated maker and taker values. */ private static calculateMakerTakerAmounts = (quantity: number, price: number, isBuy: boolean, pairId: string) => { - const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount } = + const { inboundCurrency, inboundAmount, inboundUnits, outboundCurrency, outboundAmount, outboundUnits } = Swaps.calculateInboundOutboundAmounts(quantity, price, isBuy, pairId); return { makerCurrency: inboundCurrency, makerAmount: inboundAmount, + makerUnits: inboundUnits, takerCurrency: outboundCurrency, takerAmount: outboundAmount, + takerUnits: outboundUnits, }; } @@ -102,16 +104,20 @@ class Swaps extends EventEmitter { */ public static calculateInboundOutboundAmounts = (quantity: number, price: number, isBuy: boolean, pairId: string) => { const [baseCurrency, quoteCurrency] = pairId.split('/'); - const baseCurrencyAmount = Math.round(quantity * Swaps.UNITS_PER_CURRENCY[baseCurrency]); + const baseCurrencyAmount = quantity; const quoteCurrencyAmount = price > 0 && price < Number.POSITIVE_INFINITY ? - Math.round(quantity * price * Swaps.UNITS_PER_CURRENCY[quoteCurrency]) : + Math.round(quantity * price) : 0; // if price is zero or infinity, this is a market order and we can't know the quote currency amount + const baseCurrencyUnits = Math.floor(baseCurrencyAmount * Swaps.UNITS_PER_CURRENCY[baseCurrency]); + const quoteCurrencyUnits = Math.floor(quoteCurrencyAmount * Swaps.UNITS_PER_CURRENCY[quoteCurrency]); const inboundCurrency = isBuy ? baseCurrency : quoteCurrency; const inboundAmount = isBuy ? baseCurrencyAmount : quoteCurrencyAmount; + const inboundUnits = isBuy ? baseCurrencyUnits : quoteCurrencyUnits; const outboundCurrency = isBuy ? quoteCurrency : baseCurrency; const outboundAmount = isBuy ? quoteCurrencyAmount : baseCurrencyAmount; - return { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount }; + const outboundUnits = isBuy ? quoteCurrencyUnits : baseCurrencyUnits; + return { inboundCurrency, inboundAmount, inboundUnits, outboundCurrency, outboundAmount, outboundUnits }; } public init = async () => { @@ -242,7 +248,7 @@ class Swaps extends EventEmitter { throw SwapFailureReason.SwapClientNotSetup; } - const { makerCurrency, makerAmount } = Swaps.calculateMakerTakerAmounts(taker.quantity, maker.price, maker.isBuy, maker.pairId); + const { makerCurrency, makerUnits } = Swaps.calculateMakerTakerAmounts(taker.quantity, maker.price, maker.isBuy, maker.pairId); const swapClient = this.swapClientManager.get(makerCurrency)!; @@ -254,7 +260,7 @@ class Swaps extends EventEmitter { let routes; try { - routes = await swapClient.getRoutes(makerAmount, destination); + routes = await swapClient.getRoutes(makerUnits, destination); } catch (err) { throw SwapFailureReason.UnexpectedClientError; } @@ -359,7 +365,7 @@ class Swaps extends EventEmitter { const peer = this.pool.getPeer(maker.peerPubKey); const quantity = Math.min(maker.quantity, taker.quantity); - const { makerCurrency, makerAmount, takerCurrency, takerAmount } = + const { makerCurrency, makerAmount, makerUnits, takerCurrency, takerAmount, takerUnits } = Swaps.calculateMakerTakerAmounts(quantity, maker.price, maker.isBuy, maker.pairId); const clientType = this.swapClientManager.get(makerCurrency)!.type; const destination = peer.getIdentifier(clientType, makerCurrency)!; @@ -382,6 +388,8 @@ class Swaps extends EventEmitter { makerCurrency, takerAmount, makerAmount, + takerUnits, + makerUnits, destination, peerPubKey: peer.nodePubKey!, localId: taker.localId, @@ -433,7 +441,8 @@ class Swaps extends EventEmitter { const { quantity, price, isBuy } = orderToAccept; - const { makerCurrency, makerAmount, takerCurrency, takerAmount } = Swaps.calculateMakerTakerAmounts(quantity, price, isBuy, requestBody.pairId); + const { makerCurrency, makerAmount, makerUnits, takerCurrency, takerAmount, takerUnits } = + Swaps.calculateMakerTakerAmounts(quantity, price, isBuy, requestBody.pairId); const takerSwapClient = this.swapClientManager.get(takerCurrency); if (!takerSwapClient) { @@ -459,6 +468,8 @@ class Swaps extends EventEmitter { takerAmount, makerCurrency, takerCurrency, + makerUnits, + takerUnits, destination: takerPubKey, peerPubKey: peer.nodePubKey!, localId: orderToAccept.localId, @@ -487,7 +498,7 @@ class Swaps extends EventEmitter { } try { - deal.makerToTakerRoutes = await takerSwapClient.getRoutes(takerAmount, takerPubKey, deal.takerCltvDelta); + deal.makerToTakerRoutes = await takerSwapClient.getRoutes(takerUnits, takerPubKey, deal.takerCltvDelta); } catch (err) { this.failDeal(deal, SwapFailureReason.UnexpectedClientError, err.message); await this.sendErrorToPeer({ @@ -562,7 +573,7 @@ class Swaps extends EventEmitter { const makerSwapClient = this.swapClientManager.get(makerCurrency)!; try { - await makerSwapClient.addInvoice(deal.rHash, deal.makerAmount, deal.makerCltvDelta); + await makerSwapClient.addInvoice(deal.rHash, deal.makerUnits, deal.makerCltvDelta); } catch (err) { this.failDeal(deal, SwapFailureReason.UnexpectedClientError, `could not add invoice for while accepting deal: ${err.message}`); await this.sendErrorToPeer({ @@ -639,7 +650,7 @@ class Swaps extends EventEmitter { } try { - await takerSwapClient.addInvoice(deal.rHash, deal.takerAmount, takerSwapClient.cltvDelta); + await takerSwapClient.addInvoice(deal.rHash, deal.takerUnits, takerSwapClient.cltvDelta); } catch (err) { this.failDeal(deal, SwapFailureReason.UnexpectedClientError, err.message); await this.sendErrorToPeer({ @@ -652,7 +663,7 @@ class Swaps extends EventEmitter { } try { - this.setDealPhase(deal, SwapPhase.SendingAmount); + this.setDealPhase(deal, SwapPhase.SendingPayment); await makerSwapClient.sendPayment(deal); // TODO: check preimage from payment response vs deal.preImage @@ -686,12 +697,12 @@ class Swaps extends EventEmitter { switch (deal.role) { case SwapRole.Maker: - expectedAmount = deal.makerAmount; + expectedAmount = deal.makerUnits; source = 'Taker'; destination = 'Maker'; break; case SwapRole.Taker: - expectedAmount = deal.takerAmount; + expectedAmount = deal.takerUnits; source = 'Maker'; destination = 'Taker'; break; @@ -701,8 +712,6 @@ class Swaps extends EventEmitter { return false; } - // TODO: convert amount to satoshis 1E-8 - if (amount < expectedAmount) { this.logger.error(`received ${amount}, expected ${expectedAmount}`); this.failDeal(deal, SwapFailureReason.InvalidResolveRequest, `Amount sent from ${source} to ${destination} is too small`); @@ -779,9 +788,9 @@ class Swaps extends EventEmitter { const swapClient = this.swapClientManager.get(deal.takerCurrency)!; try { - this.setDealPhase(deal, SwapPhase.SendingAmount); + this.setDealPhase(deal, SwapPhase.SendingPayment); deal.rPreimage = await swapClient.sendPayment(deal); - this.setDealPhase(deal, SwapPhase.AmountReceived); + this.setDealPhase(deal, SwapPhase.PaymentReceived); return deal.rPreimage; } catch (err) { this.failDeal(deal, SwapFailureReason.SendPaymentFailure, err.message); @@ -793,7 +802,7 @@ class Swaps extends EventEmitter { assert(htlcCurrency === undefined || htlcCurrency === deal.takerCurrency, 'incoming htlc does not match expected deal currency'); this.logger.debug('Executing taker code to resolve hash'); - this.setDealPhase(deal, SwapPhase.AmountReceived); + this.setDealPhase(deal, SwapPhase.PaymentReceived); return deal.rPreimage!; } } @@ -900,18 +909,18 @@ class Swaps extends EventEmitter { assert(deal.phase === SwapPhase.SwapCreated, 'SwapAgreed can be only be set after SwapCreated'); this.logger.debug('Sending swap response to peer '); break; - case SwapPhase.SendingAmount: + case SwapPhase.SendingPayment: assert(deal.role === SwapRole.Taker && deal.phase === SwapPhase.SwapRequested || deal.role === SwapRole.Maker && deal.phase === SwapPhase.SwapAgreed, - 'SendingAmount can only be set after SwapRequested (taker) or SwapAgreed (maker)'); + 'SendingPayment can only be set after SwapRequested (taker) or SwapAgreed (maker)'); deal.executeTime = Date.now(); break; - case SwapPhase.AmountReceived: - assert(deal.phase === SwapPhase.SendingAmount, 'AmountReceived can be only be set after SendingAmount'); - this.logger.debug(`Amount received for deal with payment hash ${deal.rPreimage}`); + case SwapPhase.PaymentReceived: + assert(deal.phase === SwapPhase.SendingPayment, 'PaymentReceived can be only be set after SendingPayment'); + this.logger.debug(`Payment received for deal with payment hash ${deal.rPreimage}`); break; case SwapPhase.SwapCompleted: - assert(deal.phase === SwapPhase.AmountReceived, 'SwapCompleted can be only be set after AmountReceived'); + assert(deal.phase === SwapPhase.PaymentReceived, 'SwapCompleted can be only be set after PaymentReceived'); deal.completeTime = Date.now(); deal.state = SwapState.Completed; this.logger.debug(`Swap completed. preimage = ${deal.rPreimage}`); @@ -922,7 +931,7 @@ class Swaps extends EventEmitter { deal.phase = newPhase; - if (deal.phase === SwapPhase.AmountReceived) { + if (deal.phase === SwapPhase.PaymentReceived) { const wasMaker = deal.role === SwapRole.Maker; const swapSuccess = { orderId: deal.orderId, diff --git a/lib/swaps/types.ts b/lib/swaps/types.ts index a08465423..052321589 100644 --- a/lib/swaps/types.ts +++ b/lib/swaps/types.ts @@ -32,16 +32,20 @@ export type SwapDeal = { quantity?: number; /** The trading pair for the swap. The pairId together with the orderId are needed to find the maker order in the order book. */ pairId: string; - /** The number of satoshis (or equivalent) the taker is expecting to receive. */ + /** The amount the taker is expecting to receive denominated in satoshis. */ takerAmount: number; + /** The number of the smallest base units of the currency (like satoshis or wei) the maker is expecting to receive. */ + takerUnits: number; /** The currency the taker is expecting to receive. */ takerCurrency: string; /** Taker's lnd pubkey on the taker currency's network. */ takerPubKey?: string; /** The CLTV delta from the current height that should be used to set the timelock for the final hop when sending to taker. */ takerCltvDelta: number; - /** The number of satoshis (or equivalent) the maker is expecting to receive. */ + /** The amount the maker is expecting to receive denominated in satoshis. */ makerAmount: number; + /** The number of the smallest base units of the currency (like satoshis or wei) the maker is expecting to receive. */ + makerUnits: number; /** The currency the maker is expecting to receive. */ makerCurrency: string; /** The CLTV delta from the current height that should be used to set the timelock for the final hop when sending to maker. */ @@ -63,9 +67,9 @@ export type SwapDeal = { /** The result of a successful swap. */ export type SwapSuccess = Pick & { - /** The amount of satoshis (or equivalent) received. */ + /** The amount received denominated in satoshis. */ amountReceived: number; - /** The amount of satoshis (or equivalent) sent. */ + /** The amount sent denominated in satoshis. */ amountSent: number; /** The ticker symbol of the currency received. */ currencyReceived: string; diff --git a/proto/xudrpc.proto b/proto/xudrpc.proto index d99ca2522..82dcbab5c 100644 --- a/proto/xudrpc.proto +++ b/proto/xudrpc.proto @@ -605,10 +605,10 @@ message SwapSuccess { uint64 quantity = 4 [json_name = "quantity"]; // The hex-encoded payment hash for the swap. string r_hash = 5 [json_name = "r_hash"]; - // The amount of the smallest base unit of the currency (like satoshis or wei) received. - int64 amount_received = 8 [json_name = "amount_received"]; - // The amount of the smallest base unit of the currency (like satoshis or wei) sent. - int64 amount_sent = 9 [json_name = "amount_sent"]; + // The amount received denominated in satoshis. + uint64 amount_received = 8 [json_name = "amount_received"]; + // The amount sent denominated in satoshis. + uint64 amount_sent = 9 [json_name = "amount_sent"]; // The node pub key of the peer that executed this order. string peer_pub_key = 10 [json_name = "peer_pub_key"]; enum Role { diff --git a/test/integration/Swaps.spec.ts b/test/integration/Swaps.spec.ts index 8b453d137..a45c77b16 100644 --- a/test/integration/Swaps.spec.ts +++ b/test/integration/Swaps.spec.ts @@ -69,6 +69,8 @@ const validSwapDeal = () => { makerCurrency: 'LTC', takerAmount: 8, makerAmount: 1000, + takerUnits: 8, + makerUnits: 1000, peerPubKey: '030130758847ada485520016a075833b8638c7e5a56889cb4b76e10c0f61f3520c', localId: '20b63440-e689-11e8-aa83-51505ebd3ca7', price: 0.008, diff --git a/test/jest/Orderbook.spec.ts b/test/jest/Orderbook.spec.ts index 02b68cdc7..b77c39e01 100644 --- a/test/jest/Orderbook.spec.ts +++ b/test/jest/Orderbook.spec.ts @@ -184,8 +184,10 @@ describe('OrderBook', () => { return { inboundCurrency: 'BTC', inboundAmount: 50000000000, + inboundUnits: 50000000000, outboundCurrency: 'LTC', outboundAmount: quantity, + outboundUnits: quantity, }; }; swaps.swapClientManager.get = jest.fn().mockReturnValue({ diff --git a/test/jest/RaidenClient.spec.ts b/test/jest/RaidenClient.spec.ts index bc2c6336f..f32f6f33a 100644 --- a/test/jest/RaidenClient.spec.ts +++ b/test/jest/RaidenClient.spec.ts @@ -29,6 +29,8 @@ const getValidDeal = () => { quantity: 10000, makerAmount: 10000, takerAmount: 1000, + makerUnits: 10000, + takerUnits: 1000, makerCurrency: 'LTC', takerCurrency: 'BTC', destination: '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac', diff --git a/test/unit/DB.spec.ts b/test/unit/DB.spec.ts index 03036aa47..0a9a1ed11 100644 --- a/test/unit/DB.spec.ts +++ b/test/unit/DB.spec.ts @@ -40,6 +40,8 @@ const deal: SwapDeal = { makerCurrency: 'LTC', takerAmount: 5000, makerAmount: 1000000, + takerUnits: 5000, + makerUnits: 1000000, takerCltvDelta: 144, makerCltvDelta: 144, rPreimage: '60743C0B6BFA885E30F101705764F43F8EF7E613DD0F07AD5178C7D9B1682B9E', diff --git a/test/unit/Swaps.spec.ts b/test/unit/Swaps.spec.ts index 51da9dccc..46a0e6c66 100644 --- a/test/unit/Swaps.spec.ts +++ b/test/unit/Swaps.spec.ts @@ -30,8 +30,10 @@ describe('Swaps', () => { isBuy: true, makerCurrency: 'LTC', takerCurrency: 'BTC', - makerAmount: Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity, - takerAmount: Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price, + makerAmount: quantity, + takerAmount: quantity * price, + makerUnits: Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity, + takerUnits: Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price, createTime: 1540716251106, }; @@ -40,8 +42,10 @@ describe('Swaps', () => { pairId: 'WETH/BTC', makerCurrency: 'WETH', takerCurrency: 'BTC', - makerAmount: Swaps['UNITS_PER_CURRENCY']['WETH'] * quantity, - takerAmount: Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price, + makerAmount: quantity, + takerAmount: quantity * price, + makerUnits: Swaps['UNITS_PER_CURRENCY']['WETH'] * quantity, + takerUnits: Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price, }; /** A swap deal for a sell order, mirrored from the buy deal for convenience. */ @@ -52,6 +56,8 @@ describe('Swaps', () => { makerCurrency: buyDeal.takerCurrency, takerAmount: buyDeal.makerAmount, makerAmount: buyDeal.takerAmount, + takerUnits: buyDeal.makerUnits, + makerUnits: buyDeal.takerUnits, }; const swapRequest: SwapRequestPacketBody = { @@ -63,21 +69,27 @@ describe('Swaps', () => { }; it('should calculate swap amounts and currencies for a buy order', () => { - const { makerCurrency, makerAmount, takerCurrency, takerAmount } = Swaps['calculateMakerTakerAmounts']( - buyDeal.quantity!, buyDeal.price, buyDeal.isBuy, buyDeal.pairId, - ); + const { makerCurrency, makerAmount, takerCurrency, takerAmount, makerUnits, takerUnits } = + Swaps['calculateMakerTakerAmounts']( + buyDeal.quantity!, buyDeal.price, buyDeal.isBuy, buyDeal.pairId, + ); expect(makerAmount).to.equal(buyDeal.makerAmount); expect(takerAmount).to.equal(buyDeal.takerAmount); + expect(makerUnits).to.equal(buyDeal.makerUnits); + expect(takerUnits).to.equal(buyDeal.takerUnits); expect(makerCurrency).to.equal(buyDeal.makerCurrency); expect(takerCurrency).to.equal(buyDeal.takerCurrency); }); it('should calculate swap amounts and currencies for a sell order', () => { - const { makerCurrency, makerAmount, takerCurrency, takerAmount } = Swaps['calculateMakerTakerAmounts']( - sellDeal.quantity!, sellDeal.price, sellDeal.isBuy, sellDeal.pairId, - ); + const { makerCurrency, makerAmount, takerCurrency, takerAmount, makerUnits, takerUnits } = + Swaps['calculateMakerTakerAmounts']( + sellDeal.quantity!, sellDeal.price, sellDeal.isBuy, sellDeal.pairId, + ); expect(makerAmount).to.equal(sellDeal.makerAmount); expect(takerAmount).to.equal(sellDeal.takerAmount); + expect(makerUnits).to.equal(sellDeal.makerUnits); + expect(takerUnits).to.equal(sellDeal.takerUnits); expect(makerCurrency).to.equal(sellDeal.makerCurrency); expect(takerCurrency).to.equal(sellDeal.takerCurrency); }); @@ -93,35 +105,52 @@ describe('Swaps', () => { }); it('should calculate inbound and outbound amounts and currencies for a buy order', () => { - const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount } = + const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount, inboundUnits, outboundUnits } = Swaps.calculateInboundOutboundAmounts(quantity, price, true, pairId); expect(inboundCurrency).to.equal('LTC'); - expect(inboundAmount).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity); + expect(inboundAmount).to.equal(quantity); + expect(inboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity); expect(outboundCurrency).to.equal('BTC'); - expect(outboundAmount).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity * price); + expect(outboundAmount).to.equal(quantity * price); + expect(outboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price); }); it('should calculate inbound and outbound amounts and currencies for a sell order', () => { - const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount } = + const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount, inboundUnits, outboundUnits } = Swaps.calculateInboundOutboundAmounts(quantity, price, false, pairId); expect(inboundCurrency).to.equal('BTC'); - expect(inboundAmount).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity * price); + expect(inboundAmount).to.equal(quantity * price); + expect(inboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price); expect(outboundCurrency).to.equal('LTC'); - expect(outboundAmount).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity); + expect(outboundAmount).to.equal(quantity); + expect(outboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['LTC'] * quantity); }); it('should calculate 0 outbound amount for a market buy order', () => { - const { outboundCurrency, outboundAmount } = + const { outboundCurrency, outboundAmount, outboundUnits } = Swaps.calculateInboundOutboundAmounts(quantity, 0, true, pairId); expect(outboundCurrency).to.equal('BTC'); expect(outboundAmount).to.equal(0); + expect(outboundUnits).to.equal(0); }); it('should calculate 0 inbound amount for a market sell order', () => { - const { inboundCurrency, inboundAmount } = + const { inboundCurrency, inboundAmount, inboundUnits } = Swaps.calculateInboundOutboundAmounts(quantity, Number.POSITIVE_INFINITY, false, pairId); expect(inboundCurrency).to.equal('BTC'); expect(inboundAmount).to.equal(0); + expect(inboundUnits).to.equal(0); + }); + + it('should calculate inbound and outbound amounts and currencies for a raiden order', () => { + const { inboundCurrency, inboundAmount, outboundCurrency, outboundAmount, inboundUnits, outboundUnits } = + Swaps.calculateInboundOutboundAmounts(quantity, price, true, 'WETH/BTC'); + expect(inboundCurrency).to.equal('WETH'); + expect(inboundAmount).to.equal(quantity); + expect(inboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['WETH'] * quantity); + expect(outboundCurrency).to.equal('BTC'); + expect(outboundAmount).to.equal(quantity * price); + expect(outboundUnits).to.equal(Swaps['UNITS_PER_CURRENCY']['BTC'] * quantity * price); }); it('should validate a good swap request', () => {