From 0110fd8d74f799d5b34437e7c221e3dbda87c8b8 Mon Sep 17 00:00:00 2001 From: Miguel Tavares Date: Wed, 7 Aug 2024 03:17:09 -0300 Subject: [PATCH 1/2] chore: handle exceeding maximum inputs when funding a transaction (#2822) * add check * refactoring * move if * allocate method in other place * add test * remove .only * remove console.log * add error docs * Update .changeset/honest-fishes-mix.md Co-authored-by: Peter Smith --------- Co-authored-by: Peter Smith --- .changeset/honest-fishes-mix.md | 6 ++ apps/docs/src/guide/errors/index.md | 4 ++ .../account/src/providers/provider.test.ts | 58 ++++++++++++++++++- packages/account/src/providers/provider.ts | 13 +++++ packages/errors/src/error-codes.ts | 1 + 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 .changeset/honest-fishes-mix.md diff --git a/.changeset/honest-fishes-mix.md b/.changeset/honest-fishes-mix.md new file mode 100644 index 00000000000..f381e3e9939 --- /dev/null +++ b/.changeset/honest-fishes-mix.md @@ -0,0 +1,6 @@ +--- +"@fuel-ts/account": patch +"@fuel-ts/errors": patch +--- + +chore: handle exceeding maximum inputs when funding a transaction \ No newline at end of file diff --git a/apps/docs/src/guide/errors/index.md b/apps/docs/src/guide/errors/index.md index af55d41ce4f..fe61222a90a 100644 --- a/apps/docs/src/guide/errors/index.md +++ b/apps/docs/src/guide/errors/index.md @@ -298,3 +298,7 @@ The purpose of the lock function is to provide a way to ensure that the implemen In cases where the error hasn't been mapped yet, this code will be used. If you believe you found a bug, please report the [issue](https://github.com/FuelLabs/fuels-ts/issues/new/choose) to the team. + +### `MAX_INPUTS_EXCEEDED` + +When the number of transaction inputs exceeds the maximum limit allowed by the blockchain. \ No newline at end of file diff --git a/packages/account/src/providers/provider.test.ts b/packages/account/src/providers/provider.test.ts index 167a58308e9..83830bdc0a6 100644 --- a/packages/account/src/providers/provider.test.ts +++ b/packages/account/src/providers/provider.test.ts @@ -8,7 +8,7 @@ import { BN, bn } from '@fuel-ts/math'; import type { Receipt } from '@fuel-ts/transactions'; import { InputType, ReceiptType, TransactionType } from '@fuel-ts/transactions'; import { DateTime, arrayify, sleep } from '@fuel-ts/utils'; -import { ASSET_A } from '@fuel-ts/utils/test-utils'; +import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils'; import { versions } from '@fuel-ts/versions'; import * as fuelTsVersionsMod from '@fuel-ts/versions'; @@ -26,6 +26,7 @@ import { import { Wallet } from '../wallet'; import type { Coin } from './coin'; +import { coinQuantityfy } from './coin-quantity'; import type { ChainInfo, CursorPaginationArgs, NodeInfo } from './provider'; import Provider, { BLOCKS_PAGE_SIZE_LIMIT, @@ -33,7 +34,7 @@ import Provider, { RESOURCES_PAGE_SIZE_LIMIT, } from './provider'; import type { CoinTransactionRequestInput } from './transaction-request'; -import { ScriptTransactionRequest, CreateTransactionRequest } from './transaction-request'; +import { CreateTransactionRequest, ScriptTransactionRequest } from './transaction-request'; import { TransactionResponse } from './transaction-response'; import type { SubmittedStatus } from './transaction-summary/types'; import * as gasMod from './utils/gas'; @@ -541,6 +542,59 @@ describe('Provider', () => { }); }); + it('should throws if max of inputs was exceeded', async () => { + const maxInputs = 2; + using launched = await setupTestProviderAndWallets({ + nodeOptions: { + snapshotConfig: { + chainConfig: { + consensus_parameters: { + V1: { + tx_params: { + V1: { + max_inputs: maxInputs, + }, + }, + }, + }, + }, + }, + }, + walletsConfig: { + amountPerCoin: 500_000, + }, + }); + + const { + wallets: [sender, receiver], + provider, + } = launched; + + const request = new ScriptTransactionRequest(); + + const quantities = [coinQuantityfy([1000, ASSET_A]), coinQuantityfy([500, ASSET_B])]; + const resources = await sender.getResourcesToSpend(quantities); + + request.addCoinOutput(receiver.address, 500, provider.getBaseAssetId()); + + const txCost = await sender.getTransactionCost(request); + + request.gasLimit = txCost.gasUsed; + request.maxFee = txCost.maxFee; + + request.addResources(resources); + + await sender.fund(request, txCost); + + await expectToThrowFuelError( + () => sender.sendTransaction(request), + new FuelError( + ErrorCode.MAX_INPUTS_EXCEEDED, + 'The transaction exceeds the maximum allowed number of inputs for funding.' + ) + ); + }); + it('can getBlocks', async () => { using launched = await setupTestProviderAndWallets(); const blocksLenght = 5; diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index 1d514c4ec6b..e6f155be09d 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -695,6 +695,15 @@ Supported fuel-core version: ${supportedVersion}.` }); } + private validateTransaction(tx: TransactionRequest, consensusParameters: ConsensusParameters) { + if (bn(tx.inputs.length).gt(consensusParameters.txParameters.maxInputs)) { + throw new FuelError( + ErrorCode.MAX_INPUTS_EXCEEDED, + 'The transaction exceeds the maximum allowed number of inputs for funding.' + ); + } + } + /** * Submits a transaction to the chain to be executed. * @@ -716,6 +725,10 @@ Supported fuel-core version: ${supportedVersion}.` } // #endregion Provider-sendTransaction + const { consensusParameters } = this.getChain(); + + this.validateTransaction(transactionRequest, consensusParameters); + const encodedTransaction = hexlify(transactionRequest.toTransactionBytes()); let abis: JsonAbisFromAllCalls | undefined; diff --git a/packages/errors/src/error-codes.ts b/packages/errors/src/error-codes.ts index 5b790cb9a91..a8a7d51305c 100644 --- a/packages/errors/src/error-codes.ts +++ b/packages/errors/src/error-codes.ts @@ -77,6 +77,7 @@ export enum ErrorCode { DUPLICATED_POLICY = 'duplicated-policy', TRANSACTION_SQUEEZED_OUT = 'transaction-squeezed-out', CONTRACT_SIZE_EXCEEDS_LIMIT = 'contract-size-exceeds-limit', + MAX_INPUTS_EXCEEDED = 'max-inputs-exceeded', // receipt INVALID_RECEIPT_TYPE = 'invalid-receipt-type', From 4e82ad42b84e520c3133907aabeea2aad1c1a199 Mon Sep 17 00:00:00 2001 From: Dhaiwat Date: Wed, 7 Aug 2024 16:15:25 +0530 Subject: [PATCH 2/2] chore: add infra to support multiple `create fuels` templates (#2851) * chore: add infra to support multiple `create fuels` templates * add docs * disable pr release * move template name validation to the top * add default value * refactor * pass template name in tests via args * mock `doesTemplateExist` * remove param * add missing testing groups * refactor * switch to named params for `generateArgs.ts` * use `spyOn` to mock * use `mockReturnValueOnce` --- .changeset/wet-elephants-impress.md | 5 ++ .../src/guide/creating-a-fuel-dapp/options.md | 4 ++ packages/create-fuels/src/bin.ts | 1 - packages/create-fuels/src/cli.ts | 22 ++++++-- .../src/lib/doesTemplateExist.test.ts | 13 +++++ .../create-fuels/src/lib/doesTemplateExist.ts | 4 ++ .../create-fuels/src/lib/setupProgram.test.ts | 12 ++++- packages/create-fuels/src/lib/setupProgram.ts | 6 +++ packages/create-fuels/test/cli.test.ts | 53 +++++++++++++++---- .../create-fuels/test/integration.test.ts | 2 +- .../create-fuels/test/utils/generateArgs.ts | 28 +++++++--- 11 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 .changeset/wet-elephants-impress.md create mode 100644 packages/create-fuels/src/lib/doesTemplateExist.test.ts create mode 100644 packages/create-fuels/src/lib/doesTemplateExist.ts diff --git a/.changeset/wet-elephants-impress.md b/.changeset/wet-elephants-impress.md new file mode 100644 index 00000000000..b0dcd15cf70 --- /dev/null +++ b/.changeset/wet-elephants-impress.md @@ -0,0 +1,5 @@ +--- +"create-fuels": patch +--- + +chore: add infra to support multiple `create fuels` templates diff --git a/apps/docs/src/guide/creating-a-fuel-dapp/options.md b/apps/docs/src/guide/creating-a-fuel-dapp/options.md index 96252b5b26d..9082aa6b651 100644 --- a/apps/docs/src/guide/creating-a-fuel-dapp/options.md +++ b/apps/docs/src/guide/creating-a-fuel-dapp/options.md @@ -23,6 +23,10 @@ bunx --bun create-fuels@{{fuels}} --bun [project-name] [options] ::: +## `--template ` + +Specifies the template to use for your project. The available templates are: `nextjs`. + ## `--pnpm` Notifies the tool to use pnpm as the package manager to install the necessary dependencies. diff --git a/packages/create-fuels/src/bin.ts b/packages/create-fuels/src/bin.ts index 2b99fd3aeb6..495ef63f4a8 100644 --- a/packages/create-fuels/src/bin.ts +++ b/packages/create-fuels/src/bin.ts @@ -5,7 +5,6 @@ import { runScaffoldCli, setupProgram } from './cli'; runScaffoldCli({ program: setupProgram(), - templateName: 'nextjs', args: process.argv, }) .then(() => process.exit(0)) diff --git a/packages/create-fuels/src/cli.ts b/packages/create-fuels/src/cli.ts index dbb7e80e125..0c9ff32b57c 100644 --- a/packages/create-fuels/src/cli.ts +++ b/packages/create-fuels/src/cli.ts @@ -10,9 +10,11 @@ import ora from 'ora'; import { join } from 'path'; import { tryInstallFuelUp } from './lib'; +import { doesTemplateExist } from './lib/doesTemplateExist'; import { getPackageManager } from './lib/getPackageManager'; import { getPackageVersion } from './lib/getPackageVersion'; -import type { ProgramOptions } from './lib/setupProgram'; +import { defaultTemplate, templates } from './lib/setupProgram'; +import type { Template, ProgramOptions } from './lib/setupProgram'; import { promptForProjectPath } from './prompts'; import { error, log } from './utils/logger'; @@ -39,18 +41,28 @@ function writeEnvFile(envFilePath: string) { export const runScaffoldCli = async ({ program, - templateName = 'nextjs', args = process.argv, }: { program: Command; args: string[]; - templateName: string; }) => { program.parse(args); + const opts = program.opts(); + + const templateOfChoice = (opts.template ?? defaultTemplate) as Template; + + if (!doesTemplateExist(templateOfChoice)) { + error(`Template '${templateOfChoice}' does not exist.`); + log(); + log('Available templates:'); + for (const template of templates) { + log(` - ${template}`); + } + process.exit(1); + } let projectPath = program.args[0] ?? (await promptForProjectPath()); - const opts = program.opts(); const verboseEnabled = opts.verbose ?? false; const packageManager = getPackageManager(opts); @@ -93,7 +105,7 @@ export const runScaffoldCli = async ({ await mkdir(projectPath); - const templateDir = join(__dirname, '..', 'templates', templateName); + const templateDir = join(__dirname, '..', 'templates', templateOfChoice); await cp(templateDir, projectPath, { recursive: true, filter: (filename) => !filename.includes('CHANGELOG.md'), diff --git a/packages/create-fuels/src/lib/doesTemplateExist.test.ts b/packages/create-fuels/src/lib/doesTemplateExist.test.ts new file mode 100644 index 00000000000..e3435505e25 --- /dev/null +++ b/packages/create-fuels/src/lib/doesTemplateExist.test.ts @@ -0,0 +1,13 @@ +import { doesTemplateExist } from './doesTemplateExist'; + +/** + * @group node + */ +test('doesTemplateExist should return true if the template exists', () => { + expect(doesTemplateExist('nextjs')).toBeTruthy(); +}); + +test('doesTemplateExist should return false if the template does not exist', () => { + // @ts-expect-error intentionally passing in a non-existent template + expect(doesTemplateExist('non-existent-template')).toBeFalsy(); +}); diff --git a/packages/create-fuels/src/lib/doesTemplateExist.ts b/packages/create-fuels/src/lib/doesTemplateExist.ts new file mode 100644 index 00000000000..c3a5ce08c18 --- /dev/null +++ b/packages/create-fuels/src/lib/doesTemplateExist.ts @@ -0,0 +1,4 @@ +import type { Template } from './setupProgram'; +import { templates } from './setupProgram'; + +export const doesTemplateExist = (templateName: Template): boolean => templates.has(templateName); diff --git a/packages/create-fuels/src/lib/setupProgram.test.ts b/packages/create-fuels/src/lib/setupProgram.test.ts index 940625f7ebc..642afdce40c 100644 --- a/packages/create-fuels/src/lib/setupProgram.test.ts +++ b/packages/create-fuels/src/lib/setupProgram.test.ts @@ -6,12 +6,22 @@ import { setupProgram } from './setupProgram'; describe('setupProgram', () => { test('setupProgram takes in args properly', () => { const program = setupProgram(); - program.parse(['', '', 'test-project-name', '--pnpm', '--npm', '--bun']); + program.parse([ + '', + '', + 'test-project-name', + '--template', + 'nextjs', + '--pnpm', + '--npm', + '--bun', + ]); expect(program.args[0]).toBe('test-project-name'); expect(program.opts().pnpm).toBe(true); expect(program.opts().npm).toBe(true); expect(program.opts().bun).toBe(true); expect(program.opts().install).toBe(true); + expect(program.opts().template).toBe('nextjs'); }); test('setupProgram - no args', () => { diff --git a/packages/create-fuels/src/lib/setupProgram.ts b/packages/create-fuels/src/lib/setupProgram.ts index 069d8734ba8..f12c827fd6b 100644 --- a/packages/create-fuels/src/lib/setupProgram.ts +++ b/packages/create-fuels/src/lib/setupProgram.ts @@ -2,6 +2,10 @@ import { Command } from 'commander'; import packageJson from '../../package.json'; +export type Template = 'nextjs'; +export const templates: Set