From a921d6a2c382700bb764c54b7d5ba228eeabb371 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 12 Dec 2023 18:14:33 -0800 Subject: [PATCH 1/5] chore: Add basic pluralization utilities --- packages/io-utils/src/index.ts | 1 + packages/io-utils/src/language/index.ts | 1 + packages/io-utils/src/language/plurals.ts | 51 ++++++++++++++ .../__snapshots__/plurals.test.ts.snap | 67 +++++++++++++++++++ .../io-utils/test/language/plurals.test.ts | 23 +++++++ .../src/tasks/oapp/wire.ts | 9 ++- 6 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 packages/io-utils/src/language/index.ts create mode 100644 packages/io-utils/src/language/plurals.ts create mode 100644 packages/io-utils/test/language/__snapshots__/plurals.test.ts.snap create mode 100644 packages/io-utils/test/language/plurals.test.ts diff --git a/packages/io-utils/src/index.ts b/packages/io-utils/src/index.ts index 0f39ca5b6..fbc046e1c 100644 --- a/packages/io-utils/src/index.ts +++ b/packages/io-utils/src/index.ts @@ -1,2 +1,3 @@ export * from './filesystem' +export * from './language' export * from './stdio' diff --git a/packages/io-utils/src/language/index.ts b/packages/io-utils/src/language/index.ts new file mode 100644 index 000000000..1e984de54 --- /dev/null +++ b/packages/io-utils/src/language/index.ts @@ -0,0 +1 @@ +export * from './plurals' diff --git a/packages/io-utils/src/language/plurals.ts b/packages/io-utils/src/language/plurals.ts new file mode 100644 index 000000000..24a7a942b --- /dev/null +++ b/packages/io-utils/src/language/plurals.ts @@ -0,0 +1,51 @@ +const cardinalRules = new Intl.PluralRules('en-US') + +const ordinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) +const ordinals: Record = { + one: 'st', + two: 'nd', + few: 'rd', + other: 'th', + zero: 'th', + many: 'th', +} + +/** + * Turn a number into an ordinal. + * + * ```typescript + * pluralizeOrdinal(7) // 7th + * pluralizeNoun(1) // 1st + * pluralizeNoun(19, 'cacti') // 19th + * ``` + * + * @param {number} n + * @returns {string} + */ +export const pluralizeOrdinal = (n: number): string => { + const rule = ordinalRules.select(n) + const suffix = ordinals[rule] + + return `${n}${suffix}` +} + +/** + * Choose a correct form of a noun based on cardinality. + * + * ```typescript + * pluralizeNoun('cat', 7) // cats + * pluralizeNoun('cat', 1) // cat + * pluralizeNoun('cactus', 19, 'cacti') // cacti + * ``` + * + * @param {string} singular The signular form of the english noun + * @param {number} n + * @param {string} [plural] Plural version of the noun for irregular cases + * @returns {string} + */ +export const pluralizeNoun = (singular: string, n: number, plural: string = `${singular}s`): string => { + const rule = cardinalRules.select(n) + if (rule === 'one') return singular + + return plural +} diff --git a/packages/io-utils/test/language/__snapshots__/plurals.test.ts.snap b/packages/io-utils/test/language/__snapshots__/plurals.test.ts.snap new file mode 100644 index 000000000..38ce724cd --- /dev/null +++ b/packages/io-utils/test/language/__snapshots__/plurals.test.ts.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`language/plurals pluralizeNoun with custom plural should work for 0 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 1 1`] = `"cactus"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 2 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 3 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 4 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 5 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 11 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 12 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 21 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 100 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun with custom plural should work for 1234 1`] = `"cacti"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 0 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 1 1`] = `"cat"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 2 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 3 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 4 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 5 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 11 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 12 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 21 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 100 1`] = `"cats"`; + +exports[`language/plurals pluralizeNoun without custom plural should work for 1234 1`] = `"cats"`; + +exports[`language/plurals pluralizeOrdinal should work for 0 1`] = `"0th"`; + +exports[`language/plurals pluralizeOrdinal should work for 1 1`] = `"1st"`; + +exports[`language/plurals pluralizeOrdinal should work for 2 1`] = `"2nd"`; + +exports[`language/plurals pluralizeOrdinal should work for 3 1`] = `"3rd"`; + +exports[`language/plurals pluralizeOrdinal should work for 4 1`] = `"4th"`; + +exports[`language/plurals pluralizeOrdinal should work for 5 1`] = `"5th"`; + +exports[`language/plurals pluralizeOrdinal should work for 11 1`] = `"11th"`; + +exports[`language/plurals pluralizeOrdinal should work for 12 1`] = `"12th"`; + +exports[`language/plurals pluralizeOrdinal should work for 21 1`] = `"21st"`; + +exports[`language/plurals pluralizeOrdinal should work for 100 1`] = `"100th"`; + +exports[`language/plurals pluralizeOrdinal should work for 1234 1`] = `"1234th"`; diff --git a/packages/io-utils/test/language/plurals.test.ts b/packages/io-utils/test/language/plurals.test.ts new file mode 100644 index 000000000..0e39ec15d --- /dev/null +++ b/packages/io-utils/test/language/plurals.test.ts @@ -0,0 +1,23 @@ +import { pluralizeNoun, pluralizeOrdinal } from '@/language' + +describe('language/plurals', () => { + describe('pluralizeOrdinal', () => { + it.each([0, 1, 2, 3, 4, 5, 11, 12, 21, 100, 1234])(`should work for %d`, (n) => + expect(pluralizeOrdinal(n)).toMatchSnapshot() + ) + }) + + describe('pluralizeNoun', () => { + describe('without custom plural', () => { + it.each([0, 1, 2, 3, 4, 5, 11, 12, 21, 100, 1234])(`should work for %d`, (n) => + expect(pluralizeNoun('cat', n)).toMatchSnapshot() + ) + }) + + describe('with custom plural', () => { + it.each([0, 1, 2, 3, 4, 5, 11, 12, 21, 100, 1234])(`should work for %d`, (n) => + expect(pluralizeNoun('cactus', n, 'cacti')).toMatchSnapshot() + ) + }) + }) +}) diff --git a/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts b/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts index 5b6dfc38a..8f0588fed 100644 --- a/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts +++ b/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts @@ -8,6 +8,7 @@ import { setDefaultLogLevel, promptToContinue, printJson, + pluralizeNoun, } from '@layerzerolabs/io-utils' import { OAppOmniGraphHardhat, OAppOmniGraphHardhatSchema } from '@/oapp' import { OAppOmniGraph, configureOApp } from '@layerzerolabs/ua-utils' @@ -126,9 +127,11 @@ const action: ActionType = async ({ oappConfig: oappConfigPath, logLev // Tell the user about the transactions logger.info( - transactions.length === 1 - ? `There is 1 transaction required to configure the OApp` - : `There are ${transactions.length} transactions required to configure the OApp` + pluralizeNoun( + `There is 1 transaction required to configure the OApp`, + transactions.length, + `There are ${transactions.length} transactions required to configure the OApp` + ) ) // Ask them whether they want to see them From 03aea580a69030a6c46cfa28ea50564f46ae03de Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 12 Dec 2023 18:17:03 -0800 Subject: [PATCH 2/5] chore: Create createSignAndSend utility for signing transactions --- packages/utils/src/transactions/index.ts | 1 + packages/utils/src/transactions/signer.ts | 61 +++++++ packages/utils/src/transactions/types.ts | 10 ++ .../utils/test/transactions/signer.test.ts | 169 ++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 packages/utils/src/transactions/signer.ts create mode 100644 packages/utils/test/transactions/signer.test.ts diff --git a/packages/utils/src/transactions/index.ts b/packages/utils/src/transactions/index.ts index 0228ff9de..49e8460a5 100644 --- a/packages/utils/src/transactions/index.ts +++ b/packages/utils/src/transactions/index.ts @@ -1,3 +1,4 @@ export * from './format' +export * from './signer' export * from './types' export * from './utils' diff --git a/packages/utils/src/transactions/signer.ts b/packages/utils/src/transactions/signer.ts new file mode 100644 index 000000000..01136d123 --- /dev/null +++ b/packages/utils/src/transactions/signer.ts @@ -0,0 +1,61 @@ +import { createModuleLogger, pluralizeNoun, pluralizeOrdinal } from '@layerzerolabs/io-utils' +import type { OmniSignerFactory, OmniTransaction, OmniTransactionWithReceipt } from './types' +import { formatOmniPoint } from '@/omnigraph/format' +import type { OmniError } from '@/omnigraph/types' + +/** + * Creates a sign & send utility for a list of transaction + * with a help of `OmniSignerFactory` + * + * @param {OmniSignerFactory} createSigner + */ +export const createSignAndSend = + (createSigner: OmniSignerFactory) => + async ( + transactions: OmniTransaction[] + ): Promise<[successful: OmniTransactionWithReceipt[], errors: OmniError[]]> => { + const logger = createModuleLogger('sign & send') + + // Put it here so that we don't need to type like seven toilet rolls of variable names + const n = transactions.length + + // Just exit when there is nothing to sign + if (n === 0) return logger.debug(`No transactions to sign, exiting`), [[], []] + + // Tell the user how many we are signing + logger.debug(`Signing ${n} ${pluralizeNoun('transaction', n)}`) + + // We'll gather the successful transactions here + const successful: OmniTransactionWithReceipt[] = [] + + for (const [index, transaction] of transactions.entries()) { + // We want to refer to this transaction by index so we create an ordinal for it (1st, 2nd etc) + const ordinal = pluralizeOrdinal(index + 1) + + try { + logger.debug(`Signing ${ordinal} transaction to ${formatOmniPoint(transaction.point)}`) + + logger.debug(`Creating signer for ${ordinal} transaction`) + const signer = await createSigner(transaction.point.eid) + + logger.debug(`Signing ${ordinal} transaction`) + const response = await signer.signAndSend(transaction) + + logger.debug(`Signed ${ordinal} transaction, got hash ${response.transactionHash}`) + + const receipt = await response.wait() + logger.debug(`Finished ${ordinal} transaction`) + + successful.push({ transaction, receipt }) + } catch (error) { + logger.debug(`Failed to process ${ordinal} transaction: ${error}`) + + return [successful, [{ point: transaction.point, error }]] + } + } + + // Tell the inquisitive user what a good job we did + logger.debug(`Successfully signed ${n} ${pluralizeNoun('transaction', n)}`) + + return [successful, []] + } diff --git a/packages/utils/src/transactions/types.ts b/packages/utils/src/transactions/types.ts index 6b1ed7a11..48ab129fd 100644 --- a/packages/utils/src/transactions/types.ts +++ b/packages/utils/src/transactions/types.ts @@ -9,6 +9,16 @@ export interface OmniTransaction { value?: string | bigint | number } +export interface OmniTransactionWithResponse { + transaction: OmniTransaction + response: OmniTransactionResponse +} + +export interface OmniTransactionWithReceipt { + transaction: OmniTransaction + receipt: TReceipt +} + export interface OmniTransactionResponse { transactionHash: string wait: (confirmations?: number) => Promise diff --git a/packages/utils/test/transactions/signer.test.ts b/packages/utils/test/transactions/signer.test.ts new file mode 100644 index 000000000..b9902bacc --- /dev/null +++ b/packages/utils/test/transactions/signer.test.ts @@ -0,0 +1,169 @@ +import fc from 'fast-check' +import { pointArbitrary } from '@layerzerolabs/test-utils' +import { OmniSignerFactory, OmniTransaction, OmniTransactionResponse, createSignAndSend } from '@/transactions' + +describe('transactions/signer', () => { + const transactionArbitrary: fc.Arbitrary = fc.record({ + point: pointArbitrary, + data: fc.hexaString(), + }) + + describe('createSignAndSend', () => { + it('should return no successes and no errors when called with an empty array', async () => { + const signAndSend = jest.fn().mockRejectedValue('Oh no') + const sign = jest.fn().mockRejectedValue('Oh god no') + const signerFactory: OmniSignerFactory = () => ({ signAndSend, sign }) + const signAndSendTransactions = createSignAndSend(signerFactory) + + expect(await signAndSendTransactions([])).toEqual([[], []]) + + expect(signAndSend).not.toHaveBeenCalled() + expect(sign).not.toHaveBeenCalled() + }) + + it('should return all successful transactions if they all go through', async () => { + await fc.assert( + fc.asyncProperty(fc.array(transactionArbitrary), async (transactions) => { + // We'll prepare some mock objects for this test + // to mock the transaction responses and receipts + const receipt = { transactionHash: '0x0' } + + // Our successful wait will produce a receipt + const successfulWait = jest.fn().mockResolvedValue(receipt) + const successfullResponse: OmniTransactionResponse = { + transactionHash: '0x0', + wait: successfulWait, + } + + // Our signAndSend will then use the map to resolve/reject transactions + const signAndSend = jest.fn().mockResolvedValue(successfullResponse) + const sign = jest.fn().mockRejectedValue('Oh god no') + const signerFactory: OmniSignerFactory = jest.fn().mockResolvedValue({ signAndSend, sign }) + const signAndSendTransactions = createSignAndSend(signerFactory) + + // Now we send all the transactions to the flow and observe the output + const [successful, errors] = await signAndSendTransactions(transactions) + + expect(successful).toEqual(transactions.map((transaction) => ({ transaction, receipt }))) + expect(errors).toEqual([]) + + // We also check that the signer factory has been called with the eids + for (const transaction of transactions) { + expect(signerFactory).toHaveBeenCalledWith(transaction.point.eid) + } + }) + ) + }) + + it('should bail on the first wait error', async () => { + await fc.assert( + fc.asyncProperty( + fc.array(transactionArbitrary), + transactionArbitrary, + fc.array(transactionArbitrary), + async (firstBatch, failedTransaction, secondBatch) => { + // We'll prepare some mock objects for this test + // to mock the transaction responses and receipts + const error = new Error('Failed transaction') + const receipt = { transactionHash: '0x0' } + + // Our successful wait will produce a receipt + const successfulWait = jest.fn().mockResolvedValue(receipt) + const successfullResponse: OmniTransactionResponse = { + transactionHash: '0x0', + wait: successfulWait, + } + + // Our unsuccessful wait will throw an error + const unsuccessfulWait = jest.fn().mockRejectedValue(error) + const unsuccessfulResponse: OmniTransactionResponse = { + transactionHash: '0x0', + wait: unsuccessfulWait, + } + + // In order to resolve the good ones and reject the bad ones + // we'll prepare a map between a transaction and its response + // + // This map relies on the fact that we are passing the transaction object without modifying it + // so the objects are referentially equal + const implementations: Map> = new Map([ + ...firstBatch.map((t) => [t, Promise.resolve(successfullResponse)] as const), + ...secondBatch.map((t) => [t, Promise.resolve(successfullResponse)] as const), + [failedTransaction, Promise.resolve(unsuccessfulResponse)], + ]) + + // Our signAndSend will then use the map to resolve/reject transactions + const signAndSend = jest.fn().mockImplementation((t) => implementations.get(t)) + const sign = jest.fn().mockRejectedValue('Oh god no') + const signerFactory: OmniSignerFactory = jest.fn().mockResolvedValue({ signAndSend, sign }) + const signAndSendTransactions = createSignAndSend(signerFactory) + + // Now we send all the transactions to the flow and observe the output + const transactions = [...firstBatch, failedTransaction, ...secondBatch] + const [successful, errors] = await signAndSendTransactions(transactions) + + expect(successful).toEqual(firstBatch.map((transaction) => ({ transaction, receipt }))) + expect(errors).toEqual([{ point: failedTransaction.point, error }]) + + // We also check that the signer factory has been called with the eids + expect(signerFactory).toHaveBeenCalledWith(failedTransaction.point.eid) + for (const transaction of firstBatch) { + expect(signerFactory).toHaveBeenCalledWith(transaction.point.eid) + } + } + ) + ) + }) + + it('should bail on the first submission error', async () => { + await fc.assert( + fc.asyncProperty( + fc.array(transactionArbitrary), + transactionArbitrary, + fc.array(transactionArbitrary), + async (firstBatch, failedTransaction, secondBatch) => { + // We'll prepare some mock objects for this test + // to mock the transaction responses and receipts + const error = new Error('Failed transaction') + const receipt = { transactionHash: '0x0' } + const successfulWait = jest.fn().mockResolvedValue(receipt) + const successfullResponse: OmniTransactionResponse = { + transactionHash: '0x0', + wait: successfulWait, + } + + // In order to resolve the good ones and reject the bad ones + // we'll prepare a map between a transaction and its response + // + // This map relies on the fact that we are passing the transaction object without modifying it + // so the objects are referentially equal + const implementations: Map> = new Map([ + ...firstBatch.map((t) => [t, Promise.resolve(successfullResponse)] as const), + ...secondBatch.map((t) => [t, Promise.resolve(successfullResponse)] as const), + [failedTransaction, Promise.reject(error)], + ]) + + // Our signAndSend will then use the map to resolve/reject transactions + const signAndSend = jest.fn().mockImplementation((t) => implementations.get(t)) + const sign = jest.fn().mockRejectedValue('Oh god no') + const signerFactory: OmniSignerFactory = jest.fn().mockResolvedValue({ signAndSend, sign }) + const signAndSendTransactions = createSignAndSend(signerFactory) + + // Now we send all the transactions to the flow and observe the output + const transactions = [...firstBatch, failedTransaction, ...secondBatch] + const [successful, errors] = await signAndSendTransactions(transactions) + + expect(successful).toEqual(firstBatch.map((transaction) => ({ transaction, receipt }))) + expect(errors).toEqual([{ point: failedTransaction.point, error }]) + + // We also check that the signer factory has been called with the eids + expect(signerFactory).toHaveBeenCalledWith(failedTransaction.point.eid) + for (const transaction of firstBatch) { + expect(signerFactory).toHaveBeenCalledWith(transaction.point.eid) + } + } + ) + ) + }) + }) +}) From f4bec8935beac164c91c4d984525828ca284dc68 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 12 Dec 2023 18:21:12 -0800 Subject: [PATCH 3/5] chore: Basic usage of signAndSend in endpoint deployment utilities --- .../test/__utils__/endpoint.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts index 3c04bb377..e8b712f1a 100644 --- a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts @@ -17,7 +17,7 @@ import { Uln302UlnConfig, } from '@layerzerolabs/protocol-utils' import { createEndpointFactory, createUln302Factory } from '@layerzerolabs/protocol-utils-evm' -import { formatOmniPoint } from '@layerzerolabs/utils' +import { createSignAndSend } from '@layerzerolabs/utils' export const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' } export const ethReceiveUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'ReceiveUln302' } @@ -92,7 +92,7 @@ export const setupDefaultEndpoint = async (): Promise => { // This is the tooling we are going to need const logger = createLogger() const contractFactory = createConnectedContractFactory() - const signerFactory = createSignerFactory() + const signAndSend = createSignAndSend(createSignerFactory()) const ulnSdkFactory = createUln302Factory(contractFactory) const endpointSdkFactory = createEndpointFactory(contractFactory, ulnSdkFactory) @@ -195,20 +195,9 @@ export const setupDefaultEndpoint = async (): Promise => { const transactions = [...sendUlnTransactions, ...receiveUlnTransactions, ...endpointTransactions] - logger.debug(`Executing ${transactions.length} transactions`) - - for (const transaction of transactions) { - const signer = await signerFactory(transaction.point.eid) - const description = transaction.description ?? '[no description]' - - logger.debug(`${formatOmniPoint(transaction.point)}: ${description}`) - - const response = await signer.signAndSend(transaction) - logger.debug(`${formatOmniPoint(transaction.point)}: ${description}: ${response.transactionHash}`) - - const receipt = await response.wait() - logger.debug(`${formatOmniPoint(transaction.point)}: ${description}: ${receipt.transactionHash}`) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [successful, errors] = await signAndSend(transactions) + if (errors.length !== 0) { + throw new Error(`Failed to deploy endpoint: ${errors}`) } - - logger.debug(`Done configuring endpoint`) } From 958fdce592ff8860b5ce6aee4fce63aee67f3370 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 12 Dec 2023 18:26:13 -0800 Subject: [PATCH 4/5] fix: Lint --- packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts index e8b712f1a..d0e29a845 100644 --- a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts +++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts @@ -5,7 +5,6 @@ import { OmniGraphBuilderHardhat, type OmniGraphHardhat, } from '@layerzerolabs/utils-evm-hardhat' -import { createLogger } from '@layerzerolabs/io-utils' import { EndpointId } from '@layerzerolabs/lz-definitions' import { omniContractToPoint } from '@layerzerolabs/utils-evm' import { @@ -90,7 +89,6 @@ export const deployEndpoint = async () => { */ export const setupDefaultEndpoint = async (): Promise => { // This is the tooling we are going to need - const logger = createLogger() const contractFactory = createConnectedContractFactory() const signAndSend = createSignAndSend(createSignerFactory()) const ulnSdkFactory = createUln302Factory(contractFactory) From f97f802a7e884b5b2721bfa687af703d012f906b Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Tue, 12 Dec 2023 18:33:16 -0800 Subject: [PATCH 5/5] chore: Switch order of arguments for pluralizeNoun --- packages/io-utils/src/language/plurals.ts | 14 +++++++------- packages/io-utils/test/language/plurals.test.ts | 4 ++-- .../ua-utils-evm-hardhat/src/tasks/oapp/wire.ts | 2 +- packages/utils/src/transactions/signer.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/io-utils/src/language/plurals.ts b/packages/io-utils/src/language/plurals.ts index 24a7a942b..6d1d6488b 100644 --- a/packages/io-utils/src/language/plurals.ts +++ b/packages/io-utils/src/language/plurals.ts @@ -15,8 +15,8 @@ const ordinals: Record = { * * ```typescript * pluralizeOrdinal(7) // 7th - * pluralizeNoun(1) // 1st - * pluralizeNoun(19, 'cacti') // 19th + * pluralizeOrdinal(1) // 1st + * pluralizeOrdinal(19) // 19th * ``` * * @param {number} n @@ -33,17 +33,17 @@ export const pluralizeOrdinal = (n: number): string => { * Choose a correct form of a noun based on cardinality. * * ```typescript - * pluralizeNoun('cat', 7) // cats - * pluralizeNoun('cat', 1) // cat - * pluralizeNoun('cactus', 19, 'cacti') // cacti + * pluralizeNoun(7, 'cat') // cats + * pluralizeNoun(1, 'cat') // cat + * pluralizeNoun(19, 'cactus', 'cacti') // cacti * ``` * - * @param {string} singular The signular form of the english noun * @param {number} n + * @param {string} singular The signular form of the english noun * @param {string} [plural] Plural version of the noun for irregular cases * @returns {string} */ -export const pluralizeNoun = (singular: string, n: number, plural: string = `${singular}s`): string => { +export const pluralizeNoun = (n: number, singular: string, plural: string = `${singular}s`): string => { const rule = cardinalRules.select(n) if (rule === 'one') return singular diff --git a/packages/io-utils/test/language/plurals.test.ts b/packages/io-utils/test/language/plurals.test.ts index 0e39ec15d..a06b48bab 100644 --- a/packages/io-utils/test/language/plurals.test.ts +++ b/packages/io-utils/test/language/plurals.test.ts @@ -10,13 +10,13 @@ describe('language/plurals', () => { describe('pluralizeNoun', () => { describe('without custom plural', () => { it.each([0, 1, 2, 3, 4, 5, 11, 12, 21, 100, 1234])(`should work for %d`, (n) => - expect(pluralizeNoun('cat', n)).toMatchSnapshot() + expect(pluralizeNoun(n, 'cat')).toMatchSnapshot() ) }) describe('with custom plural', () => { it.each([0, 1, 2, 3, 4, 5, 11, 12, 21, 100, 1234])(`should work for %d`, (n) => - expect(pluralizeNoun('cactus', n, 'cacti')).toMatchSnapshot() + expect(pluralizeNoun(n, 'cactus', 'cacti')).toMatchSnapshot() ) }) }) diff --git a/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts b/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts index 8f0588fed..3ccfff06e 100644 --- a/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts +++ b/packages/ua-utils-evm-hardhat/src/tasks/oapp/wire.ts @@ -128,8 +128,8 @@ const action: ActionType = async ({ oappConfig: oappConfigPath, logLev // Tell the user about the transactions logger.info( pluralizeNoun( - `There is 1 transaction required to configure the OApp`, transactions.length, + `There is 1 transaction required to configure the OApp`, `There are ${transactions.length} transactions required to configure the OApp` ) ) diff --git a/packages/utils/src/transactions/signer.ts b/packages/utils/src/transactions/signer.ts index 01136d123..a48926485 100644 --- a/packages/utils/src/transactions/signer.ts +++ b/packages/utils/src/transactions/signer.ts @@ -23,7 +23,7 @@ export const createSignAndSend = if (n === 0) return logger.debug(`No transactions to sign, exiting`), [[], []] // Tell the user how many we are signing - logger.debug(`Signing ${n} ${pluralizeNoun('transaction', n)}`) + logger.debug(`Signing ${n} ${pluralizeNoun(n, 'transaction')}`) // We'll gather the successful transactions here const successful: OmniTransactionWithReceipt[] = [] @@ -55,7 +55,7 @@ export const createSignAndSend = } // Tell the inquisitive user what a good job we did - logger.debug(`Successfully signed ${n} ${pluralizeNoun('transaction', n)}`) + logger.debug(`Successfully signed ${n} ${pluralizeNoun(n, 'transaction')}`) return [successful, []] }