diff --git a/.gitignore b/.gitignore index 3a5a151fb..632f2b292 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .env .eslintcache .vscode +.idea coverage dist node_modules diff --git a/__tests__/cairo1v2_typed.test.ts b/__tests__/cairo1v2_typed.test.ts index ae31a39f3..cdcefcc35 100644 --- a/__tests__/cairo1v2_typed.test.ts +++ b/__tests__/cairo1v2_typed.test.ts @@ -27,7 +27,7 @@ import { types, } from '../src'; import { hexToDecimalString } from '../src/utils/num'; -import { encodeShortString } from '../src/utils/shortString'; +import { encodeShortString, isString } from '../src/utils/shortString'; import { TEST_TX_VERSION, compiledC1Account, @@ -337,7 +337,7 @@ describe('Cairo 1', () => { const status = await cairo1Contract.echo_struct({ val: 'simple', }); - if (typeof status.val === 'string') { + if (isString(status.val)) { expect(shortString.decodeShortString(status.val)).toBe('simple'); } }); diff --git a/__tests__/config/schema.ts b/__tests__/config/schema.ts index b5b0b5649..b6d20820f 100644 --- a/__tests__/config/schema.ts +++ b/__tests__/config/schema.ts @@ -7,6 +7,7 @@ import libSchemas from '../schemas/lib.json'; import providerSchemas from '../schemas/provider.json'; import rpcSchemas from '../schemas/rpc.json'; import sequencerSchemas from '../schemas/sequencer.json'; +import { isBigInt } from '../../src/utils/num'; const matcherSchemas = [accountSchemas, libSchemas, providerSchemas, rpcSchemas, sequencerSchemas]; const schemas = [...matcherSchemas, componentSchemas]; @@ -16,7 +17,7 @@ const jestJsonMatchers = matchersWithOptions({ schemas }, (ajv: any) => { keyword: 'isBigInt', type: 'object', validate: (_schema: any, data: any) => { - return typeof data === 'bigint' && data < 2n ** 64n && data >= 0n; + return isBigInt(data) && data < 2n ** 64n && data >= 0n; }, errors: true, }); diff --git a/src/account/default.ts b/src/account/default.ts index 23d2f185e..4101c1a10 100644 --- a/src/account/default.ts +++ b/src/account/default.ts @@ -56,6 +56,7 @@ import { import { buildUDCCall, getExecuteCalldata } from '../utils/transaction'; import { getMessageHash } from '../utils/typedData'; import { AccountInterface } from './interface'; +import { isString } from '../utils/shortString'; export class Account extends Provider implements AccountInterface { public signer: SignerInterface; @@ -76,7 +77,7 @@ export class Account extends Provider implements AccountInterface { super(providerOrOptions); this.address = address.toLowerCase(); this.signer = - typeof pkOrSigner === 'string' || pkOrSigner instanceof Uint8Array + isString(pkOrSigner) || pkOrSigner instanceof Uint8Array ? new Signer(pkOrSigner) : pkOrSigner; diff --git a/src/utils/cairoDataTypes/felt.ts b/src/utils/cairoDataTypes/felt.ts index ffbf448b2..480f7eba1 100644 --- a/src/utils/cairoDataTypes/felt.ts +++ b/src/utils/cairoDataTypes/felt.ts @@ -1,7 +1,7 @@ // TODO Convert to CairoFelt base on CairoUint256 and implement it in the codebase in the backward compatible manner -import { BigNumberish, isBigInt, isHex, isStringWholeNumber } from '../num'; -import { encodeShortString, isShortString, isText } from '../shortString'; +import { BigNumberish, isBigInt, isBoolean, isHex, isStringWholeNumber } from '../num'; +import { encodeShortString, isShortString, isString, isText } from '../shortString'; /** * Create felt Cairo type (cairo type helper) @@ -9,29 +9,33 @@ import { encodeShortString, isShortString, isText } from '../shortString'; */ export function CairoFelt(it: BigNumberish): string { // BN or number - if (isBigInt(it) || (typeof it === 'number' && Number.isInteger(it))) { + if (isBigInt(it) || Number.isInteger(it)) { return it.toString(); } - // string text - if (isText(it)) { - if (!isShortString(it as string)) - throw new Error( - `${it} is a long string > 31 chars, felt can store short strings, split it to array of short strings` - ); - const encoded = encodeShortString(it as string); - return BigInt(encoded).toString(); - } - // hex string - if (typeof it === 'string' && isHex(it)) { - // toBN().toString - return BigInt(it).toString(); - } - // string number (already converted), or unhandled type - if (typeof it === 'string' && isStringWholeNumber(it)) { - return it; + + // Handling strings + if (isString(it)) { + // Hex strings + if (isHex(it)) { + return BigInt(it).toString(); + } + // Text strings that must be short + if (isText(it)) { + if (!isShortString(it)) { + throw new Error( + `${it} is a long string > 31 chars. Please split it into an array of short strings.` + ); + } + // Assuming encodeShortString returns a hex representation of the string + return BigInt(encodeShortString(it)).toString(); + } + // Whole numeric strings + if (isStringWholeNumber(it)) { + return it; + } } // bool to felt - if (typeof it === 'boolean') { + if (isBoolean(it)) { return `${+it}`; } diff --git a/src/utils/calldata/propertyOrder.ts b/src/utils/calldata/propertyOrder.ts index d187e9472..8fc9916d3 100644 --- a/src/utils/calldata/propertyOrder.ts +++ b/src/utils/calldata/propertyOrder.ts @@ -22,6 +22,8 @@ import { } from './enum'; import extractTupleMemberTypes from './tuple'; +import { isString } from '../shortString'; + function errorU256(key: string) { return Error( `Your object includes the property : ${key}, containing an Uint256 object without the 'low' and 'high' keys.` @@ -92,7 +94,7 @@ export default function orderPropsByAbi( function orderArray(myArray: Array | string, abiParam: string): Array | string { const typeInArray = getArrayType(abiParam); - if (typeof myArray === 'string') { + if (isString(myArray)) { return myArray; // longstring } return myArray.map((myElem) => orderInput(myElem, typeInArray)); diff --git a/src/utils/calldata/requestParser.ts b/src/utils/calldata/requestParser.ts index 091f91b85..f7fb61382 100644 --- a/src/utils/calldata/requestParser.ts +++ b/src/utils/calldata/requestParser.ts @@ -9,7 +9,7 @@ import { Tupled, } from '../../types'; import { CairoUint256 } from '../cairoDataTypes/uint256'; -import { encodeShortString, isText, splitLongString } from '../shortString'; +import { encodeShortString, isString, isText, splitLongString } from '../shortString'; import { byteArrayFromString } from './byteArray'; import { felt, @@ -266,7 +266,7 @@ export function parseCalldataField( if (!Array.isArray(value) && !isText(value)) { throw Error(`ABI expected parameter ${name} to be array or long string, got ${value}`); } - if (typeof value === 'string') { + if (isString(value)) { // long string match cairo felt* value = splitLongString(value); } diff --git a/src/utils/calldata/validate.ts b/src/utils/calldata/validate.ts index cf0fcfc38..56b4e28fb 100644 --- a/src/utils/calldata/validate.ts +++ b/src/utils/calldata/validate.ts @@ -13,8 +13,8 @@ import { } from '../../types'; import assert from '../assert'; import { CairoUint256 } from '../cairoDataTypes/uint256'; -import { isHex, toBigInt } from '../num'; -import { isLongText } from '../shortString'; +import { isBigInt, isBoolean, isHex, isNumber, toBigInt } from '../num'; +import { isLongText, isString } from '../shortString'; import { getArrayType, isLen, @@ -34,10 +34,10 @@ import { const validateFelt = (parameter: any, input: AbiEntry) => { assert( - typeof parameter === 'string' || typeof parameter === 'number' || typeof parameter === 'bigint', + isString(parameter) || isNumber(parameter) || isBigInt(parameter), `Validate: arg ${input.name} should be a felt typed as (String, Number or BigInt)` ); - if (typeof parameter === 'string' && !isHex(parameter)) return; // shortstring + if (isString(parameter) && !isHex(parameter)) return; // shortstring const param = BigInt(parameter.toString(10)); assert( // from : https://github.com/starkware-libs/starknet-specs/blob/29bab650be6b1847c92d4461d4c33008b5e50b1a/api/starknet_api_openrpc.json#L1266 @@ -47,7 +47,7 @@ const validateFelt = (parameter: any, input: AbiEntry) => { }; const validateBytes31 = (parameter: any, input: AbiEntry) => { - assert(typeof parameter === 'string', `Validate: arg ${input.name} should be a string.`); + assert(isString(parameter), `Validate: arg ${input.name} should be a string.`); assert( parameter.length < 32, `Validate: arg ${input.name} cairo typed ${input.type} should be a string of less than 32 characters.` @@ -55,20 +55,20 @@ const validateBytes31 = (parameter: any, input: AbiEntry) => { }; const validateByteArray = (parameter: any, input: AbiEntry) => { - assert(typeof parameter === 'string', `Validate: arg ${input.name} should be a string.`); + assert(isString(parameter), `Validate: arg ${input.name} should be a string.`); }; const validateUint = (parameter: any, input: AbiEntry) => { - if (typeof parameter === 'number') { + if (isNumber(parameter)) { assert( parameter <= Number.MAX_SAFE_INTEGER, `Validation: Parameter is to large to be typed as Number use (BigInt or String)` ); } assert( - typeof parameter === 'string' || - typeof parameter === 'number' || - typeof parameter === 'bigint' || + isString(parameter) || + isNumber(parameter) || + isBigInt(parameter) || (typeof parameter === 'object' && 'low' in parameter && 'high' in parameter), `Validate: arg ${input.name} of cairo type ${ input.type @@ -142,7 +142,7 @@ const validateUint = (parameter: any, input: AbiEntry) => { const validateBool = (parameter: any, input: AbiEntry) => { assert( - typeof parameter === 'boolean', + isBoolean(parameter), `Validate: arg ${input.name} of cairo type ${input.type} should be type (Boolean)` ); }; diff --git a/src/utils/contract.ts b/src/utils/contract.ts index 7a39db24c..09d51301a 100644 --- a/src/utils/contract.ts +++ b/src/utils/contract.ts @@ -11,10 +11,12 @@ import { computeCompiledClassHash, computeContractClassHash } from './hash'; import { parse } from './json'; import { decompressProgram } from './stark'; +import { isString } from './shortString'; + export function isSierra( contract: CairoContract | string ): contract is SierraContractClass | CompiledSierra { - const compiledContract = typeof contract === 'string' ? parse(contract) : contract; + const compiledContract = isString(contract) ? parse(contract) : contract; return 'sierra_program' in compiledContract; } diff --git a/src/utils/hash/classHash.ts b/src/utils/hash/classHash.ts index 85836075d..ac9166eea 100644 --- a/src/utils/hash/classHash.ts +++ b/src/utils/hash/classHash.ts @@ -22,7 +22,7 @@ import { starkCurve } from '../ec'; import { addHexPrefix, utf8ToArray } from '../encode'; import { parse, stringify } from '../json'; import { toHex } from '../num'; -import { encodeShortString } from '../shortString'; +import { encodeShortString, isString } from '../shortString'; export function computePedersenHash(a: BigNumberish, b: BigNumberish): string { return starkCurve.pedersen(BigInt(a), BigInt(b)); @@ -125,8 +125,9 @@ export default function computeHintedClassHash(compiledContract: LegacyCompiledC * @returns format: hex-string */ export function computeLegacyContractClassHash(contract: LegacyCompiledContract | string) { - const compiledContract = - typeof contract === 'string' ? (parse(contract) as LegacyCompiledContract) : contract; + const compiledContract = isString(contract) + ? (parse(contract) as LegacyCompiledContract) + : contract; const apiVersion = toHex(API_VERSION); @@ -287,7 +288,7 @@ export function computeSierraContractClassHash(sierra: CompiledSierra) { * @returns format: hex-string */ export function computeContractClassHash(contract: CompiledContract | string) { - const compiledContract = typeof contract === 'string' ? parse(contract) : contract; + const compiledContract = isString(contract) ? parse(contract) : contract; if ('sierra_program' in compiledContract) { return computeSierraContractClassHash(compiledContract as CompiledSierra); diff --git a/src/utils/num.ts b/src/utils/num.ts index d3e9f9c45..544cb3bab 100644 --- a/src/utils/num.ts +++ b/src/utils/num.ts @@ -181,3 +181,23 @@ export function addPercent(number: BigNumberish, percent: number) { const bigIntNum = BigInt(number); return bigIntNum + (bigIntNum * BigInt(percent)) / 100n; } + +/** + * Check if a value is a number. + * + * @param {unknown} value - The value to check. + * @return {boolean} Returns true if the value is a number, otherwise returns false. + */ +export function isNumber(value: unknown): value is number { + return typeof value === 'number'; +} + +/** + * Checks if a given value is of boolean type. + * + * @param {unknown} value - The value to check. + * @return {boolean} - True if the value is of boolean type, false otherwise. + */ +export function isBoolean(value: unknown): value is boolean { + return typeof value === 'boolean'; +} diff --git a/src/utils/provider.ts b/src/utils/provider.ts index bd125b8df..59983e23d 100644 --- a/src/utils/provider.ts +++ b/src/utils/provider.ts @@ -21,8 +21,9 @@ import { ETransactionVersion } from '../types/api'; import { isSierra } from './contract'; import { formatSpaces } from './hash'; import { parse, stringify } from './json'; -import { isHex, toHex } from './num'; +import { isBigInt, isHex, isNumber, toHex } from './num'; import { compressProgram } from './stark'; +import { isString } from './shortString'; /** * Helper - Async Sleep for 'delay' time @@ -53,8 +54,7 @@ export function createSierraContractClass(contract: CompiledSierra): SierraContr * (CompiledContract or string) -> ContractClass */ export function parseContract(contract: CompiledContract | string): ContractClass { - const parsedContract = - typeof contract === 'string' ? (parse(contract) as CompiledContract) : contract; + const parsedContract = isString(contract) ? (parse(contract) as CompiledContract) : contract; if (!isSierra(contract)) { return { @@ -85,7 +85,7 @@ export const getDefaultNodeUrl = (networkName?: NetworkName, mute: boolean = fal * [Reference](https://github.com/starkware-libs/cairo-lang/blob/fc97bdd8322a7df043c87c371634b26c15ed6cee/src/starkware/starknet/services/api/feeder_gateway/feeder_gateway_client.py#L148-L153) */ export function formatHash(hashValue: BigNumberish): string { - if (typeof hashValue === 'string') return hashValue; + if (isString(hashValue)) return hashValue; return toHex(hashValue); } @@ -111,19 +111,17 @@ export class Block { tag: BlockIdentifier = null; private setIdentifier(__identifier: BlockIdentifier) { - if (typeof __identifier === 'string' && isHex(__identifier)) { - this.hash = __identifier; - } else if (typeof __identifier === 'bigint') { + if (isString(__identifier)) { + if (isHex(__identifier)) { + this.hash = __identifier; + } else if (validBlockTags.includes(__identifier as BlockTag)) { + this.tag = __identifier; + } + } else if (isBigInt(__identifier)) { this.hash = toHex(__identifier); - } else if (typeof __identifier === 'number') { + } else if (isNumber(__identifier)) { this.number = __identifier; - } else if ( - typeof __identifier === 'string' && - validBlockTags.includes(__identifier as BlockTag) - ) { - this.tag = __identifier; } else { - // default this.tag = BlockTag.pending; } } diff --git a/src/utils/responseParser/rpc.ts b/src/utils/responseParser/rpc.ts index 96c82387c..9ae3ce45d 100644 --- a/src/utils/responseParser/rpc.ts +++ b/src/utils/responseParser/rpc.ts @@ -19,6 +19,7 @@ import { import { toBigInt } from '../num'; import { estimateFeeToBounds, estimatedFeeToMaxFee } from '../stark'; import { ResponseParser } from '.'; +import { isString } from '../shortString'; export class RPCResponseParser implements @@ -57,7 +58,7 @@ export class RPCResponseParser public parseTransactionReceipt(res: TransactionReceipt): GetTransactionReceiptResponse { // HOTFIX RPC 0.5 to align with RPC 0.6 // This case is RPC 0.5. It can be only v2 thx with FRI units - if ('actual_fee' in res && typeof res.actual_fee === 'string') { + if ('actual_fee' in res && isString(res.actual_fee)) { return { ...(res as GetTransactionReceiptResponse), actual_fee: { @@ -113,7 +114,7 @@ export class RPCResponseParser public parseContractClassResponse(res: ContractClassPayload): ContractClassResponse { return { ...(res as ContractClassResponse), - abi: typeof res.abi === 'string' ? JSON.parse(res.abi) : res.abi, + abi: isString(res.abi) ? JSON.parse(res.abi) : res.abi, }; } } diff --git a/src/utils/shortString.ts b/src/utils/shortString.ts index bbcc27be6..bd4d8953a 100644 --- a/src/utils/shortString.ts +++ b/src/utils/shortString.ts @@ -24,11 +24,21 @@ export function isDecimalString(str: string): boolean { return /^[0-9]*$/i.test(str); } +/** + * Checks if a given value is a string. + * + * @param {unknown} value - The value to be checked. + * @return {boolean} - Returns true if the value is a string, false otherwise. + */ +export function isString(value: unknown): value is string { + return typeof value === 'string'; +} + /** * Test if value is a free-from string text, and not a hex string or number string */ export function isText(val: any) { - return typeof val === 'string' && !isHex(val) && !isStringWholeNumber(val); + return isString(val) && !isHex(val) && !isStringWholeNumber(val); } /** diff --git a/src/utils/stark.ts b/src/utils/stark.ts index ec14eb36c..65955fc6b 100644 --- a/src/utils/stark.ts +++ b/src/utils/stark.ts @@ -18,8 +18,10 @@ import { addPercent, bigNumberishArrayToDecimalStringArray, bigNumberishArrayToHexadecimalStringArray, + isBigInt, toHex, } from './num'; +import { isString } from './shortString'; /** * Compress compiled Cairo program @@ -28,7 +30,7 @@ import { * @param jsonProgram Representing the compiled cairo program */ export function compressProgram(jsonProgram: Program | string): CompressedProgram { - const stringified = typeof jsonProgram === 'string' ? jsonProgram : stringify(jsonProgram); + const stringified = isString(jsonProgram) ? jsonProgram : stringify(jsonProgram); const compressedProgram = gzip(stringified); return btoaUniversal(compressedProgram); } @@ -107,7 +109,7 @@ export function estimateFeeToBounds( amountOverhead: number = feeMarginPercentage.L1_BOUND_MAX_AMOUNT, priceOverhead: number = feeMarginPercentage.L1_BOUND_MAX_PRICE_PER_UNIT ): ResourceBounds { - if (typeof estimate === 'bigint') { + if (isBigInt(estimate)) { return { l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, l1_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, diff --git a/src/utils/typedData.ts b/src/utils/typedData.ts index 16413f0c2..1df8d393b 100644 --- a/src/utils/typedData.ts +++ b/src/utils/typedData.ts @@ -17,7 +17,7 @@ import { } from './hash'; import { MerkleTree } from './merkle'; import { isHex, toHex } from './num'; -import { encodeShortString } from './shortString'; +import { encodeShortString, isString } from './shortString'; /** @deprecated prefer importing from 'types' over 'typedData' */ export * from '../types/typedData'; @@ -79,7 +79,7 @@ function getHex(value: BigNumberish): string { try { return toHex(value); } catch (e) { - if (typeof value === 'string') { + if (isString(value)) { return toHex(encodeShortString(value)); } throw new Error(`Invalid BigNumberish: ${value}`);