From d0ae443e5dfbf99c0c4a1ff55689fe52e67e46ed Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Tue, 25 Jun 2019 07:41:15 -0400 Subject: [PATCH] feat(raiden): channel balance by currency This modifies the logic around querying raiden for channel balances. Rather than summing up all balances across all tokens, it allows for specifying a currency and retrieving only the balance for that currency. Calling `ChannelBalance` without specifying a currency will return each currency separately. It also scales the balance returned by raiden to satoshis (10^-8). This is because the default units of 10^-18 for currencies such as WETH can exceed the maximum value of a `uint64` used by the gRPC layer. Closes #1051. --- lib/raidenclient/RaidenClient.ts | 20 ++++++++++---- lib/service/Service.ts | 26 ++++++++++-------- lib/swaps/SwapClient.ts | 4 ++- test/jest/RaidenClient.spec.ts | 47 ++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/lib/raidenclient/RaidenClient.ts b/lib/raidenclient/RaidenClient.ts index 54450d18c..037ffc0ef 100644 --- a/lib/raidenclient/RaidenClient.ts +++ b/lib/raidenclient/RaidenClient.ts @@ -41,6 +41,12 @@ class RaidenClient extends SwapClient { private host: string; private disable: boolean; + // TODO: Populate the mapping from the database (Currency.decimalPlaces). + private static readonly UNITS_PER_CURRENCY: { [key: string]: number } = { + WETH: 10 ** 10, + DAI: 10 ** 10, + }; + /** * Creates a raiden client. */ @@ -272,14 +278,18 @@ class RaidenClient extends SwapClient { } /** - * Returns the total balance available across all channels. + * Returns the total balance available across all channels for a specified currency. */ - public channelBalance = async (): Promise => { - // TODO: refine logic to determine balance per token rather than all combined - const channels = await this.getChannels(); + public channelBalance = async (currency?: string): Promise => { + if (!currency) { + return { balance: 0, pendingOpenBalance: 0 }; + } + + const channels = await this.getChannels(this.tokenAddresses.get(currency)); const balance = channels.filter(channel => channel.state === 'opened') .map(channel => channel.balance) - .reduce((acc, sum) => sum + acc, 0); + .reduce((acc, sum) => sum + acc, 0) + / (RaidenClient.UNITS_PER_CURRENCY[currency] || 1); return { balance, pendingOpenBalance: 0 }; } diff --git a/lib/service/Service.ts b/lib/service/Service.ts index 0a4363a79..4476a12b7 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -119,23 +119,27 @@ class Service { public channelBalance = async (args: { currency: string }) => { const { currency } = args; const balances = new Map(); - const getBalance = async (currency: string) => { + + if (currency) { + argChecks.VALID_CURRENCY(args); + const swapClient = this.swapClientManager.get(currency.toUpperCase()); if (swapClient) { - const channelBalance = await swapClient.channelBalance(); - return channelBalance; + const channelBalance = await swapClient.channelBalance(currency); + balances.set(currency, channelBalance); } else { throw swapsErrors.SWAP_CLIENT_NOT_FOUND(currency); } - }; - - if (currency) { - argChecks.VALID_CURRENCY(args); - balances.set(currency, await getBalance(currency)); } else { - for (const currency of this.orderBook.currencies) { - balances.set(currency, await getBalance(currency)); - } + const balancePromises: Promise[] = []; + this.swapClientManager.swapClients.forEach((swapClient, currency) => { + if (swapClient.isConnected()) { + balancePromises.push(swapClient.channelBalance(currency).then((channelBalance) => { + balances.set(currency, channelBalance); + })); + } + }); + await Promise.all(balancePromises); } return balances; diff --git a/lib/swaps/SwapClient.ts b/lib/swaps/SwapClient.ts index 503fc0221..2031dfbcd 100644 --- a/lib/swaps/SwapClient.ts +++ b/lib/swaps/SwapClient.ts @@ -44,8 +44,10 @@ abstract class SwapClient extends EventEmitter { /** * Returns the total balance available across all channels. + * @param currency the currency whose balance to query for, otherwise all/any + * currencies supported by this client are included in the balance. */ - public abstract channelBalance(): Promise; + public abstract channelBalance(currency?: string): Promise; protected setStatus = async (status: ClientStatus): Promise => { this.logger.info(`${this.constructor.name} status: ${ClientStatus[status]}`); diff --git a/test/jest/RaidenClient.spec.ts b/test/jest/RaidenClient.spec.ts index bc2c6336f..05e517e3a 100644 --- a/test/jest/RaidenClient.spec.ts +++ b/test/jest/RaidenClient.spec.ts @@ -16,6 +16,45 @@ const getValidTokenPaymentResponse = () => { }; }; +const channelBalance1 = 25000000; +const channelBalance2 = 5000000; +const channelBalanceTokenAddress = '0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8'; +const getChannelsResponse = [ + { + token_network_identifier: '0xE5637F0103794C7e05469A9964E4563089a5E6f2', + channel_identifier: 1, + partner_address: '0x61C808D82A3Ac53231750daDc13c777b59310bD9', + token_address: channelBalanceTokenAddress, + balance: channelBalance1, + total_deposit: 35000000, + state: 'opened', + settle_timeout: 100, + reveal_timeout: 30, + }, + { + token_network_identifier: '0xE5637F0103794C7e05469A9964E4563089a5E6f2', + channel_identifier: 2, + partner_address: '0x2A4722462bb06511b036F00C7EbF938B2377F446', + token_address: channelBalanceTokenAddress, + balance: channelBalance2, + total_deposit: 35000000, + state: 'opened', + settle_timeout: 100, + reveal_timeout: 30, + }, + { + token_network_identifier: '0xE5637F0103794C7e05469A9964E4563089a5E6f2', + channel_identifier: 3, + partner_address: '0x3b1c3C1568C848b3C12c88e2aF5E5CAa0b62071A', + token_address: channelBalanceTokenAddress, + balance: 1000000, + total_deposit: 35000000, + state: 'closed', + settle_timeout: 100, + reveal_timeout: 30, + }, +]; + const getValidDeal = () => { return { proposedQuantity: 10000, @@ -91,4 +130,12 @@ describe('RaidenClient', () => { .rejects.toMatchSnapshot(); }); + test('channelBalance calculates the total balance of open channels for a currency', async () => { + raiden = new RaidenClient(config, raidenLogger); + await raiden.init(); + raiden.tokenAddresses.get = jest.fn().mockReturnValue(channelBalanceTokenAddress); + raiden['getChannels'] = jest.fn() + .mockReturnValue(Promise.resolve(getChannelsResponse)); + await expect(raiden.channelBalance('ABC')).resolves.toHaveProperty('balance', channelBalance1 + channelBalance2); + }); });