diff --git a/__tests__/utils/address.test.ts b/__tests__/utils/address.test.ts index b3c346f37..cd59cff97 100644 --- a/__tests__/utils/address.test.ts +++ b/__tests__/utils/address.test.ts @@ -6,6 +6,22 @@ import { validateChecksumAddress, } from '../../src/utils/address'; +describe('addAddressPadding', () => { + test('should correctly add padding', () => { + const addr = '0x6eff1d71'; + const padded = '0x000000000000000000000000000000000000000000000000000000006eff1d71'; + + return expect(addAddressPadding(addr)).toBe(padded); + }); + + test('should add hex prefix', () => { + const addr = 'a7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf'; + const padded = '0xa7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf'; + + return expect(addAddressPadding(addr)).toBe(padded); + }); +}); + describe('validateAndParseAddress', () => { test('should pass when correct starknet address is passed', () => { const addr = '0x7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf'; @@ -14,11 +30,16 @@ describe('validateAndParseAddress', () => { }); test('should add 0x prefix if not provided', () => { - const addr = '0x6eff1d71068df8e6677f59a556151c56ed13e14ad431a9bef6fcb3fc5e6fa7'; + const addr = '6eff1d71068df8e6677f59a556151c56ed13e14ad431a9bef6fcb3fc5e6fa7'; return expect(validateAndParseAddress(addr)).toEqual(`${addAddressPadding(addr)}`); }); + test('should fail for invalid address', () => { + const addr = 'test'; + expect(() => validateAndParseAddress(addr)).toThrow('Cannot convert 0xtest to a BigInt'); + }); + test('should fail for out of bound address', () => { const addr = num.toHex(constants.ADDR_BOUND + 1n); expect(() => validateAndParseAddress(addr)).toThrow(/^Message not signable/); diff --git a/__tests__/utils/contract.test.ts b/__tests__/utils/contract.test.ts new file mode 100644 index 000000000..d74529b49 --- /dev/null +++ b/__tests__/utils/contract.test.ts @@ -0,0 +1,31 @@ +import { isSierra, extractContractHashes } from '../../src/utils/contract'; +import { compiledHelloSierra, compiledHelloSierraCasm, compiledErc20 } from '../config/fixtures'; + +describe('isSierra', () => { + test('should return true for a contract in Sierra format', () => { + expect(isSierra(compiledHelloSierra)).toBe(true); + }); + + test('should return false for a contract not in Sierra format', () => { + expect(isSierra(compiledErc20)).toBe(false); + }); +}); + +describe('extractContractHashes', () => { + test('should properly extract hashes from contract', () => { + const declareContractPayload = { + contract: compiledHelloSierra, + casm: compiledHelloSierraCasm, + }; + const result = extractContractHashes(declareContractPayload); + + expect(result).toHaveProperty( + 'classHash', + '0x50f3c3b9bb088969310de339fd1c1da88945f5db15bd5ea0810e4d954308734' + ); + expect(result).toHaveProperty( + 'compiledClassHash', + '0x31c736e739e4bd35116ed6cdcbb99c94e6f4fa8268d339da23e1ca80fe1de8d' + ); + }); +}); diff --git a/src/utils/address.ts b/src/utils/address.ts index fe5a012e4..905ec45ee 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -9,6 +9,7 @@ import { assertInRange, toHex } from './num'; /** * Format a hex number to '0x' and 64 characters, adding leading zeros if necessary. + * * @param {BigNumberish} address * @returns {string} Hex string : 0x followed by 64 characters. No upper case characters in the response. * @example @@ -19,13 +20,17 @@ import { assertInRange, toHex } from './num'; * ``` */ export function addAddressPadding(address: BigNumberish): string { - return addHexPrefix(removeHexPrefix(toHex(address)).padStart(64, '0')); + const hex = toHex(addHexPrefix(address.toString())); + const padded = removeHexPrefix(hex).padStart(64, '0'); + return addHexPrefix(padded); } /** * Check the validity of a Starknet address, and format it as a hex number : '0x' and 64 characters, adding leading zeros if necessary. + * * @param {BigNumberish} address * @returns {string} Hex string : 0x followed by 64 characters. No upper case characters in the response. + * @throws address argument must be a valid address inside the address range bound * @example * ```typescript * const address = "0x90591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf"; @@ -34,14 +39,14 @@ export function addAddressPadding(address: BigNumberish): string { * ``` */ export function validateAndParseAddress(address: BigNumberish): string { - assertInRange(address, ZERO, ADDR_BOUND - 1n, 'Starknet Address'); - const result = addAddressPadding(address); if (!result.match(/^(0x)?[0-9a-fA-F]{64}$/)) { throw new Error('Invalid Address Format'); } + assertInRange(result, ZERO, ADDR_BOUND - 1n, 'Starknet Address'); + return result; } @@ -80,7 +85,6 @@ export function getChecksumAddress(address: BigNumberish): string { * a given address to reduce the risk of errors introduced from typing an address or cut and paste issues. * * @param address string - * * @returns true if the ChecksumAddress is valid * @example * ```typescript diff --git a/src/utils/assert.ts b/src/utils/assert.ts index ef35545a3..56323961f 100644 --- a/src/utils/assert.ts +++ b/src/utils/assert.ts @@ -3,6 +3,11 @@ * @param {any} condition - The condition to check. * @param {string} [message] - The optional message to include in the error. * @throws {Error} Throws an error if the condition is false. + * @example + * ```typescript + * const address = '0xa7ee790591d9fa3efc87067d95a643f8455e0b8190eb8cb7bfd39e4fb7571fdf'; + * assert(/^(0x)?[0-9a-fA-F]{64}$/.test(address), 'Invalid address format'); + * ``` */ export default function assert(condition: boolean, message?: string): asserts condition { if (!condition) { diff --git a/src/utils/contract.ts b/src/utils/contract.ts index 1fb6a5315..b93c8c230 100644 --- a/src/utils/contract.ts +++ b/src/utils/contract.ts @@ -18,6 +18,11 @@ import { isString } from './shortString'; * * @param {CairoContract | string} contract - The contract to check. Can be either a CairoContract object or a string representation of the contract. * @return {boolean} - Returns true if the contract is a Sierra contract, otherwise false. + * @example + * ```typescript + * const result = isSierra(contract); + * // result = true | false + * ``` */ export function isSierra( contract: CairoContract | string @@ -30,10 +35,18 @@ export function isSierra( * Extracts contract hashes from `DeclareContractPayload`. * * @param {DeclareContractPayload} payload - The payload containing contract information. - * * @return {CompleteDeclareContractPayload} - The `CompleteDeclareContractPayload` with extracted contract hashes. - * * @throws {Error} - If extraction of compiledClassHash or classHash fails. + * @example + * ```typescript + * const result = extractContractHashes(contract); + * // result = { + * // contract: ..., + * // classHash: ..., + * // casm: ..., + * // compiledClassHash: ..., + * // } + * ``` */ export function extractContractHashes( payload: DeclareContractPayload diff --git a/src/utils/events.ts b/src/utils/events.ts index 700dd97b6..3fcb4ef47 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -6,7 +6,34 @@ import { cleanHex } from './num'; * Parse Transaction Receipt Event from UDC invoke transaction and * create DeployContractResponse compatible response with addition of the UDC Event data * - * @returns DeployContractResponse | UDC Event Response data + * @param {InvokeTransactionReceiptResponse} txReceipt + * @return {object} Object including DeployContractResponse and UDC Event data + * @example + * ```typescript + * const deployment = await account.deploy({ + * classHash, + * constructorCalldata: [ + * encodeShortString('Token'), + * encodeShortString('ERC20'), + * account.address, + * ], + * salt, + * unique: true, + * }); + * const txReceipt = await provider.waitForTransaction(deployment.transaction_hash); + * const udcEvent = parseUDCEvent(txReceipt as any); + * // udcEvent = { + * // transaction_hash: ..., + * // contract_address: ..., + * // address: ..., + * // deployer: ..., + * // unique: ..., + * // classHash: ..., + * // calldata_len: ..., + * // calldata: ..., + * // salt: ..., + * // } + * ``` */ export function parseUDCEvent(txReceipt: InvokeTransactionReceiptResponse) { if (!txReceipt.events) { diff --git a/src/utils/json.ts b/src/utils/json.ts index 20d9a2801..84a6a47e5 100644 --- a/src/utils/json.ts +++ b/src/utils/json.ts @@ -1,12 +1,12 @@ import * as json from 'lossless-json'; /** - * Convert string to number or bigint based on size + * Helper to convert string to number or bigint based on size */ -const parseIntAsNumberOrBigInt = (x: string) => { - if (!json.isInteger(x)) return parseFloat(x); - const v = parseInt(x, 10); - return Number.isSafeInteger(v) ? v : BigInt(x); +const parseIntAsNumberOrBigInt = (str: string) => { + if (!json.isInteger(str)) return parseFloat(str); + const num = parseInt(str, 10); + return Number.isSafeInteger(num) ? num : BigInt(str); }; /** @@ -14,23 +14,50 @@ const parseIntAsNumberOrBigInt = (x: string) => { * * NOTE: the String() wrapping is used so the behavior conforms to JSON.parse() * which can accept simple data types but is not represented in the default typing - * @param x JSON string + * + * @param str JSON string + * @return {object} Parsed json object + * @example + * ```typescript + * const str = '[123, 12.3, 11223344556677889900]'; + * const result = parse(str); + * // result = [123, 12.3, 11223344556677890048n] + * ``` */ -export const parse = (x: string): any => json.parse(String(x), undefined, parseIntAsNumberOrBigInt); +export const parse = (str: string): any => + json.parse(String(str), undefined, parseIntAsNumberOrBigInt); /** * Convert JSON string to JSON object with all numbers as bigint - * @param x JSON string + * @param str JSON string + * @return {object} Parsed json object + * @example + * ```typescript + * const str = '[123, 12.3, 1234567890]'; + * const result = parseAlwaysAsBig(str); + * // result = [123n, 12.3, 1234567890n] + * ``` */ -export const parseAlwaysAsBig = (x: string): any => - json.parse(String(x), undefined, json.parseNumberAndBigInt); +export const parseAlwaysAsBig = (str: string): any => + json.parse(String(str), undefined, json.parseNumberAndBigInt); /** * Convert JSON object to JSON string * * NOTE: the not-null assertion is used so the return type conforms to JSON.stringify() * which can also return undefined but is not represented in the default typing - * @returns JSON string + * + * @param value JSON object + * @param [replacer] Function that alters the behavior of the stringification process + * @param [space] Used to insert white space into the output JSON string + * @param [numberStringifiers] Function used to stringify numbers (returning undefined will delete the property from the object) + * @return {string} JSON string + * @example + * ```typescript + * const value = [123, 12.3, 1234567890]; + * const result = stringify(value); + * // result = '[123,12.3,1234567890]' + * ``` */ export const stringify = ( value: unknown,