Skip to content

Commit

Permalink
Merge branch 'master' into db/feat/implement-ethers-utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
Torres-ssf authored Nov 29, 2023
2 parents 1350232 + 4d1f623 commit 727c5b9
Show file tree
Hide file tree
Showing 17 changed files with 169 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-falcons-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/program": patch
---

Add transaction id helper function to base invocation scope
5 changes: 5 additions & 0 deletions .changeset/selfish-rocks-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels": patch
---

Updated npm keywords
7 changes: 7 additions & 0 deletions .changeset/strong-penguins-pretend.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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());
});

Expand All @@ -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);
});
});
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/src/guide/scripts/preparing-a-script.md
Original file line number Diff line number Diff line change
@@ -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}
44 changes: 36 additions & 8 deletions packages/address/src/address.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand Down Expand Up @@ -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)', () => {
Expand Down Expand Up @@ -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)', () => {
Expand Down Expand Up @@ -256,13 +254,33 @@ 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);

expect(address.toAddress()).toEqual(ADDRESS_BECH32);
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}"`);
Expand Down Expand Up @@ -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);
});
});
18 changes: 18 additions & 0 deletions packages/address/src/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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));
}

Expand Down Expand Up @@ -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));
Expand Down
9 changes: 4 additions & 5 deletions packages/address/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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;
};
4 changes: 2 additions & 2 deletions packages/contract/src/contract-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
11 changes: 9 additions & 2 deletions packages/contract/src/util.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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}.`);
};
3 changes: 3 additions & 0 deletions packages/errors/src/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand Down
11 changes: 10 additions & 1 deletion packages/fuels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
5 changes: 3 additions & 2 deletions packages/program/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
14 changes: 14 additions & 0 deletions packages/program/src/functions/base-invocation-scope.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -372,4 +373,17 @@ export class BaseInvocationScope<TReturn = any> {

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<string> {
const chainIdToHash = chainId ?? (await this.getProvider().getChainId());

const transactionRequest = await this.getTransactionRequest();
return hashTransaction(transactionRequest, chainIdToHash);
}
}
4 changes: 3 additions & 1 deletion packages/wallet/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionRequestLike, 'gasLimit' | 'gasPrice' | 'maturity'> = {
gasLimit: bn(1),
gasPrice: bn(1),
Expand Down
Loading

0 comments on commit 727c5b9

Please sign in to comment.