Skip to content

Commit

Permalink
refactor: clean up account limits abstractions & unit test (#3290)
Browse files Browse the repository at this point in the history
* refactor: simplify 'AccountLimitsChecker' abstraction into flat functions

* refactor: add 'tx volume calculator'

* refactor: refactor limit checks into 'limit checker'

* chore: move check-limit use-cases & refactor calling code

* test: add multiple wallets for wallet-volume tests
  • Loading branch information
vindard authored Oct 2, 2023
1 parent 21cca06 commit fc57df9
Show file tree
Hide file tree
Showing 18 changed files with 629 additions and 418 deletions.
184 changes: 144 additions & 40 deletions src/app/accounts/account-limit.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,167 @@
import { getMidPriceRatio } from "@app/prices"
import { getAccountLimits, ONE_DAY } from "@config"

import { getAccountLimits, getDealerConfig, ONE_DAY } from "@config"

import { AccountLimitsType } from "@domain/accounts"
import { AccountLimitsVolumes } from "@domain/accounts/limits-volume"
import {
AccountLimitsType,
AccountTxVolumeLimitChecker,
AccountTxVolumeRemaining,
} from "@domain/accounts"
import { InvalidAccountLimitTypeError } from "@domain/errors"

import * as LedgerFacade from "@services/ledger/facade"
import { WalletsRepository } from "@services/mongoose"
import { AccountsRepository, WalletsRepository } from "@services/mongoose"

export const remainingLimit = async ({
account,
limitType,
export const remainingIntraLedgerLimit = async ({
accountId,
priceRatio,
}: {
account: Account
limitType: AccountLimitsType
}): Promise<UsdPaymentAmount | ApplicationError> => {
const usdHedgeEnabled = getDealerConfig().usd.hedgingEnabled
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountVolumeRemaining = AccountTxVolumeRemaining(accountLimits)

const accountWalletDescriptors =
await WalletsRepository().findAccountWalletsByAccountId(accountId)
if (accountWalletDescriptors instanceof Error) return accountWalletDescriptors

const priceRatio = await getMidPriceRatio(usdHedgeEnabled)
if (priceRatio instanceof Error) return priceRatio
const walletVolumes = await LedgerFacade.intraledgerTxBaseVolumeAmountForAccountSince({
accountWalletDescriptors,
period: ONE_DAY,
})
if (walletVolumes instanceof Error) return walletVolumes

return accountVolumeRemaining.intraLedger({
priceRatio,
walletVolumes,
})
}

export const checkIntraledgerLimits = async ({
amount,
accountId,
priceRatio,
}: {
amount: UsdPaymentAmount
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountLimitsChecker = AccountTxVolumeLimitChecker(accountLimits)

const accountVolumes = AccountLimitsVolumes({ accountLimits, priceRatio })
if (accountVolumes instanceof Error) return accountVolumes
const volumeRemaining = await remainingIntraLedgerLimit({ accountId, priceRatio })
if (volumeRemaining instanceof Error) return volumeRemaining

let limitsVolumeFn: LimitsVolumesFn
let getVolumeFn: GetVolumeAmountForAccountSinceFn
switch (limitType) {
case AccountLimitsType.IntraLedger:
limitsVolumeFn = accountVolumes.volumesIntraledger
getVolumeFn = LedgerFacade.intraledgerTxBaseVolumeAmountForAccountSince
break
case AccountLimitsType.Withdrawal:
limitsVolumeFn = accountVolumes.volumesWithdrawal
getVolumeFn = LedgerFacade.externalPaymentVolumeAmountForAccountSince
break
case AccountLimitsType.SelfTrade:
limitsVolumeFn = accountVolumes.volumesTradeIntraAccount
getVolumeFn = LedgerFacade.tradeIntraAccountTxBaseVolumeAmountForAccountSince
break
default:
return new InvalidAccountLimitTypeError(limitType)
}
return accountLimitsChecker.checkIntraledger({
amount,
volumeRemaining,
})
}

export const remainingWithdrawalLimit = async ({
accountId,
priceRatio,
}: {
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountVolumeRemaining = AccountTxVolumeRemaining(accountLimits)

const accountWalletDescriptors =
await WalletsRepository().findAccountWalletsByAccountId(account.id)
await WalletsRepository().findAccountWalletsByAccountId(accountId)
if (accountWalletDescriptors instanceof Error) return accountWalletDescriptors
const walletVolumes = await getVolumeFn({

const walletVolumes = await LedgerFacade.externalPaymentVolumeAmountForAccountSince({
accountWalletDescriptors,
period: ONE_DAY,
})
if (walletVolumes instanceof Error) return walletVolumes

const limitVolumes = await limitsVolumeFn(walletVolumes)
if (limitVolumes instanceof Error) return limitVolumes
return accountVolumeRemaining.withdrawal({
priceRatio,
walletVolumes,
})
}

export const checkWithdrawalLimits = async ({
amount,
accountId,
priceRatio,
}: {
amount: UsdPaymentAmount
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountLimitsChecker = AccountTxVolumeLimitChecker(accountLimits)

const volumeRemaining = await remainingWithdrawalLimit({ accountId, priceRatio })
if (volumeRemaining instanceof Error) return volumeRemaining

return accountLimitsChecker.checkWithdrawal({
amount,
volumeRemaining,
})
}

export const remainingTradeIntraAccountLimit = async ({
accountId,
priceRatio,
}: {
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountVolumeRemaining = AccountTxVolumeRemaining(accountLimits)

const accountWalletDescriptors =
await WalletsRepository().findAccountWalletsByAccountId(accountId)
if (accountWalletDescriptors instanceof Error) return accountWalletDescriptors

const walletVolumes =
await LedgerFacade.tradeIntraAccountTxBaseVolumeAmountForAccountSince({
accountWalletDescriptors,
period: ONE_DAY,
})
if (walletVolumes instanceof Error) return walletVolumes

return accountVolumeRemaining.tradeIntraAccount({
priceRatio,
walletVolumes,
})
}

export const checkTradeIntraAccountLimits = async ({
amount,
accountId,
priceRatio,
}: {
amount: UsdPaymentAmount
accountId: AccountId
priceRatio: WalletPriceRatio
}) => {
const account = await AccountsRepository().findById(accountId)
if (account instanceof Error) return account
const accountLimits = getAccountLimits({ level: account.level })
const accountLimitsChecker = AccountTxVolumeLimitChecker(accountLimits)

const volumeRemaining = await remainingTradeIntraAccountLimit({ accountId, priceRatio })
if (volumeRemaining instanceof Error) return volumeRemaining

return limitVolumes.volumeRemaining
return accountLimitsChecker.checkTradeIntraAccount({
amount,
volumeRemaining,
})
}

export const totalLimit = async ({
Expand Down
9 changes: 4 additions & 5 deletions src/app/payments/get-protocol-fee.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { WalletCurrency } from "@domain/shared"
import { decodeInvoice, defaultTimeToExpiryInSeconds } from "@domain/bitcoin/lightning"
import { checkedToWalletId } from "@domain/wallets"
import {
Expand All @@ -13,14 +12,14 @@ import { DealerPriceService } from "@services/dealer-price"
import { addAttributesToCurrentSpan } from "@services/tracing"

import { validateIsBtcWallet, validateIsUsdWallet } from "@app/wallets"

import { PartialResult } from "../partial-result"

import {
checkIntraledgerLimits,
checkTradeIntraAccountLimits,
checkWithdrawalLimits,
} from "./limits-check"
} from "@app/accounts"

import { PartialResult } from "../partial-result"

import { constructPaymentFlowBuilder, getPriceRatioForLimits } from "./helpers"

const getLightningFeeEstimation = async ({
Expand Down
1 change: 0 additions & 1 deletion src/app/payments/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from "./add-earn"
export * from "./get-protocol-fee"
export * from "./limits-check"
export * from "./reimburse-fee"
export * from "./send-intraledger"
export * from "./send-lightning"
Expand Down
103 changes: 0 additions & 103 deletions src/app/payments/limits-check.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/payments/send-intraledger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getCallbackServiceConfig, getValuesToSkipProbe } from "@config"

import { checkIntraledgerLimits, checkTradeIntraAccountLimits } from "@app/accounts"
import {
btcFromUsdMidPriceFn,
getCurrentPriceAsDisplayPriceRatio,
Expand Down Expand Up @@ -40,7 +41,6 @@ import { CallbackService } from "@services/svix"

import { CallbackEventType } from "@domain/callback"

import { checkIntraledgerLimits, checkTradeIntraAccountLimits } from "./limits-check"
import { addContactsAfterSend, getPriceRatioForLimits } from "./helpers"

const dealer = DealerPriceService()
Expand Down
10 changes: 5 additions & 5 deletions src/app/payments/send-lightning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ import {
recordExceptionInCurrentSpan,
} from "@services/tracing"

import {
checkIntraledgerLimits,
checkTradeIntraAccountLimits,
checkWithdrawalLimits,
} from "@app/accounts"
import { getCurrentPriceAsDisplayPriceRatio } from "@app/prices"
import { removeDeviceTokens } from "@app/users/remove-device-tokens"
import { validateIsBtcWallet, validateIsUsdWallet } from "@app/wallets"
Expand All @@ -61,11 +66,6 @@ import { CallbackService } from "@services/svix"
import { getCallbackServiceConfig } from "@config"
import { CallbackEventType } from "@domain/callback"

import {
checkIntraledgerLimits,
checkTradeIntraAccountLimits,
checkWithdrawalLimits,
} from "./limits-check"
import {
constructPaymentFlowBuilder,
getPriceRatioForLimits,
Expand Down
Loading

0 comments on commit fc57df9

Please sign in to comment.