diff --git a/.changeset/tall-planes-nail.md b/.changeset/tall-planes-nail.md new file mode 100644 index 00000000000..1419e9c50db --- /dev/null +++ b/.changeset/tall-planes-nail.md @@ -0,0 +1,8 @@ +--- +"@fuel-ts/transactions": minor +"@fuel-ts/contract": patch +"@fuel-ts/account": minor +"@fuel-ts/errors": minor +--- + +feat!: deploy contract validation diff --git a/apps/docs/src/guide/contracts/deploying-contracts.md b/apps/docs/src/guide/contracts/deploying-contracts.md index 1defa7265da..9e00beac37a 100644 --- a/apps/docs/src/guide/contracts/deploying-contracts.md +++ b/apps/docs/src/guide/contracts/deploying-contracts.md @@ -9,6 +9,8 @@ This guide walks you through deploying a contract using the SDK, covering loading contract artifacts, initializing a contract factory, and deploying the contract. +> **Note:** The maximum contract deployment size supported by the SDK is 100 KB. The SDK will `throw` an [error](../errors/index.md) for contracts larger than this size. + ## 1. Obtaining Contract Artifacts After writing a contract in Sway and compiling it with `forc build` (read more on how to work with Sway), you will obtain two important artifacts: the compiled binary file and the JSON ABI file. These files are required for deploying a contract using the SDK. diff --git a/apps/docs/src/guide/errors/index.md b/apps/docs/src/guide/errors/index.md index 27b94c4c1ef..b84657a5d52 100644 --- a/apps/docs/src/guide/errors/index.md +++ b/apps/docs/src/guide/errors/index.md @@ -30,6 +30,12 @@ When converting a big number into an incompatible format. Ensure that the value you've supplied to the big number is compatible with the value you are converting to. +### `CONTRACT_SIZE_EXCEEDS_LIMIT` + +When the contract size exceeds the maximum contract size limit. + +Ensure that the contract size is less than the maximum contract size limit, of 100 KB. This can be validated by checking the bytecode length of the contract. + ### `DUPLICATED_POLICY` When there are more than policies with the same type, for a transaction. @@ -174,9 +180,9 @@ When the transaction status received from the node is unexpected. Check the status received is within `TransactionStatus`. -### `INVALID_TRANSACTION_TYPE` +### `UNSUPPORTED_TRANSACTION_TYPE` -When the transaction type from the Fuel Node is _not_ valid. +When the transaction type from the Fuel Node is _not_ supported. The type is within [`TransactionType`](../../api/Account/TransactionType.md). diff --git a/apps/docs/src/guide/fuels-cli/commands.md b/apps/docs/src/guide/fuels-cli/commands.md index 0afb25e397d..5bdcaa9683e 100644 --- a/apps/docs/src/guide/fuels-cli/commands.md +++ b/apps/docs/src/guide/fuels-cli/commands.md @@ -95,6 +95,8 @@ npx fuels@{{fuels}} deploy > [!NOTE] Note > We recommend using the `fuels deploy` command only when you are deploying contracts to a local node. > If you are deploying contracts to a live network like the Testnet, we recommend using the [`forc deploy`](https://docs.fuel.network/docs/intro/quickstart-contract/#deploy-to-testnet) command instead. +> +> Additionally, the maximum contract deployment size supported by the SDK is 100 KB. The SDK will `throw` an [error](../errors/index.md) for contracts larger than this size. The `fuels deploy` command does two things: diff --git a/packages/account/src/providers/transaction-request/transaction-request.test.ts b/packages/account/src/providers/transaction-request/transaction-request.test.ts index d25feee64e3..738c46cb9e8 100644 --- a/packages/account/src/providers/transaction-request/transaction-request.test.ts +++ b/packages/account/src/providers/transaction-request/transaction-request.test.ts @@ -185,11 +185,13 @@ describe('transactionRequestify', () => { expect(txRequest.witnesses).toEqual(txRequestLike.witnesses); }); - it('should throw error if invalid transaction type', () => { + it('should throw error if unsupported transaction type', () => { const txRequestLike = { - type: 5, + type: 1234, }; - expect(() => transactionRequestify(txRequestLike)).toThrow('Invalid transaction type: 5'); + expect(() => transactionRequestify(txRequestLike)).toThrow( + 'Unsupported transaction type: 1234' + ); }); }); diff --git a/packages/account/src/providers/transaction-request/utils.ts b/packages/account/src/providers/transaction-request/utils.ts index d89de4772cb..3aeac32e56a 100644 --- a/packages/account/src/providers/transaction-request/utils.ts +++ b/packages/account/src/providers/transaction-request/utils.ts @@ -21,7 +21,10 @@ export const transactionRequestify = (obj: TransactionRequestLike): TransactionR return CreateTransactionRequest.from(obj); } default: { - throw new FuelError(ErrorCode.INVALID_TRANSACTION_TYPE, `Invalid transaction type: ${type}.`); + throw new FuelError( + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${type}.` + ); } } }; diff --git a/packages/account/src/providers/transaction-summary/operations.test.ts b/packages/account/src/providers/transaction-summary/operations.test.ts index 2cda7a660bb..cf20482aca0 100644 --- a/packages/account/src/providers/transaction-summary/operations.test.ts +++ b/packages/account/src/providers/transaction-summary/operations.test.ts @@ -936,7 +936,7 @@ describe('operations', () => { expect(getTransactionTypeName(TransactionType.Script)).toBe(TransactionTypeName.Script); expect(() => getTransactionTypeName('' as unknown as TransactionType)).toThrowError( - 'Invalid transaction type: ' + 'Unsupported transaction type: ' ); }); }); diff --git a/packages/account/src/providers/transaction-summary/operations.ts b/packages/account/src/providers/transaction-summary/operations.ts index 55b4edd64b1..17e4f677302 100644 --- a/packages/account/src/providers/transaction-summary/operations.ts +++ b/packages/account/src/providers/transaction-summary/operations.ts @@ -57,8 +57,8 @@ export function getTransactionTypeName(transactionType: TransactionType): Transa return TransactionTypeName.Script; default: throw new FuelError( - ErrorCode.INVALID_TRANSACTION_TYPE, - `Invalid transaction type: ${transactionType}.` + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${transactionType}.` ); } } diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index 362d0abf025..f24f03d1d7d 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -15,7 +15,12 @@ import { Contract } from '@fuel-ts/program'; import type { StorageSlot } from '@fuel-ts/transactions'; import { arrayify, isDefined } from '@fuel-ts/utils'; -import { getContractId, getContractStorageRoot, hexlifyWithPrefix } from './util'; +import { + MAX_CONTRACT_SIZE, + getContractId, + getContractStorageRoot, + hexlifyWithPrefix, +} from './util'; /** * Options for deploying a contract. @@ -149,7 +154,15 @@ export default class ContractFactory { async deployContract( deployContractOptions: DeployContractOptions = {} ): Promise> { + if (this.bytecode.length > MAX_CONTRACT_SIZE) { + throw new FuelError( + ErrorCode.CONTRACT_SIZE_EXCEEDS_LIMIT, + 'Contract bytecode is too large. Max contract size is 100KB' + ); + } + const { contractId, transactionRequest } = await this.prepareDeploy(deployContractOptions); + const account = this.getAccount(); const transactionResponse = await account.sendTransaction(transactionRequest, { diff --git a/packages/contract/src/util.ts b/packages/contract/src/util.ts index 610f7b73ff1..e540d55235f 100644 --- a/packages/contract/src/util.ts +++ b/packages/contract/src/util.ts @@ -4,6 +4,9 @@ import { calcRoot, SparseMerkleTree } from '@fuel-ts/merkle'; import type { StorageSlot } from '@fuel-ts/transactions'; import { chunkAndPadBytes, hexlify, concat, arrayify } from '@fuel-ts/utils'; +// Max contract size in bytes is 100KB +export const MAX_CONTRACT_SIZE = 102400; + /** * @hidden * diff --git a/packages/errors/src/error-codes.ts b/packages/errors/src/error-codes.ts index 3a7927f71e1..4fc0c2f59d7 100644 --- a/packages/errors/src/error-codes.ts +++ b/packages/errors/src/error-codes.ts @@ -67,11 +67,12 @@ export enum ErrorCode { INVALID_TRANSACTION_INPUT = 'invalid-transaction-input', INVALID_TRANSACTION_OUTPUT = 'invalid-transaction-output', INVALID_TRANSACTION_STATUS = 'invalid-transaction-status', - INVALID_TRANSACTION_TYPE = 'invalid-transaction-type', + UNSUPPORTED_TRANSACTION_TYPE = 'unsupported-transaction-type', TRANSACTION_ERROR = 'transaction-error', INVALID_POLICY_TYPE = 'invalid-policy-type', DUPLICATED_POLICY = 'duplicated-policy', TRANSACTION_SQUEEZED_OUT = 'transaction-squeezed-out', + CONTRACT_SIZE_EXCEEDS_LIMIT = 'contract-size-exceeds-limit', // receipt INVALID_RECEIPT_TYPE = 'invalid-receipt-type', diff --git a/packages/fuel-gauge/src/contract-factory.test.ts b/packages/fuel-gauge/src/contract-factory.test.ts index 6d551a8ffb3..03e918c17ee 100644 --- a/packages/fuel-gauge/src/contract-factory.test.ts +++ b/packages/fuel-gauge/src/contract-factory.test.ts @@ -247,4 +247,22 @@ describe('Contract Factory', () => { ) ); }); + + it('should not deploy contracts greater than 100KB', async () => { + using launched = await launchTestNode(); + const { + wallets: [wallet], + } = launched; + + const largeByteCode = `0x${'00'.repeat(112400)}`; + const factory = new ContractFactory(largeByteCode, StorageTestContractAbi__factory.abi, wallet); + + await expectToThrowFuelError( + async () => factory.deployContract(), + new FuelError( + ErrorCode.CONTRACT_SIZE_EXCEEDS_LIMIT, + 'Contract bytecode is too large. Max contract size is 100KB' + ) + ); + }); }); diff --git a/packages/transactions/src/coders/transaction.ts b/packages/transactions/src/coders/transaction.ts index 1aab7442bb4..efca5d0e472 100644 --- a/packages/transactions/src/coders/transaction.ts +++ b/packages/transactions/src/coders/transaction.ts @@ -628,8 +628,8 @@ export class TransactionCoder extends Coder { } default: { throw new FuelError( - ErrorCode.INVALID_TRANSACTION_TYPE, - `Invalid transaction type: ${type}` + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${type}` ); } } @@ -667,8 +667,8 @@ export class TransactionCoder extends Coder { } default: { throw new FuelError( - ErrorCode.INVALID_TRANSACTION_TYPE, - `Invalid transaction type: ${type}` + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${type}` ); } } diff --git a/packages/transactions/src/coders/upgrade-purpose.ts b/packages/transactions/src/coders/upgrade-purpose.ts index 1b51e340abf..33db4b8d5ad 100644 --- a/packages/transactions/src/coders/upgrade-purpose.ts +++ b/packages/transactions/src/coders/upgrade-purpose.ts @@ -59,8 +59,8 @@ export class UpgradePurposeCoder extends Coder { default: { throw new FuelError( - ErrorCode.INVALID_TRANSACTION_TYPE, - `Invalid transaction type: ${type}` + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${type}` ); } } @@ -94,8 +94,8 @@ export class UpgradePurposeCoder extends Coder { default: { throw new FuelError( - ErrorCode.INVALID_TRANSACTION_TYPE, - `Invalid transaction type: ${type}` + ErrorCode.UNSUPPORTED_TRANSACTION_TYPE, + `Unsupported transaction type: ${type}` ); } }