From 83923590f8b8780a7c6e0084e0dc160aa0a66162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Ace=C3=B1olaza?= Date: Wed, 14 Jun 2023 12:58:43 -0300 Subject: [PATCH] Minor fixes and cleanup --- README.md | 8 ++++---- config/wallets.example.json | 2 +- src/config.test.ts | 4 ++-- src/types.ts | 17 ++++++++++++----- src/wallet-watcher.test.ts | 28 ++++++---------------------- src/wallet-watcher.ts | 29 ++++++++++++++--------------- test/e2e/wallet-watcher.feature.ts | 2 +- test/fixtures/config.ts | 2 +- 8 files changed, 41 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 000146e..37c8cd0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Wallet Watcher The wallet watcher loads addresses defined in wallets.json and checks that their balances are not below a defined -threshold. If they are not then an alert will be sent to OpsGenie. +threshold. If they are then an alert will be sent to OpsGenie. ## Deployment @@ -54,8 +54,8 @@ Be sure to watch the logs to make sure the applications are behaving as you expe - `[n].apiName` (optional): The name of the API provider. - `[n].providerXpub`: The extended public key of the sponsor address. - `[n].sponsor`: The sponsor address to derive the destination wallet. -- `[n].lowBalance.value`: The wallet balance value below which an alert is triggered. +- `[n].lowBalance.value`: The value used to send an alert if wallet balance is below it. - `[n].lowBalance.unit`: The token units used to parse the `lowBalance.value` for balance check (i.e. ether, wei, etc). -- `[n].lowBalance.criticalPercentage`: The percentage below the `lowBalance.value` used to trigger a critical - alert. +- `[n].lowBalance.criticalValue`: The value used to send a _critical_ alert if wallet balance is below it. This + value must be below the `lowBalance.value` value. diff --git a/config/wallets.example.json b/config/wallets.example.json index abd6761..4d78abf 100644 --- a/config/wallets.example.json +++ b/config/wallets.example.json @@ -10,7 +10,7 @@ "apiName": "FunderDepository contract", "walletType": "API3", "address": "0xE889956fA885F42B0C294507d0e67a3168BE188b", - "lowThreshold": { "value": 0.3, "unit": "ether", "criticalPercentage": 33 } + "lowThreshold": { "value": 0.3, "unit": "ether", "criticalValue": 0.1 } } ], "5": [ diff --git a/src/config.test.ts b/src/config.test.ts index b57baf6..e1574c7 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -174,7 +174,7 @@ describe('wallets.json', () => { apiName: 'api3', walletType: 'API3', address: '0x9fEe9F24ab79adacbB51af82fb82CFb9D818c6d9', - lowThreshold: { value: '0.2', unit: 'satoshi', criticalPercentage: '50' }, + lowThreshold: { value: '0.2', unit: 'satoshi', criticalValue: '0.1' }, }, ], }; @@ -286,7 +286,7 @@ describe('wallets.json', () => { code: 'invalid_type', expected: 'number', received: 'string', - path: ['1', 0, 'lowThreshold', 'criticalPercentage'], + path: ['1', 0, 'lowThreshold', 'criticalValue'], message: 'Expected number, received string', }, ])}` diff --git a/src/types.ts b/src/types.ts index f75a188..e0f7c09 100644 --- a/src/types.ts +++ b/src/types.ts @@ -51,11 +51,18 @@ export const namedUnits = z.union([ const baseWalletSchema = z.object({ apiName: z.string().optional(), - lowThreshold: z.object({ - value: z.number().positive(), - unit: namedUnits, - criticalPercentage: z.number().positive().lt(100).optional(), - }), + lowThreshold: z + .object({ + value: z.number().positive(), + unit: namedUnits, + criticalValue: z.number().positive().optional(), + }) + .refine( + ({ value, criticalValue }) => { + return criticalValue === undefined || criticalValue < value; + }, + { message: 'Critical value must be lower than value' } + ), }); const providerWalletSchema = baseWalletSchema.extend({ diff --git a/src/wallet-watcher.test.ts b/src/wallet-watcher.test.ts index 17f60c3..4454019 100644 --- a/src/wallet-watcher.test.ts +++ b/src/wallet-watcher.test.ts @@ -144,9 +144,8 @@ describe('walletWatcher', () => { const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`)); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, + 2, `critical-low-balance-${opsGenieAliasSuffix}` ); expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith( @@ -169,12 +168,8 @@ describe('walletWatcher', () => { const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`)); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, - `critical-low-balance-${opsGenieAliasSuffix}` - ); + expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenCalledWith(`get-balance-error-${opsGenieAliasSuffix}`); + expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith( { priority: 'P1', @@ -197,10 +192,7 @@ describe('walletWatcher', () => { expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, - `critical-low-balance-${opsGenieAliasSuffix}` - ); + expect(limitedSendToOpsGenieLowLevel).not.toHaveBeenCalled(); }); @@ -225,9 +217,8 @@ describe('walletWatcher', () => { const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`)); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, + 2, `critical-low-balance-${opsGenieAliasSuffix}` ); expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledTimes(1); @@ -255,9 +246,8 @@ describe('walletWatcher', () => { expect(getBalanceMock).toHaveBeenCalledTimes(2); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, + 2, `critical-low-balance-${opsGenieAliasSuffix}` ); expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith( @@ -282,12 +272,6 @@ describe('walletWatcher', () => { expect(getBalanceMock).toHaveBeenCalledTimes(3); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`); - expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith( - 3, - `critical-low-balance-${opsGenieAliasSuffix}` - ); expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith( { priority: 'P2', diff --git a/src/wallet-watcher.ts b/src/wallet-watcher.ts index 18683e1..a54ecaa 100644 --- a/src/wallet-watcher.ts +++ b/src/wallet-watcher.ts @@ -122,11 +122,6 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W ethers.utils.toUtf8Bytes(`${wallet.address}${wallet.chainId}`) ); - // Close previous cycle alerts - limitedCloseOpsGenieAlertWithAlias(`get-balance-error-${opsGenieAliasSuffix}`); - limitedCloseOpsGenieAlertWithAlias(`low-balance-${opsGenieAliasSuffix}`); - limitedCloseOpsGenieAlertWithAlias(`critical-low-balance-${opsGenieAliasSuffix}`); - const chainState = chainStates[wallet.chainId]; const getBalanceResult = await go(() => chainState.provider.getBalance(wallet.address), { totalTimeoutMs: 15_000, @@ -147,20 +142,20 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W config.opsGenieConfig ); throw new Error(message); + } else { + limitedCloseOpsGenieAlertWithAlias(`get-balance-error-${opsGenieAliasSuffix}`); } const balance = getBalanceResult.data; // Send alert if balance is equal or below threshold - const walletBalanceThreshold = ethers.utils.parseUnits( - wallet.lowThreshold.value.toString(), - wallet.lowThreshold.unit - ); - if (balance.lte(walletBalanceThreshold)) { - if (wallet.lowThreshold.criticalPercentage) { + const balanceThreshold = ethers.utils.parseUnits(wallet.lowThreshold.value.toString(), wallet.lowThreshold.unit); + if (balance.lte(balanceThreshold)) { + if (wallet.lowThreshold.criticalValue) { // Send emergency alert if balance is even below a critical percentage - const criticalBalanceThreshold = walletBalanceThreshold.sub( - walletBalanceThreshold.mul(wallet.lowThreshold.criticalPercentage).div(100) + const criticalBalanceThreshold = ethers.utils.parseUnits( + wallet.lowThreshold.criticalValue.toString(), + wallet.lowThreshold.unit ); if (balance.lte(criticalBalanceThreshold)) { const criticalMessage = `Critical low balance alert for address ${wallet.address} on chain ${chainState.chainName}`; @@ -169,11 +164,13 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W priority: 'P1', alias: `critical-low-balance-${opsGenieAliasSuffix}`, message: criticalMessage, - description: `Current balance: ${balance.toString()}\nThreshold: ${walletBalanceThreshold.toString()}\nCritical threshold: ${criticalBalanceThreshold.toString()}`, + description: `Current balance: ${balance.toString()}\nThreshold: ${balanceThreshold.toString()}\nCritical threshold: ${criticalBalanceThreshold.toString()}`, }, config.opsGenieConfig ); return; + } else { + limitedCloseOpsGenieAlertWithAlias(`critical-low-balance-${opsGenieAliasSuffix}`); } } @@ -183,10 +180,12 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W priority: 'P2', alias: `low-balance-${opsGenieAliasSuffix}`, message, - description: `Current balance: ${balance.toString()}\nThreshold: ${walletBalanceThreshold.toString()}`, + description: `Current balance: ${balance.toString()}\nThreshold: ${balanceThreshold.toString()}`, }, config.opsGenieConfig ); + } else { + limitedCloseOpsGenieAlertWithAlias(`low-balance-${opsGenieAliasSuffix}`); } }) ); diff --git a/test/e2e/wallet-watcher.feature.ts b/test/e2e/wallet-watcher.feature.ts index 5192668..b28d4d5 100644 --- a/test/e2e/wallet-watcher.feature.ts +++ b/test/e2e/wallet-watcher.feature.ts @@ -40,7 +40,7 @@ describe('walletWatcher', () => { await walletWatcher.runWalletWatcher(config, wallets); - expect(limitedCloseOpsGenieAlertWithAliasMock).toHaveBeenCalledTimes(3); + expect(limitedCloseOpsGenieAlertWithAliasMock).toHaveBeenCalledTimes(2); expect(limitedSendToOpsGenieLowLevelMock).toHaveBeenCalledWith( { alias: 'low-balance-0xa029e0cf3ff3ea6562c3e67315c9bbec596fb7ac0ac862365eb7ec783b426c0d', diff --git a/test/fixtures/config.ts b/test/fixtures/config.ts index 3628b9b..d6654b8 100644 --- a/test/fixtures/config.ts +++ b/test/fixtures/config.ts @@ -29,7 +29,7 @@ export const buildWallets = (): Wallets => ({ address: '0xC26f10e1b37A1E7A7De266FeF0c19533489C3e75', providerXpub: 'xpub661MyMwAqRbcFeZ1CUvUpMs5bBSVLPHiuTqj7dZPertAGtd3xyTW1vrPspz7B34A7sdPahw7psrJjCXmn8KpF92jQssoqmsTk8fZ9PZN8xK', - lowThreshold: { value: 0.2, unit: 'ether', criticalPercentage: 50 }, + lowThreshold: { value: 0.2, unit: 'ether', criticalValue: 0.1 }, }, ], });