From 25cc508c51fe7280d7e3e098eb2f7dd8db507147 Mon Sep 17 00:00:00 2001 From: Cameron Manavian Date: Mon, 27 Nov 2023 07:30:30 -0800 Subject: [PATCH 1/3] fix: add keywords (#1453) * add keywords * cs --- .changeset/selfish-rocks-tap.md | 5 +++++ packages/fuels/package.json | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/selfish-rocks-tap.md diff --git a/.changeset/selfish-rocks-tap.md b/.changeset/selfish-rocks-tap.md new file mode 100644 index 00000000000..7dd18075079 --- /dev/null +++ b/.changeset/selfish-rocks-tap.md @@ -0,0 +1,5 @@ +--- +"fuels": patch +--- + +Updated npm keywords diff --git a/packages/fuels/package.json b/packages/fuels/package.json index 4cc27f342d1..d793a919c58 100644 --- a/packages/fuels/package.json +++ b/packages/fuels/package.json @@ -85,5 +85,14 @@ "devDependencies": { "@types/lodash.camelcase": "^4.3.7", "@types/rimraf": "^3.0.2" - } + }, + "keywords": [ + "ethereum", + "forc", + "fuel", + "fuels", + "fuel-vm", + "sway", + "typescript" + ] } From c9a8b3335b58baf929ce363d6471f5aa4aa9cc08 Mon Sep 17 00:00:00 2001 From: Cameron Manavian Date: Mon, 27 Nov 2023 08:44:59 -0800 Subject: [PATCH 2/3] fix: ensure hex values are prefixed or fail (#1454) * change strictness * adjust tests * add codes * add checks * tests * catch hexlify case * remove prefixing * use slice * cs * prefix * revert * adjust how the API works * fix address --- .changeset/strong-penguins-pretend.md | 7 ++++ packages/address/src/address.test.ts | 44 ++++++++++++++++++----- packages/address/src/address.ts | 18 ++++++++++ packages/address/src/utils.ts | 9 +++-- packages/contract/src/contract-factory.ts | 4 +-- packages/contract/src/util.ts | 11 ++++-- packages/errors/src/error-codes.ts | 3 ++ packages/wallet/src/account.test.ts | 4 ++- 8 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 .changeset/strong-penguins-pretend.md diff --git a/.changeset/strong-penguins-pretend.md b/.changeset/strong-penguins-pretend.md new file mode 100644 index 00000000000..cd20534da1c --- /dev/null +++ b/.changeset/strong-penguins-pretend.md @@ -0,0 +1,7 @@ +--- +"@fuel-ts/address": minor +"@fuel-ts/contract": minor +"@fuel-ts/errors": patch +--- + +Remove hexlify logic on values that are not hex diff --git a/packages/address/src/address.test.ts b/packages/address/src/address.test.ts index b6233157bbb..6c2d95cdd43 100644 --- a/packages/address/src/address.test.ts +++ b/packages/address/src/address.test.ts @@ -87,9 +87,9 @@ describe('Address utils', () => { }); test('isB256 (no hex prefix)', () => { - const result = utils.isB256('ef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a'); + const result = utils.isB256(ADDRESS_B256.slice(2)); - expect(result).toBeTruthy(); + expect(result).toBeFalsy(); }); test('isB256 (using toB256)', () => { @@ -123,11 +123,9 @@ describe('Address utils', () => { }); test('isPublicKey (no hex prefix)', () => { - const result = utils.isPublicKey( - '2f34bc0df4db0ec391792cedb05768832b49b1aa3a2dd8c30054d1af00f67d00b74b7acbbf3087c8e0b1a4c343db50aa471d21f278ff5ce09f07795d541fb47e' - ); + const result = utils.isPublicKey(PUBLIC_KEY.slice(2)); - expect(result).toBeTruthy(); + expect(result).toBeFalsy(); }); test('isEvmAddress (EvmAddress)', () => { @@ -155,9 +153,9 @@ describe('Address utils', () => { }); test('isEvmAddress (no hex prefix)', () => { - const result = utils.isEvmAddress('07a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a'); + const result = utils.isEvmAddress(ADDRESS_EVM.slice(2)); - expect(result).toBeTruthy(); + expect(result).toBeFalsy(); }); test('getBytesFromBech32 (bech32 to Uint8Array)', () => { @@ -256,6 +254,16 @@ describe('Address class', () => { expect(address.toB256()).toEqual(signMessageTest.b256Address); }); + test('create an Address class using invalid public key (no hex prefix)', async () => { + const address = PUBLIC_KEY.slice(2); + + const expectedError = new FuelError( + FuelError.CODES.INVALID_PUBLIC_KEY, + `Invalid Public Key: ${address}.` + ); + await expectToThrowFuelError(() => Address.fromPublicKey(address), expectedError); + }); + test('create an Address class using b256Address', () => { const address = Address.fromB256(ADDRESS_B256); @@ -263,6 +271,16 @@ describe('Address class', () => { expect(address.toB256()).toEqual(ADDRESS_B256); }); + test('create an Address class using invalid b256Address (no hex prefix)', async () => { + const address = ADDRESS_B256.slice(2); + + const expectedError = new FuelError( + FuelError.CODES.INVALID_B256_ADDRESS, + `Invalid B256 Address: ${address}.` + ); + await expectToThrowFuelError(() => Address.fromB256(address), expectedError); + }); + test('when parsing to JSON it should show the bech32 address', () => { const result = Address.fromB256(signMessageTest.b256Address); expect(JSON.stringify(result)).toEqual(`"${signMessageTest.address}"`); @@ -334,4 +352,14 @@ describe('Address class', () => { expect(address.toEvmAddress()).toMatchObject(evmAddressWrapped); expect(address.toB256()).toEqual(ADDRESS_B256_EVM_PADDED); }); + + test('create an Address class using invalid Evm Address (no hex prefix)', async () => { + const address = ADDRESS_EVM.slice(2); + + const expectedError = new FuelError( + FuelError.CODES.INVALID_EVM_ADDRESS, + `Invalid Evm Address: ${address}.` + ); + await expectToThrowFuelError(() => Address.fromEvmAddress(address), expectedError); + }); }); diff --git a/packages/address/src/address.ts b/packages/address/src/address.ts index deb7e43174f..4d73f3148e1 100644 --- a/packages/address/src/address.ts +++ b/packages/address/src/address.ts @@ -134,6 +134,10 @@ export default class Address extends AbstractAddress { * @returns A new `Address` instance */ static fromPublicKey(publicKey: string): Address { + if (!isPublicKey(publicKey)) { + throw new FuelError(FuelError.CODES.INVALID_PUBLIC_KEY, `Invalid Public Key: ${publicKey}.`); + } + const b256Address = sha256(hexlify(getBytesCopy(publicKey))); return new Address(toBech32(b256Address)); } @@ -145,6 +149,13 @@ export default class Address extends AbstractAddress { * @returns A new `Address` instance */ static fromB256(b256Address: string): Address { + if (!isB256(b256Address)) { + throw new FuelError( + FuelError.CODES.INVALID_B256_ADDRESS, + `Invalid B256 Address: ${b256Address}.` + ); + } + return new Address(toBech32(b256Address)); } @@ -218,6 +229,13 @@ export default class Address extends AbstractAddress { * @returns A new `Address` instance */ static fromEvmAddress(evmAddress: string): Address { + if (!isEvmAddress(evmAddress)) { + throw new FuelError( + FuelError.CODES.INVALID_EVM_ADDRESS, + `Invalid Evm Address: ${evmAddress}.` + ); + } + const paddedAddress = padFirst12BytesOfEvmAddress(evmAddress); return new Address(toBech32(paddedAddress)); diff --git a/packages/address/src/utils.ts b/packages/address/src/utils.ts index cd247ab7c18..d2f11c0393f 100644 --- a/packages/address/src/utils.ts +++ b/packages/address/src/utils.ts @@ -61,7 +61,7 @@ export function isBech32(address: BytesLike): boolean { * @hidden */ export function isB256(address: string): boolean { - return (address.length === 66 || address.length === 64) && /(0x)?[0-9a-f]{64}$/i.test(address); + return address.length === 66 && /(0x)[0-9a-f]{64}$/i.test(address); } /** @@ -70,7 +70,7 @@ export function isB256(address: string): boolean { * @hidden */ export function isPublicKey(address: string): boolean { - return (address.length === 130 || address.length === 128) && /(0x)?[0-9a-f]{128}$/i.test(address); + return address.length === 130 && /(0x)[0-9a-f]{128}$/i.test(address); } /** @@ -79,7 +79,7 @@ export function isPublicKey(address: string): boolean { * @hidden */ export function isEvmAddress(address: string): boolean { - return (address.length === 42 || address.length === 40) && /(0x)?[0-9a-f]{40}$/i.test(address); + return address.length === 42 && /(0x)[0-9a-f]{40}$/i.test(address); } /** @@ -187,6 +187,5 @@ export const padFirst12BytesOfEvmAddress = (address: string): B256AddressEvm => throw new FuelError(FuelError.CODES.INVALID_EVM_ADDRESS, 'Invalid EVM address format.'); } - const prefixedAddress = address.startsWith('0x') ? address : `0x${address}`; - return prefixedAddress.replace('0x', '0x000000000000000000000000') as B256AddressEvm; + return address.replace('0x', '0x000000000000000000000000') as B256AddressEvm; }; diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index 5ae3782ebb8..a2c67f044bb 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -92,8 +92,8 @@ export default class ContractFactory { createTransactionRequest(deployContractOptions?: DeployContractOptions) { const storageSlots = deployContractOptions?.storageSlots ?.map(({ key, value }) => ({ - key: hexlifyWithPrefix(key), - value: hexlifyWithPrefix(value), + key: hexlifyWithPrefix(key, true), + value: hexlifyWithPrefix(value, true), })) .sort(({ key: keyA }, { key: keyB }) => keyA.localeCompare(keyB)); diff --git a/packages/contract/src/util.ts b/packages/contract/src/util.ts index 6a7bbecaa9c..a5a00a2b65c 100644 --- a/packages/contract/src/util.ts +++ b/packages/contract/src/util.ts @@ -1,3 +1,4 @@ +import { FuelError } from '@fuel-ts/errors'; import { calcRoot, SparseMerkleTree } from '@fuel-ts/merkle'; import type { StorageSlot } from '@fuel-ts/transactions'; import { chunkAndPadBytes } from '@fuel-ts/utils'; @@ -63,11 +64,17 @@ export const getContractId = ( * Ensures that a string is hexlified. * * @param value - The value to be hexlified. + * @param isKnownHex - Required if using hex values that need to be converted * @returns The input value hexlified with prefix. */ -export const hexlifyWithPrefix = (value: string) => { +export const hexlifyWithPrefix = (value: string, isKnownHex = false) => { if (value.startsWith('0x')) { return hexlify(value); } - return hexlify(`0x${value}`); + + if (isKnownHex) { + return hexlify(`0x${value}`); + } + + throw new FuelError(FuelError.CODES.UNEXPECTED_HEX_VALUE, `Value should be hex string ${value}.`); }; diff --git a/packages/errors/src/error-codes.ts b/packages/errors/src/error-codes.ts index 1e0459d191f..255c9e9c6af 100644 --- a/packages/errors/src/error-codes.ts +++ b/packages/errors/src/error-codes.ts @@ -22,6 +22,7 @@ export enum ErrorCode { // address INVALID_BECH32_ADDRESS = 'invalid-bech32-address', INVALID_EVM_ADDRESS = 'invalid-evm-address', + INVALID_B256_ADDRESS = 'invalid-b256-address', // provider INVALID_URL = 'invalid-url', @@ -30,6 +31,7 @@ export enum ErrorCode { MISSING_PROVIDER = 'missing-provider', // wallet + INVALID_PUBLIC_KEY = 'invalid-public-key', INSUFFICIENT_BALANCE = 'insufficient-balance', WALLET_MANAGER_ERROR = 'wallet-manager-error', HD_WALLET_ERROR = 'hd-wallet-error', @@ -47,6 +49,7 @@ export enum ErrorCode { CONVERTING_FAILED = 'converting-error', ELEMENT_NOT_FOUND = 'element-not-found', MISSING_REQUIRED_PARAMETER = 'missing-required-parameter', + UNEXPECTED_HEX_VALUE = 'unexpected-hex-value', // transaction GAS_PRICE_TOO_LOW = 'gas-price-too-low', diff --git a/packages/wallet/src/account.test.ts b/packages/wallet/src/account.test.ts index 7c245eab712..0367187d67c 100644 --- a/packages/wallet/src/account.test.ts +++ b/packages/wallet/src/account.test.ts @@ -249,7 +249,9 @@ describe('Account', () => { it('should execute transfer just as fine', async () => { const amount = bn(1); const assetId = '0x0101010101010101010101010101010101010101010101010101010101010101'; - const destination = Address.fromAddressOrString('0x0101010101010101010101010101010101010101'); + const destination = Address.fromAddressOrString( + '0x0101010101010101010101010101010101010101000000000000000000000000' + ); const txParam: Pick = { gasLimit: bn(1), gasPrice: bn(1), From 4d1f62301c8ccdaad9dd9a087f421a71d3600ef5 Mon Sep 17 00:00:00 2001 From: Daniel Bate Date: Tue, 28 Nov 2023 11:15:20 +0000 Subject: [PATCH 3/3] feat: improve tx preparation for a `Script` and implement txId helper (#1466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add helper function to retrieve transaction Id * feat: install hasher in program, * feat: add docs for tx preparation in scripts * chore: linting * chore: changeset * chore: fix grammar Co-authored-by: Nedim Salkić --------- Co-authored-by: Nedim Salkić --- .changeset/gold-falcons-nail.md | 5 +++ .../scripts/script-with-configurable.test.ts | 40 ++++++++++++++++++- apps/docs/.vitepress/config.ts | 4 ++ .../src/guide/scripts/preparing-a-script.md | 5 +++ packages/program/package.json | 5 ++- .../src/functions/base-invocation-scope.ts | 14 +++++++ pnpm-lock.yaml | 3 ++ 7 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 .changeset/gold-falcons-nail.md create mode 100644 apps/docs/src/guide/scripts/preparing-a-script.md diff --git a/.changeset/gold-falcons-nail.md b/.changeset/gold-falcons-nail.md new file mode 100644 index 00000000000..3aab47d4556 --- /dev/null +++ b/.changeset/gold-falcons-nail.md @@ -0,0 +1,5 @@ +--- +"@fuel-ts/program": patch +--- + +Add transaction id helper function to base invocation scope diff --git a/apps/docs-snippets/src/guide/scripts/script-with-configurable.test.ts b/apps/docs-snippets/src/guide/scripts/script-with-configurable.test.ts index 63806679a67..8cc7a492cef 100644 --- a/apps/docs-snippets/src/guide/scripts/script-with-configurable.test.ts +++ b/apps/docs-snippets/src/guide/scripts/script-with-configurable.test.ts @@ -1,18 +1,23 @@ -import type { WalletUnlocked } from 'fuels'; +import type { WalletUnlocked, Provider } from 'fuels'; import { Script, BN } from 'fuels'; -import { DocSnippetProjectsEnum, getDocsSnippetsForcProject } from '../../../test/fixtures/forc-projects'; +import { + DocSnippetProjectsEnum, + getDocsSnippetsForcProject, +} from '../../../test/fixtures/forc-projects'; import { getTestWallet } from '../../utils'; describe(__filename, () => { let wallet: WalletUnlocked; let gasPrice: BN; + let provider: Provider; const { abiContents, binHexlified } = getDocsSnippetsForcProject( DocSnippetProjectsEnum.SUM_SCRIPT ); beforeAll(async () => { wallet = await getTestWallet(); + provider = wallet.provider; ({ minGasPrice: gasPrice } = wallet.provider.getGasConfig()); }); @@ -35,4 +40,35 @@ describe(__filename, () => { expect(new BN(value as number).toNumber()).toEqual(expectedTotal); // #endregion script-with-configurable-contants-2 }); + + it('prepares a script and retrieves the id before submission', async () => { + const argument = 10; + const expected = 20; + + // #region preparing-scripts + const script = new Script(binHexlified, abiContents, wallet); + const { minGasPrice, maxGasPerTx } = provider.getGasConfig(); + + const tx = await script.functions.main(argument); + + // Set the call parameters + tx.callParams({ gasLimit: 100 }); + + // Set the transaction parameters + tx.txParams({ gasPrice: minGasPrice, gasLimit: maxGasPerTx }); + + // Get the entire transaction request prior to + const txRequest = await tx.getTransactionRequest(); + + // Get the transaction ID + const txId = await tx.getTransactionId(); + + // Retrieve the value of the call and the actual gas used + const { value, gasUsed } = await tx.call(); + // #endregion preparing-scripts + expect(txRequest).toBeDefined(); + expect(txId).toBeDefined(); + expect(new BN(value as number).toNumber()).toEqual(expected); + expect(new BN(gasUsed).toNumber()).toBeGreaterThan(0); + }); }); diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts index f0368c11247..9bf9a9b0ab2 100644 --- a/apps/docs/.vitepress/config.ts +++ b/apps/docs/.vitepress/config.ts @@ -363,6 +363,10 @@ export default defineConfig({ text: 'Calling A Script', link: '/guide/scripts/calling-a-script', }, + { + text: 'Preparing A Script', + link: '/guide/scripts/preparing-a-script', + }, { text: 'Script With Configurable Constants', link: '/guide/scripts/script-with-configurable-constants', diff --git a/apps/docs/src/guide/scripts/preparing-a-script.md b/apps/docs/src/guide/scripts/preparing-a-script.md new file mode 100644 index 00000000000..9595f9bf8b1 --- /dev/null +++ b/apps/docs/src/guide/scripts/preparing-a-script.md @@ -0,0 +1,5 @@ +# Preparing a Script Transaction + +Akin to Contracts, we can configure the [call parameters](../contracts/call-parameters.md) and [transaction parameters](../contracts/transaction-parameters.md) for Scripts, as well as retrieve the entire transaction request or transaction ID prior to submission. + +<<< @/../../docs-snippets/src/guide/scripts/script-with-configurable.test.ts#preparing-scripts{ts:line-numbers} \ No newline at end of file diff --git a/packages/program/package.json b/packages/program/package.json index 8efd172bae4..9bea56f7ebb 100644 --- a/packages/program/package.json +++ b/packages/program/package.json @@ -39,14 +39,15 @@ "dependencies": { "@fuel-ts/abi-coder": "workspace:*", "@fuel-ts/address": "workspace:*", + "@fuel-ts/errors": "workspace:*", + "@fuel-ts/hasher": "workspace:^", "@fuel-ts/interfaces": "workspace:*", "@fuel-ts/math": "workspace:*", "@fuel-ts/providers": "workspace:*", "@fuel-ts/transactions": "workspace:*", + "@fuel-ts/utils": "workspace:*", "@fuel-ts/versions": "workspace:*", "@fuel-ts/wallet": "workspace:*", - "@fuel-ts/errors": "workspace:*", - "@fuel-ts/utils": "workspace:*", "@fuels/vm-asm": "0.36.1", "ethers": "^6.7.1" }, diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 85ec87d1134..a2b94631704 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { InputValue } from '@fuel-ts/abi-coder'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; +import { hashTransaction } from '@fuel-ts/hasher'; import type { AbstractContract, AbstractProgram } from '@fuel-ts/interfaces'; import type { BN } from '@fuel-ts/math'; import { bn, toNumber } from '@fuel-ts/math'; @@ -372,4 +373,17 @@ export class BaseInvocationScope { return provider; } + + /** + * Obtains the ID of a transaction. + * + * @param chainId - the chainId to use to hash the transaction with + * @returns the ID of the transaction. + */ + async getTransactionId(chainId?: number): Promise { + const chainIdToHash = chainId ?? (await this.getProvider().getChainId()); + + const transactionRequest = await this.getTransactionRequest(); + return hashTransaction(transactionRequest, chainIdToHash); + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29cb1774544..b3e238b9cd2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -904,6 +904,9 @@ importers: '@fuel-ts/errors': specifier: workspace:* version: link:../errors + '@fuel-ts/hasher': + specifier: workspace:^ + version: link:../hasher '@fuel-ts/interfaces': specifier: workspace:* version: link:../interfaces