diff --git a/__tests__/factories/abi.ts b/__tests__/factories/abi.ts new file mode 100644 index 000000000..84a6d42d3 --- /dev/null +++ b/__tests__/factories/abi.ts @@ -0,0 +1,47 @@ +import type { InterfaceAbi, AbiEntry, AbiEnums, AbiStructs, FunctionAbi } from '../../src'; + +export const getAbiEntry = (type: string): AbiEntry => ({ name: 'test', type }); + +export const getFunctionAbi = (inputsType: string): FunctionAbi => ({ + inputs: [getAbiEntry(inputsType)], + name: 'test', + outputs: [getAbiEntry(inputsType)], + stateMutability: 'view', + type: 'function', +}); + +export const getInterfaceAbi = (functionAbiType: string = 'struct'): InterfaceAbi => ({ + items: [getFunctionAbi(functionAbiType)], + name: 'test_interface_abi', + type: 'interface', +}); + +export const getAbiStructs = (): AbiStructs => ({ + struct: { + members: [ + { + name: 'test_name', + type: 'test_type', + offset: 1, + }, + ], + size: 2, + name: 'cairo__struct', + type: 'struct', + }, +}); + +export const getAbiEnums = (): AbiEnums => ({ + enum: { + variants: [ + { + name: 'test_name', + type: 'cairo_struct_variant', + offset: 1, + }, + ], + size: 2, + name: 'test_cairo', + type: 'enum', + }, +}); diff --git a/__tests__/utils/calldata/byteArray.test.ts b/__tests__/utils/calldata/byteArray.test.ts new file mode 100644 index 000000000..3cd5654c0 --- /dev/null +++ b/__tests__/utils/calldata/byteArray.test.ts @@ -0,0 +1,23 @@ +import { stringFromByteArray, byteArrayFromString } from '../../../src/utils/calldata/byteArray'; + +describe('stringFromByteArray', () => { + test('should return string from Cairo byte array', () => { + const str = stringFromByteArray({ + data: [], + pending_word: '0x414243444546474849', + pending_word_len: 9, + }); + expect(str).toEqual('ABCDEFGHI'); + }); +}); + +describe('byteArrayFromString', () => { + test('should return Cairo byte array from string', () => { + const byteArray = byteArrayFromString('ABCDEFGHI'); + expect(byteArray).toEqual({ + data: [], + pending_word: '0x414243444546474849', + pending_word_len: 9, + }); + }); +}); diff --git a/__tests__/utils/calldata/cairo.test.ts b/__tests__/utils/calldata/cairo.test.ts new file mode 100644 index 000000000..2c43cda3e --- /dev/null +++ b/__tests__/utils/calldata/cairo.test.ts @@ -0,0 +1,318 @@ +import { + isLen, + isTypeFelt, + isTypeUint, + isTypeUint256, + isTypeArray, + uint256, + uint512, + isTypeTuple, + isTypeNamedTuple, + isTypeStruct, + isTypeEnum, + isTypeOption, + isTypeResult, + isTypeLiteral, + isTypeBool, + isTypeContractAddress, + isTypeEthAddress, + isTypeBytes31, + isTypeByteArray, + isTypeSecp256k1Point, + isCairo1Type, + getArrayType, + isCairo1Abi, + isTypeNonZero, + getAbiContractVersion, + tuple, + felt, +} from '../../../src/utils/calldata/cairo'; +import { ETH_ADDRESS, Literal, Uint, type ContractVersion, NON_ZERO_PREFIX } from '../../../src'; +import { getFunctionAbi, getAbiEnums, getAbiStructs, getInterfaceAbi } from '../../factories/abi'; + +describe('isLen', () => { + test('should return true if name ends with "_len"', () => { + expect(isLen('test_len')).toEqual(true); + }); + + test('should return false if name does not end with "_len"', () => { + expect(isLen('test')).toEqual(false); + }); +}); + +describe('isTypeFelt', () => { + test('should return true if given type is Felt', () => { + expect(isTypeFelt('felt')).toEqual(true); + expect(isTypeFelt('core::felt252')).toEqual(true); + }); + + test('should return false if given type is not Felt', () => { + expect(isTypeFelt('core::bool')).toEqual(false); + }); +}); + +describe('isTypeArray', () => { + test('should return true if given type is an Array', () => { + expect(isTypeArray('core::array::Array::')).toEqual(true); + expect(isTypeArray('core::array::Span::')).toEqual(true); + expect(isTypeArray('felt*')).toEqual(true); + }); + + test('should return false if given type is not an Array ', () => { + expect(isTypeArray('core::bool')).toEqual(false); + }); +}); + +describe('isTypeTuple', () => { + test('should return true if given type is Tuple', () => { + expect(isTypeTuple('(core::bool, felt)')).toEqual(true); + }); + + test('should return false if given type is not Tuple ', () => { + expect(isTypeTuple('core::bool')).toEqual(false); + }); +}); + +describe('isTypeNamedTuple', () => { + test('should return true if given type is named Tuple', () => { + expect(isTypeNamedTuple('(core::bool, core::bool)')).toEqual(true); + expect(isTypeNamedTuple('(core::bool, felt)')).toEqual(true); + }); + + test('should return false if given type is not named Tuple ', () => { + expect(isTypeNamedTuple('(felt, felt)')).toEqual(false); + }); +}); + +describe('isTypeStruct', () => { + test('should return true if given type is Struct', () => { + expect(isTypeStruct('struct', getAbiStructs())).toEqual(true); + }); + + test('should return false if given type is not Struct', () => { + expect(isTypeStruct('struct', { test: getAbiStructs().struct })).toEqual(false); + }); +}); + +describe('isTypeEnum', () => { + test('should return true if given type is Enum', () => { + expect(isTypeEnum('enum', getAbiEnums())).toEqual(true); + }); + + test('should return false if given type is not Enum', () => { + expect(isTypeEnum('enum', { test: getAbiEnums().enum })).toEqual(false); + }); +}); + +describe('isTypeOption', () => { + test('should return true if given type is Option', () => { + expect(isTypeOption('core::option::Option::core::bool')).toEqual(true); + }); + + test('should return false if given type is not Option', () => { + expect(isTypeOption('core::bool')).toEqual(false); + }); +}); + +describe('isTypeResult', () => { + test('should return true if given type is Result', () => { + expect(isTypeResult('core::result::Result::core::bool')).toEqual(true); + }); + + test('should return false if given type is not Result', () => { + expect(isTypeResult('core::bool')).toEqual(false); + }); +}); + +describe('isTypeUint', () => { + test('should return true if given type is Uint', () => { + Object.values(Uint).forEach((uint) => { + expect(isTypeUint(uint)).toEqual(true); + }); + }); + + test('should return false if given type is not Uint', () => { + expect(isTypeUint('core::bool')).toEqual(false); + }); +}); + +describe('isTypeUint256', () => { + test('should return true if given type is Uint256', () => { + expect(isTypeUint256('core::integer::u256')).toEqual(true); + }); + + test('should return false if given type is not Uint256', () => { + expect(isTypeUint256('core::bool')).toEqual(false); + }); +}); + +describe('isTypeLiteral', () => { + test('should return true if given type is Literal', () => { + Object.values(Literal).forEach((literal) => { + expect(isTypeLiteral(literal)).toEqual(true); + }); + }); + + test('should return false if given type is not Literal', () => { + expect(isTypeLiteral('core::bool')).toEqual(false); + }); +}); + +describe('isTypeBool', () => { + test('should return true if given type is Bool', () => { + expect(isTypeBool('core::bool')).toEqual(true); + }); + + test('should return false if given type is not Bool', () => { + expect(isTypeBool(Uint.u8)).toEqual(false); + }); +}); + +describe('isTypeContractAddress', () => { + test('should return true if given type is ContractAddress', () => { + expect(isTypeContractAddress(Literal.ContractAddress)).toEqual(true); + }); + + test('should return false if given type is not ContractAddress', () => { + expect(isTypeContractAddress(Uint.u8)).toEqual(false); + }); +}); + +describe('isTypeEthAddress', () => { + test('should return true if given type is EthAddress', () => { + expect(isTypeEthAddress(ETH_ADDRESS)).toEqual(true); + }); + + test('should return false if given type is not EthAddress', () => { + expect(isTypeEthAddress(Literal.ContractAddress)).toEqual(false); + }); +}); + +describe('isTypeBytes31', () => { + test('should return true if given type is Bytes31', () => { + expect(isTypeBytes31('core::bytes_31::bytes31')).toEqual(true); + }); + + test('should return false if given type is not Bytes31', () => { + expect(isTypeBytes31('core::bool')).toEqual(false); + }); +}); + +describe('isTypeByteArray', () => { + test('should return true if given type is ByteArray', () => { + expect(isTypeByteArray('core::byte_array::ByteArray')).toEqual(true); + }); + + test('should return false if given type is not ByteArray', () => { + expect(isTypeByteArray('core::bool')).toEqual(false); + }); +}); + +describe('isTypeSecp256k1Point', () => { + test('should return true if given type is Secp256k1Point', () => { + expect(isTypeSecp256k1Point(Literal.Secp256k1Point)).toEqual(true); + }); + + test('should return false if given type is not Secp256k1Point', () => { + expect(isTypeSecp256k1Point('core::bool')).toEqual(false); + }); +}); + +describe('isCairo1Type', () => { + test('should return true if given type is Cairo1', () => { + expect(isCairo1Type('core::bool')).toEqual(true); + }); + + test('should return false if given type is not Cairo1', () => { + expect(isCairo1Type('felt')).toEqual(false); + }); +}); + +describe('getArrayType', () => { + test('should extract type from an array', () => { + expect(getArrayType('felt*')).toEqual('felt'); + expect(getArrayType('core::array::Array::')).toEqual('core::bool'); + }); +}); + +describe('isTypeNonZero', () => { + test('should return true if given type is NonZero', () => { + expect(isTypeNonZero(`${NON_ZERO_PREFIX}core::bool`)).toEqual(true); + }); + + test('should return false if given type is not NonZero', () => { + expect(isTypeNonZero('core::bool')).toEqual(false); + }); +}); + +describe('isCairo1Abi', () => { + test('should return true if ABI comes from Cairo 1 contract', () => { + expect(isCairo1Abi([getInterfaceAbi()])).toEqual(true); + }); + + test('should return false if ABI comes from Cairo 0 contract', () => { + expect(isCairo1Abi([getFunctionAbi('felt')])).toEqual(false); + }); + + test('should throw an error if ABI does not come from Cairo 1 contract ', () => { + expect(() => isCairo1Abi([{}])).toThrow(new Error('Unable to determine Cairo version')); + }); +}); + +describe('getAbiContractVersion', () => { + test('should return Cairo 0 contract version', () => { + const contractVersion: ContractVersion = getAbiContractVersion([getFunctionAbi('felt')]); + expect(contractVersion).toEqual({ cairo: '0', compiler: '0' }); + }); + + test('should return Cairo 1 with compiler 2 contract version', () => { + const contractVersion: ContractVersion = getAbiContractVersion([getInterfaceAbi()]); + expect(contractVersion).toEqual({ cairo: '1', compiler: '2' }); + }); + + test('should return Cairo 1 with compiler 1 contract version', () => { + const contractVersion: ContractVersion = getAbiContractVersion([getFunctionAbi('core::bool')]); + expect(contractVersion).toEqual({ cairo: '1', compiler: '1' }); + }); + + test('should return undefined values for cairo and compiler', () => { + const contractVersion: ContractVersion = getAbiContractVersion([{}]); + expect(contractVersion).toEqual({ cairo: undefined, compiler: undefined }); + }); +}); + +describe('uint256', () => { + test('should create Uint256 Cairo type', () => { + const uint = uint256('892349863487563453485768723498'); + expect(uint).toEqual({ low: '892349863487563453485768723498', high: '0' }); + }); +}); + +describe('uint512', () => { + test('should create Uint512 Cairo type', () => { + const uint = uint512('345745685892349863487563453485768723498'); + expect(uint).toEqual({ + limb0: '5463318971411400024188846054000512042', + limb1: '1', + limb2: '0', + limb3: '0', + }); + }); +}); + +describe('tuple', () => { + test('should create unnamed Cairo type tuples', () => { + const tuples = [tuple(true, false), tuple(1, '0x101', 16)]; + expect(tuples).toEqual([ + { '0': true, '1': false }, + { '0': 1, '1': '0x101', '2': 16 }, + ]); + }); +}); + +describe('felt', () => { + test('should create Cairo type felts', () => { + const felts = [felt('test'), felt(256n), felt(1234)]; + expect(felts).toEqual(['1952805748', '256', '1234']); + }); +}); diff --git a/__tests__/utils/calldata/enum/CairoCustomEnum.test.ts b/__tests__/utils/calldata/enum/CairoCustomEnum.test.ts new file mode 100644 index 000000000..154e5bed2 --- /dev/null +++ b/__tests__/utils/calldata/enum/CairoCustomEnum.test.ts @@ -0,0 +1,35 @@ +import { CairoCustomEnum } from '../../../../src/utils/calldata/enum'; + +describe('CairoCustomEnum', () => { + describe('constructor', () => { + test('should set "variant" if enum content is provided', () => { + const cairoCustomEnum = new CairoCustomEnum({ test: 'custom_enum' }); + expect(cairoCustomEnum.variant).toEqual({ test: 'custom_enum' }); + }); + + test('should throw an error if enum does not have any variant', () => { + const error = new Error('This Enum must have at least 1 variant'); + expect(() => new CairoCustomEnum({})).toThrow(error); + }); + + test('should throw an error if there is more then one active variant', () => { + const content = { test: 'custom_enum', test2: 'custom_enum_2' }; + const error = new Error('This Enum must have exactly one active variant'); + expect(() => new CairoCustomEnum(content)).toThrow(error); + }); + }); + + describe('unwrap', () => { + test('should return content of the valid variant', () => { + const cairoCustomEnum = new CairoCustomEnum({ test: undefined, test2: 'test_2' }); + expect(cairoCustomEnum.unwrap()).toEqual('test_2'); + }); + }); + + describe('activeVariant', () => { + test('should return the name of the valid variant', () => { + const cairoCustomEnum = new CairoCustomEnum({ test: undefined, test2: 'test_2' }); + expect(cairoCustomEnum.activeVariant()).toEqual('test2'); + }); + }); +}); diff --git a/__tests__/utils/calldata/enum/CairoOption.test.ts b/__tests__/utils/calldata/enum/CairoOption.test.ts new file mode 100644 index 000000000..b1dfaff76 --- /dev/null +++ b/__tests__/utils/calldata/enum/CairoOption.test.ts @@ -0,0 +1,55 @@ +import { CairoOption } from '../../../../src/utils/calldata/enum'; + +describe('CairoOption', () => { + describe('constructor', () => { + test('should set "Some" if variant is 0', () => { + const cairoOption = new CairoOption(0, 'option_content'); + expect(cairoOption.Some).toEqual('option_content'); + expect(cairoOption.None).toBeUndefined(); + }); + + test('should set "None" if variant is 1', () => { + const cairoOption = new CairoOption(1, 'option_content'); + expect(cairoOption.None).toEqual(true); + expect(cairoOption.Some).toBeUndefined(); + }); + + test('should throw an error if wrong variant is provided', () => { + expect(() => new CairoOption(2, 'option_content')).toThrow( + new Error('Wrong variant! It should be CairoOptionVariant.Some or .None.') + ); + }); + + test('should throw an error if content is undefined or not provided', () => { + expect(() => new CairoOption(0)).toThrow( + new Error('The creation of a Cairo Option with "Some" variant needs a content as input.') + ); + }); + }); + + describe('unwrap', () => { + test('should return undefined if "None" value is set', () => { + const cairoOption = new CairoOption(1, 'option_content'); + expect(cairoOption.unwrap()).toBeUndefined(); + }); + + test('should return "Some" value if it is set', () => { + const cairoOption = new CairoOption(0, 'option_content'); + expect(cairoOption.unwrap()).toEqual('option_content'); + }); + }); + + describe('isSome', () => { + test('should return true if "Some" value is set', () => { + const cairoOption = new CairoOption(0, 'option_content'); + expect(cairoOption.isSome()).toEqual(true); + }); + }); + + describe('isNone', () => { + test('should return true if "None" value is set', () => { + const cairoOption = new CairoOption(1, 'option_content'); + expect(cairoOption.isNone()).toEqual(true); + }); + }); +}); diff --git a/__tests__/utils/calldata/enum/CairoResult.test.ts b/__tests__/utils/calldata/enum/CairoResult.test.ts new file mode 100644 index 000000000..a039e5d28 --- /dev/null +++ b/__tests__/utils/calldata/enum/CairoResult.test.ts @@ -0,0 +1,49 @@ +import { CairoResult } from '../../../../src/utils/calldata/enum'; + +describe('CairoResult', () => { + describe('constructor', () => { + test('should set "Ok" if variant is 0', () => { + const cairoResult = new CairoResult(0, 'result_content'); + expect(cairoResult.Ok).toEqual('result_content'); + expect(cairoResult.Err).toBeUndefined(); + }); + + test('should set "Err" if variant is 1', () => { + const cairoResult = new CairoResult(1, 'result_content'); + expect(cairoResult.Err).toEqual('result_content'); + expect(cairoResult.Ok).toBeUndefined(); + }); + + test('should throw an error if wrong variant is provided', () => { + expect(() => new CairoResult(2, 'result_content')).toThrow( + new Error('Wrong variant! It should be CairoResultVariant.Ok or .Err.') + ); + }); + }); + + describe('unwrap', () => { + test('should return "Ok" value', () => { + const cairoResult = new CairoResult(0, 'result_content'); + expect(cairoResult.unwrap()).toEqual('result_content'); + }); + + test('should return "Err" value', () => { + const cairoResult = new CairoResult(1, 'result_content'); + expect(cairoResult.unwrap()).toEqual('result_content'); + }); + }); + + describe('isOk', () => { + test('should return true if "Ok" value is set', () => { + const cairoResult = new CairoResult(0, 'result_content'); + expect(cairoResult.isOk()).toEqual(true); + }); + }); + + describe('isErr', () => { + test('should return true if "Err" value is set', () => { + const cairoResult = new CairoResult(1, 'result_content'); + expect(cairoResult.isErr()).toEqual(true); + }); + }); +}); diff --git a/__tests__/utils/calldata/formatter.test.ts b/__tests__/utils/calldata/formatter.test.ts new file mode 100644 index 000000000..a44cabe44 --- /dev/null +++ b/__tests__/utils/calldata/formatter.test.ts @@ -0,0 +1,43 @@ +import formatter from '../../../src/utils/calldata/formatter'; +import { toBigInt } from '../../../src/utils/num'; + +describe('formatter', () => { + test('should format one level depth object', () => { + const data = { value: toBigInt(1000), name: toBigInt(1) }; + const type = { value: 'number', name: 'string' }; + const formatted = formatter(data, type); + expect(formatted).toEqual({ value: 1000, name: '1' }); + }); + + test('should format nested object', () => { + const data = { test: { id: toBigInt(123), value: toBigInt(30) }, active: toBigInt(1) }; + const type = { test: { id: 'number', value: 'number' }, active: 'number' }; + const formatted = formatter(data, type); + expect(formatted).toEqual({ test: { id: 123, value: 30 }, active: 1 }); + }); + + test('should format object that has arrays in it', () => { + const data = { items: [toBigInt(1), toBigInt(2), toBigInt(3)], value: toBigInt(1) }; + const type = { items: ['number'], value: 'string' }; + const formatted = formatter(data, type); + expect(formatted).toEqual({ items: [1, 2, 3], value: '1' }); + }); + + test('should throw an error if at least one of the value is not Big Int', () => { + const data = { value: '123', name: toBigInt(1) }; + const type = { value: 'number', name: 'string' }; + expect(() => formatter(data, type)).toThrow( + new Error( + 'Data and formatter mismatch on value:number, expected response data value:123 to be BN instead it is string' + ) + ); + }); + + test('should throw an error for unhandled formatter types', () => { + const data = { value: toBigInt(1) }; + const type = { value: 'symbol' }; + expect(() => formatter(data, type)).toThrow( + new Error('Unhandled formatter type on value:symbol for data value:1') + ); + }); +}); diff --git a/__tests__/utils/calldata/parser/parser-0-1.1.0.test.ts b/__tests__/utils/calldata/parser/parser-0-1.1.0.test.ts new file mode 100644 index 000000000..5a33dfd8f --- /dev/null +++ b/__tests__/utils/calldata/parser/parser-0-1.1.0.test.ts @@ -0,0 +1,44 @@ +import { AbiParser1 } from '../../../../src/utils/calldata/parser/parser-0-1.1.0'; +import { getFunctionAbi, getInterfaceAbi } from '../../../factories/abi'; + +describe('AbiParser1', () => { + test('should create an instance', () => { + const abiParser = new AbiParser1([getFunctionAbi('struct')]); + expect(abiParser instanceof AbiParser1).toEqual(true); + expect(abiParser.abi).toStrictEqual([getFunctionAbi('struct')]); + }); + + describe('methodInputsLength', () => { + test('should return inputs length', () => { + const abiParser = new AbiParser1([getFunctionAbi('struct')]); + expect(abiParser.methodInputsLength(getFunctionAbi('felt'))).toEqual(1); + }); + + test('should return 0 if inputs are empty', () => { + const abiParser = new AbiParser1([getFunctionAbi('felt')]); + const functionAbi = getFunctionAbi('felt'); + functionAbi.inputs[0].name = 'test_len'; + expect(abiParser.methodInputsLength(functionAbi)).toEqual(0); + }); + }); + + describe('getMethod', () => { + test('should return method definition from ABI', () => { + const abiParser = new AbiParser1([getFunctionAbi('struct'), getInterfaceAbi()]); + expect(abiParser.getMethod('test')).toEqual(getFunctionAbi('struct')); + }); + + test('should return undefined if method is not found', () => { + const abiParser = new AbiParser1([getFunctionAbi('struct')]); + expect(abiParser.getMethod('struct')).toBeUndefined(); + }); + }); + + describe('getLegacyFormat', () => { + test('should return method definition from ABI', () => { + const abiParser = new AbiParser1([getFunctionAbi('struct'), getInterfaceAbi()]); + const legacyFormat = abiParser.getLegacyFormat(); + expect(legacyFormat).toStrictEqual(abiParser.abi); + }); + }); +}); diff --git a/__tests__/utils/calldata/parser/parser-2.0.0.test.ts b/__tests__/utils/calldata/parser/parser-2.0.0.test.ts new file mode 100644 index 000000000..d49f0f5c8 --- /dev/null +++ b/__tests__/utils/calldata/parser/parser-2.0.0.test.ts @@ -0,0 +1,45 @@ +import { AbiParser2 } from '../../../../src/utils/calldata/parser/parser-2.0.0'; +import { getFunctionAbi, getInterfaceAbi } from '../../../factories/abi'; + +describe('AbiParser2', () => { + test('should create an instance', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct')]); + expect(abiParser instanceof AbiParser2).toEqual(true); + expect(abiParser.abi).toStrictEqual([getFunctionAbi('struct')]); + }); + + describe('methodInputsLength', () => { + test('should return inputs length', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct')]); + expect(abiParser.methodInputsLength(getFunctionAbi('test'))).toEqual(1); + }); + + test('should return 0 if inputs are empty', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct')]); + const functionAbi = getFunctionAbi('test'); + functionAbi.inputs = []; + expect(abiParser.methodInputsLength(functionAbi)).toEqual(0); + }); + }); + + describe('getMethod', () => { + test('should return method definition from ABI', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct'), getInterfaceAbi()]); + expect(abiParser.getMethod('test')).toEqual(getFunctionAbi('struct')); + }); + + test('should return undefined if method is not found', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct')]); + expect(abiParser.getMethod('test')).toBeUndefined(); + }); + }); + + describe('getLegacyFormat', () => { + test('should return method definition from ABI', () => { + const abiParser = new AbiParser2([getFunctionAbi('struct'), getInterfaceAbi()]); + const legacyFormat = abiParser.getLegacyFormat(); + const result = [getFunctionAbi('struct'), getFunctionAbi('struct')]; + expect(legacyFormat).toEqual(result); + }); + }); +}); diff --git a/__tests__/utils/calldata/parser/parser.test.ts b/__tests__/utils/calldata/parser/parser.test.ts new file mode 100644 index 000000000..03674447c --- /dev/null +++ b/__tests__/utils/calldata/parser/parser.test.ts @@ -0,0 +1,44 @@ +import { + createAbiParser, + getAbiVersion, + isNoConstructorValid, +} from '../../../../src/utils/calldata/parser'; +import { AbiParser2 } from '../../../../src/utils/calldata/parser/parser-2.0.0'; +import { AbiParser1 } from '../../../../src/utils/calldata/parser/parser-0-1.1.0'; +import { getFunctionAbi, getInterfaceAbi } from '../../../factories/abi'; + +describe('createAbiParser', () => { + test('should create an AbiParser2 instance', () => { + const abiParser = createAbiParser([getInterfaceAbi()]); + expect(abiParser instanceof AbiParser2).toEqual(true); + }); + + test('should create an AbiParser1 instance', () => { + const abiParser = createAbiParser([getFunctionAbi('struct')]); + expect(abiParser instanceof AbiParser1).toEqual(true); + }); +}); + +describe('getAbiVersion', () => { + test('should return ABI version 2', () => { + expect(getAbiVersion([getInterfaceAbi()])).toEqual(2); + }); + + test('should return ABI version 1', () => { + expect(getAbiVersion([getFunctionAbi('core::bool')])).toEqual(1); + }); + + test('should return ABI version 0', () => { + expect(getAbiVersion([getFunctionAbi('felt')])).toEqual(0); + }); +}); + +describe('isNoConstructorValid', () => { + test('should return true if no constructor valid', () => { + expect(isNoConstructorValid('constructor', [])).toEqual(true); + }); + + test('should return false if constructor valid', () => { + expect(isNoConstructorValid('test', ['test'])).toEqual(false); + }); +}); diff --git a/__tests__/utils/calldata/tuple.test.ts b/__tests__/utils/calldata/tuple.test.ts new file mode 100644 index 000000000..5e2511560 --- /dev/null +++ b/__tests__/utils/calldata/tuple.test.ts @@ -0,0 +1,15 @@ +import extractTupleMemberTypes from '../../../src/utils/calldata/tuple'; + +describe('extractTupleMemberTypes', () => { + test('should return tuple member types for Cairo0', () => { + const tuple = '(u8, u8)'; + const result = extractTupleMemberTypes(tuple); + expect(result).toEqual(['u8', 'u8']); + }); + + test('should return tuple member types for Cairo1', () => { + const tuple = '(core::result::Result::, u8)'; + const result = extractTupleMemberTypes(tuple); + expect(result).toEqual(['core::result::Result::', 'u8']); + }); +}); diff --git a/__tests__/utils/calldata/validate.test.ts b/__tests__/utils/calldata/validate.test.ts new file mode 100644 index 000000000..3110e4d3e --- /dev/null +++ b/__tests__/utils/calldata/validate.test.ts @@ -0,0 +1,721 @@ +import validateFields from '../../../src/utils/calldata/validate'; +import { + CairoOption, + CairoResult, + ETH_ADDRESS, + Literal, + NON_ZERO_PREFIX, + Uint, +} from '../../../src'; +import { getFunctionAbi, getAbiEnums, getAbiStructs } from '../../factories/abi'; + +describe('validateFields', () => { + test('should throw an error if validation is unhandled', () => { + expect(() => { + validateFields(getFunctionAbi('test_test'), [true], getAbiStructs(), getAbiEnums()); + }).toThrow(new Error('Validate Unhandled: argument test, type test_test, value true')); + }); + + describe('felt validation', () => { + test('should return void if felt validation passes', () => { + const result = validateFields( + getFunctionAbi('felt'), + ['test'], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if felt is not the type of string, number or big int', () => { + const validateFelt = (params: unknown[]) => + validateFields(getFunctionAbi('felt'), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + 'Validate: arg test should be a felt typed as (String, Number or BigInt)' + ); + expect(() => validateFelt([{}])).toThrow(error); + expect(() => validateFelt([new Map()])).toThrow(error); + expect(() => validateFelt([true])).toThrow(error); + expect(() => validateFelt([])).toThrow(error); + expect(() => validateFelt([Symbol('test')])).toThrow(error); + }); + + test('should throw an error if felt is not in the range', () => { + const validateFelt = (params: unknown[]) => + validateFields(getFunctionAbi('felt'), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + 'Validate: arg test cairo typed felt should be in range [0, 2^252-1]' + ); + expect(() => validateFelt([-1])).toThrow(error); + expect(() => validateFelt([2n ** 252n])).toThrow(error); + }); + }); + + describe('bytes31 validation', () => { + test('should return void if bytes31 validation passes', () => { + const result = validateFields( + getFunctionAbi('core::bytes_31::bytes31'), + ['test'], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if parameter is not the type of string', () => { + const validateBytes31 = (params: unknown[]) => + validateFields( + getFunctionAbi('core::bytes_31::bytes31'), + params, + getAbiStructs(), + getAbiEnums() + ); + + const error = new Error('Validate: arg test should be a string.'); + + expect(() => validateBytes31([0, BigInt(22), new Map(), true, Symbol('test')])).toThrow( + error + ); + }); + + test('should throw an error if parameter is less than 32 chars', () => { + const validateBytes31 = (params: unknown[]) => + validateFields( + getFunctionAbi('core::bytes_31::bytes31'), + params, + getAbiStructs(), + getAbiEnums() + ); + + const error = new Error( + 'Validate: arg test cairo typed core::bytes_31::bytes31 should be a string of less than 32 characters.' + ); + expect(() => validateBytes31(['String_that_is_bigger_than_32_characters'])).toThrow(error); + }); + }); + + describe('Uint validation', () => { + test('should return void if Uint "u8" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u8), + [255n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u16" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u16), + [65535n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u32" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u32), + [4294967295n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u64" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u64), + [2n ** 64n - 1n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u128" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u128), + [2n ** 128n - 1n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u256" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u256), + [2n ** 256n - 1n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should return void if Uint "u512" validation passes', () => { + const result = validateFields( + getFunctionAbi(Uint.u512), + [2n ** 512n - 1n], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if parameter is too large', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u8), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + 'Validation: Parameter is too large to be typed as Number use (BigInt or String)' + ); + + expect(() => validateUint([Number.MAX_SAFE_INTEGER + 1])).toThrow(error); + }); + + test('should throw an error if parameter type is not valid', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u8), params, getAbiStructs(), getAbiEnums()); + + const getError = (param: any) => + new Error( + `Validate: arg test of cairo type ${Uint.u8} should be type (String, Number or BigInt), but is ${typeof param} ${param}.` + ); + + expect(() => validateUint([new Map()])).toThrow(getError(new Map())); + expect(() => validateUint([true])).toThrow(getError(true)); + expect(() => validateUint([{ test: 'test' }])).toThrow(getError({ test: 'test' })); + }); + + test('should throw an error if Uint "u8" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u8), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Uint.u8} should be in range [0 - 255]` + ); + + expect(() => validateUint([-1])).toThrow(error); + expect(() => validateUint([256n])).toThrow(error); + }); + + test('should throw an error if Uint "u16" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u16), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Uint.u16} should be in range [0, 65535]` + ); + + expect(() => validateUint([65536n])).toThrow(error); + }); + + test('should throw an error if Uint "u32" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u32), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Uint.u32} should be in range [0, 4294967295]` + ); + + expect(() => validateUint([4294967296n])).toThrow(error); + }); + + test('should throw an error if Uint "u64" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u64), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Uint.u64} should be in range [0, 2^64-1]` + ); + + expect(() => validateUint([2n ** 64n])).toThrow(error); + }); + + test('should throw an error if Uint "u128" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u128), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Uint.u128} should be in range [0, 2^128-1]` + ); + + expect(() => validateUint([2n ** 128n])).toThrow(error); + }); + + test('should throw an error if Uint "u256" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u256), params, getAbiStructs(), getAbiEnums()); + + const error = new Error('bigNumberish is bigger than UINT_256_MAX'); + + expect(() => validateUint([2n ** 256n])).toThrow(error); + }); + + test('should throw an error if Uint "u512" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Uint.u512), params, getAbiStructs(), getAbiEnums()); + + const error = new Error('bigNumberish is bigger than UINT_512_MAX.'); + + expect(() => validateUint([2n ** 512n])).toThrow(error); + }); + + test('should throw an error if "Literal.ClassHash" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields(getFunctionAbi(Literal.ClassHash), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test cairo typed ${Literal.ClassHash} should be in range [0, 2^252-1]` + ); + + expect(() => validateUint([2n ** 252n])).toThrow(error); + }); + + test('should throw an error if "Literal.ContractAddress" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields( + getFunctionAbi(Literal.ContractAddress), + params, + getAbiStructs(), + getAbiEnums() + ); + + const error = new Error( + `Validate: arg test cairo typed ${Literal.ContractAddress} should be in range [0, 2^252-1]` + ); + + expect(() => validateUint([2n ** 252n])).toThrow(error); + }); + + test('should throw an error if "Literal.Secp256k1Point" is not in range', () => { + const validateUint = (params: unknown[]) => + validateFields( + getFunctionAbi(Literal.Secp256k1Point), + params, + getAbiStructs(), + getAbiEnums() + ); + + const error = new Error( + `Validate: arg test must be ${Literal.Secp256k1Point} : a 512 bits number.` + ); + + expect(() => validateUint([2n ** 512n])).toThrow(error); + }); + }); + + describe('Boolean validation', () => { + test('should return void if boolean validation passes', () => { + const result = validateFields( + getFunctionAbi('core::bool'), + [true], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if boolean validation fails', () => { + const validateBool = (params: unknown[]) => + validateFields(getFunctionAbi('core::bool'), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test of cairo type core::bool should be type (Boolean)` + ); + + expect(() => validateBool(['bool', 22, Symbol('test'), BigInt(2)])).toThrow(error); + }); + }); + + describe('Boolean validation', () => { + test('should return void if boolean validation passes', () => { + const result = validateFields( + getFunctionAbi('core::bool'), + [true], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if boolean validation fails', () => { + const validateBool = (params: unknown[]) => + validateFields(getFunctionAbi('core::bool'), params, getAbiStructs(), getAbiEnums()); + + const error = new Error( + `Validate: arg test of cairo type core::bool should be type (Boolean)` + ); + + expect(() => validateBool(['bool'])).toThrow(error); + }); + }); + + describe('ByteArray validation', () => { + test('should return void if byte array validation passes', () => { + const result = validateFields( + getFunctionAbi('core::byte_array::ByteArray'), + ['byte_array'], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if byte array validation fails', () => { + const validateByteArray = (params: unknown[]) => + validateFields( + getFunctionAbi('core::byte_array::ByteArray'), + params, + getAbiStructs(), + getAbiEnums() + ); + + const error = new Error(`Validate: arg test should be a string.`); + + expect(() => validateByteArray([false, 0, {}, new Map(), Symbol('test')])).toThrow(error); + }); + }); + + describe('Tuple validation', () => { + test('should return void if tuple validation passes', () => { + const result = validateFields( + getFunctionAbi('(core::bool, core::bool)'), + [{ min: true, max: true }], + getAbiStructs(), + getAbiEnums() + ); + expect(result).toBeUndefined(); + }); + + test('should throw an error if tupple validation fails', () => { + const error = new Error(`Validate: arg test should be a tuple (defined as object)`); + + expect(() => + validateFields( + getFunctionAbi('(core::bool, core::bool)'), + [], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + }); + + describe('Struct validation', () => { + test('should return void if struct validation passes for common struct', () => { + const result = validateFields( + getFunctionAbi('struct'), + [{ test_name: 'test' }], + getAbiStructs(), + getAbiEnums() + ); + + expect(result).toBeUndefined(); + }); + + test('should return void if struct validation passes for Uint 256 or 512', () => { + const abiStructs256 = { + [Uint.u256]: getAbiStructs().struct, + }; + const result256 = validateFields( + getFunctionAbi(Uint.u256), + [2n ** 256n - 1n], + abiStructs256, + getAbiEnums() + ); + + const abiStructs512 = { + [Uint.u512]: getAbiStructs().struct, + }; + const result512 = validateFields( + getFunctionAbi(Uint.u512), + [2n ** 512n - 1n], + abiStructs512, + getAbiEnums() + ); + + expect(result256).toBeUndefined(); + expect(result512).toBeUndefined(); + }); + + test('should return void if struct validation passes for EthAddress', () => { + const abiStructs = { + [ETH_ADDRESS]: getAbiStructs().struct, + }; + const result = validateFields(getFunctionAbi(ETH_ADDRESS), [1n], abiStructs, getAbiEnums()); + + expect(result).toBeUndefined(); + }); + + test('should throw an error for EthAddress struct if type is not a BigNumberish', () => { + const error = new Error('EthAddress type is waiting a BigNumberish. Got "[object Object]"'); + + expect(() => { + const abiStructs = { + [ETH_ADDRESS]: getAbiStructs().struct, + }; + + validateFields(getFunctionAbi(ETH_ADDRESS), [{ test: 1 }], abiStructs, getAbiEnums()); + }).toThrow(error); + }); + + test('should throw an error for EthAddress struct if it is not in range', () => { + const error = new Error( + `Validate: arg test cairo typed ${ETH_ADDRESS} should be in range [0, 2^160-1]` + ); + + expect(() => { + const abiStructs = { + [ETH_ADDRESS]: getAbiStructs().struct, + }; + + validateFields(getFunctionAbi(ETH_ADDRESS), [2n ** 160n], abiStructs, getAbiEnums()); + }).toThrow(error); + }); + + test('should throw an error if arg is not an JS object', () => { + const error = new Error( + 'Validate: arg test is cairo type struct (struct), and should be defined as a js object (not array)' + ); + + expect(() => + validateFields(getFunctionAbi('struct'), [2], getAbiStructs(), getAbiEnums()) + ).toThrow(error); + }); + + test('should throw an error if arg property name does not exist in the struct members', () => { + const error = new Error('Validate: arg test should have a property test_name'); + + expect(() => + validateFields( + getFunctionAbi('struct'), + [{ example: 'test' }], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + }); + + describe('Enum validation', () => { + test('should return void if enum validation passes for custom enum', () => { + const result = validateFields( + getFunctionAbi('enum'), + [{ variant: 'test', activeVariant: 'test' }], + getAbiStructs(), + getAbiEnums() + ); + + expect(result).toBeUndefined(); + }); + + test('should return void if enum validation passes for type option', () => { + const enumOption = 'core::option::Option::core::bool'; + + const abiEnums = { + [enumOption]: getAbiEnums().enum, + }; + const result = validateFields( + getFunctionAbi(enumOption), + [new CairoOption(0, 'content')], + getAbiStructs(), + abiEnums + ); + + expect(result).toBeUndefined(); + }); + + test('should return void if enum validation passes for type result', () => { + const enumResult = 'core::result::Result::bool'; + + const abiEnums = { + [enumResult]: getAbiEnums().enum, + }; + const result = validateFields( + getFunctionAbi(enumResult), + [new CairoResult(0, 'content')], + getAbiStructs(), + abiEnums + ); + + expect(result).toBeUndefined(); + }); + + test('should throw an error if arg is not an JS object', () => { + const error = new Error( + 'Validate: arg test is cairo type Enum (enum), and should be defined as a js object (not array)' + ); + + expect(() => + validateFields(getFunctionAbi('enum'), [2], getAbiStructs(), getAbiEnums()) + ).toThrow(error); + }); + + test('should throw an error if arg is not an enum', () => { + const error = new Error( + 'Validate Enum: argument test, type enum, value received "[object Object]", is not an Enum.' + ); + + expect(() => + validateFields( + getFunctionAbi('enum'), + [{ example: 'test' }], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + }); + + describe('NonZero validation', () => { + test('should return void if non zero validation passes for felt', () => { + const result = validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}`), + [1n], + getAbiStructs(), + getAbiEnums() + ); + + expect(result).toBeUndefined(); + }); + + test('should return void if non zero validation passes for Uint', () => { + const result = validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}<${Uint.u8}>`), + [1n], + getAbiStructs(), + getAbiEnums() + ); + + expect(result).toBeUndefined(); + }); + + test('should throw an error if type is not authorized', () => { + const error = new Error('Validate: test type is not authorized for NonZero type.'); + + expect(() => + validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}`), + [true], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + + test('should throw an error if value 0 iz provided for felt252 type', () => { + const error = new Error('Validate: value 0 is not authorized in NonZero felt252 type.'); + + expect(() => + validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}`), + [0], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + + test('should throw an error if value 0 iz provided for uint256 type', () => { + const error = new Error('Validate: value 0 is not authorized in NonZero uint256 type.'); + + expect(() => + validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}<${Uint.u256}>`), + [0], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + + test('should throw an error if value 0 iz provided for any uint type', () => { + const error = new Error('Validate: value 0 is not authorized in NonZero uint type.'); + + expect(() => + validateFields( + getFunctionAbi(`${NON_ZERO_PREFIX}<${Uint.u8}>`), + [0], + getAbiStructs(), + getAbiEnums() + ) + ).toThrow(error); + }); + }); + + describe('Array validation', () => { + test('should return void if array validation passes for each type', () => { + const validateArray = (type: string, param: unknown) => + validateFields(getFunctionAbi(type), [[param]], getAbiStructs(), getAbiEnums()); + + expect(validateArray('core::array::Array::', true)).toBeUndefined(); + expect(validateArray('core::array::Array::', 'test')).toBeUndefined(); + expect(validateArray('core::array::Span::', true)).toBeUndefined(); + expect(validateArray('core::array::Array::', 'felt')).toBeUndefined(); + expect(validateArray(`core::array::Array::<${Uint.u8}>`, 2n)).toBeUndefined(); + expect(validateArray('core::array::Array::', 'felt')).toBeUndefined(); + expect( + validateArray('core::array::Array::<(core::bool, core::bool)>', { min: true, max: true }) + ).toBeUndefined(); + expect( + validateArray('core::array::Array::>', [true]) + ).toBeUndefined(); + + const enumArrayResult = 'core::array::Array::'; + + const abiEnums = { 'core::result::Result::core::bool': getAbiEnums().enum }; + const validatedArrayEnum = validateFields( + getFunctionAbi(enumArrayResult), + [[new CairoResult(0, 'content')]], + getAbiStructs(), + abiEnums + ); + + expect(validatedArrayEnum).toBeUndefined(); + + const structArrayEth = `core::array::Array::<${ETH_ADDRESS}>`; + const abiStructs = { [ETH_ADDRESS]: getAbiStructs().struct }; + + const validatedArrayStruct = validateFields( + getFunctionAbi(structArrayEth), + [[1n]], + abiStructs, + getAbiEnums() + ); + + expect(validatedArrayStruct).toBeUndefined(); + }); + + test('should throw an error if parameter is not an array', () => { + expect(() => { + validateFields( + getFunctionAbi('core::array::Span::'), + [true], + getAbiStructs(), + getAbiEnums() + ); + }).toThrow(new Error('Validate: arg test should be an Array')); + }); + + test('should throw an error if array validation is unhandled', () => { + expect(() => { + validateFields( + getFunctionAbi('core::array::Span::'), + [[true]], + getAbiStructs(), + getAbiEnums() + ); + }).toThrow( + new Error( + 'Validate Unhandled: argument test, type core::array::Span::, value true' + ) + ); + }); + }); +}); diff --git a/__tests__/utils/events.test.ts b/__tests__/utils/events.test.ts index db41690fe..c2dcadf89 100644 --- a/__tests__/utils/events.test.ts +++ b/__tests__/utils/events.test.ts @@ -4,28 +4,11 @@ import type { AbiEvent, AbiStructs, CairoEventVariant, - FunctionAbi, - InterfaceAbi, InvokeTransactionReceiptResponse, RPC, } from '../../src'; import { isAbiEvent, getAbiEvents, parseEvents, parseUDCEvent } from '../../src/utils/events'; - -const getAbiEventEntry = (): AbiEntry => ({ name: 'test', type: 'event' }); - -const getFunctionAbi = (): FunctionAbi => ({ - inputs: [getAbiEventEntry()], - name: 'test', - outputs: [getAbiEventEntry()], - stateMutability: 'view', - type: 'function', -}); - -const getInterfaceAbi = (): InterfaceAbi => ({ - items: [getFunctionAbi()], - name: 'test_interface_abi', - type: 'interface', -}); +import { getFunctionAbi, getInterfaceAbi, getAbiEntry } from '../factories/abi'; const getBaseTxReceiptData = (): InvokeTransactionReceiptResponse => ({ type: 'INVOKE', @@ -49,7 +32,7 @@ const getBaseTxReceiptData = (): InvokeTransactionReceiptResponse => ({ describe('isAbiEvent', () => { test('should return true if it is Abi event', () => { - expect(isAbiEvent(getAbiEventEntry())).toEqual(true); + expect(isAbiEvent(getAbiEntry('event'))).toEqual(true); }); test('should return false if it is not Abi event', () => { @@ -141,7 +124,7 @@ describe('getAbiEvents', () => { type: 'event', }; - const abiEvents = getAbiEvents([getFunctionAbi(), abiCairoEventStruct]); + const abiEvents = getAbiEvents([getFunctionAbi('event'), abiCairoEventStruct]); const result = { '0x27b21abc103381e154ea5c557dfe64466e0d25add7ef91a45718f5b8ee8fae3': abiCairoEventStruct, }; diff --git a/src/types/calldata.ts b/src/types/calldata.ts index 75729969b..de9a53aaa 100644 --- a/src/types/calldata.ts +++ b/src/types/calldata.ts @@ -27,3 +27,6 @@ export const Literal = { } as const; export type Literal = ValuesType; + +export const ETH_ADDRESS = 'core::starknet::eth_address::EthAddress'; +export const NON_ZERO_PREFIX = 'core::zeroable::NonZero::'; diff --git a/src/utils/calldata/cairo.ts b/src/utils/calldata/cairo.ts index d33060a8c..88ca11801 100644 --- a/src/utils/calldata/cairo.ts +++ b/src/utils/calldata/cairo.ts @@ -4,7 +4,9 @@ import { AbiStructs, BigNumberish, ContractVersion, + ETH_ADDRESS, Literal, + NON_ZERO_PREFIX, Uint, Uint256, Uint512, @@ -116,16 +118,14 @@ export const isTypeBool = (type: string) => type === 'core::bool'; * @param {string} type - The type to be checked. * @returns - true if the type matches 'core::starknet::contract_address::ContractAddress', false otherwise. */ -export const isTypeContractAddress = (type: string) => - type === 'core::starknet::contract_address::ContractAddress'; +export const isTypeContractAddress = (type: string) => type === Literal.ContractAddress; /** * Determines if the given type is an Ethereum address type. * * @param {string} type - The type to check. * @returns - Returns true if the given type is 'core::starknet::eth_address::EthAddress', otherwise false. */ -export const isTypeEthAddress = (type: string) => - type === 'core::starknet::eth_address::EthAddress'; +export const isTypeEthAddress = (type: string) => type === ETH_ADDRESS; /** * Checks if the given type is 'core::bytes_31::bytes31'. * @@ -140,8 +140,9 @@ export const isTypeBytes31 = (type: string) => type === 'core::bytes_31::bytes31 * @returns - True if the given type is equal to 'core::byte_array::ByteArray', false otherwise. */ export const isTypeByteArray = (type: string) => type === 'core::byte_array::ByteArray'; -export const isTypeSecp256k1Point = (type: string) => - type === 'core::starknet::secp256k1::Secp256k1Point'; + +export const isTypeSecp256k1Point = (type: string) => type === Literal.Secp256k1Point; + export const isCairo1Type = (type: string) => type.includes('::'); /** * Retrieves the array type from the given type string. @@ -151,10 +152,9 @@ export const isCairo1Type = (type: string) => type.includes('::'); * @returns - The array type. */ export const getArrayType = (type: string) => { - if (isCairo1Type(type)) { - return type.substring(type.indexOf('<') + 1, type.lastIndexOf('>')); - } - return type.replace('*', ''); + return isCairo1Type(type) + ? type.substring(type.indexOf('<') + 1, type.lastIndexOf('>')) + : type.replace('*', ''); }; /** @@ -186,7 +186,7 @@ export function isCairo1Abi(abi: Abi): boolean { * ``` */ export function isTypeNonZero(type: string): boolean { - return type.startsWith('core::zeroable::NonZero::'); + return type.startsWith(NON_ZERO_PREFIX); } /** diff --git a/src/utils/calldata/enum/CairoCustomEnum.ts b/src/utils/calldata/enum/CairoCustomEnum.ts index fb00a4214..a1231fa2a 100644 --- a/src/utils/calldata/enum/CairoCustomEnum.ts +++ b/src/utils/calldata/enum/CairoCustomEnum.ts @@ -1,8 +1,6 @@ import { isUndefined } from '../../typed'; -export type CairoEnumRaw = { - [key: string]: any; -}; +export type CairoEnumRaw = Record; /** * Class to handle Cairo custom Enum @@ -45,9 +43,8 @@ export class CairoCustomEnum { * @returns the content of the valid variant of a Cairo custom Enum. */ public unwrap(): any { - const variants = Object.entries(this.variant); - const activeVariant = variants.find((item) => !isUndefined(item[1])); - return isUndefined(activeVariant) ? undefined : activeVariant[1]; + const variants = Object.values(this.variant); + return variants.find((item) => !isUndefined(item)); } /** diff --git a/src/utils/calldata/enum/CairoOption.ts b/src/utils/calldata/enum/CairoOption.ts index 97919e742..dfed9db92 100644 --- a/src/utils/calldata/enum/CairoOption.ts +++ b/src/utils/calldata/enum/CairoOption.ts @@ -11,7 +11,7 @@ export type CairoOptionVariant = ValuesType; /** * Class to handle Cairo Option * @param variant CairoOptionVariant.Some or CairoOptionVariant.None - * @param someContent value of type T. + * @param content value of type T. * @returns an instance representing a Cairo Option. * @example * ```typescript @@ -23,17 +23,17 @@ export class CairoOption { readonly None?: boolean; - constructor(variant: CairoOptionVariant | number, someContent?: T) { + constructor(variant: CairoOptionVariant | number, content?: T) { if (!(variant in Object.values(CairoOptionVariant))) { - throw new Error('Wrong variant : should be CairoOptionVariant.Some or .None.'); + throw new Error('Wrong variant! It should be CairoOptionVariant.Some or .None.'); } if (variant === CairoOptionVariant.Some) { - if (isUndefined(someContent)) { + if (isUndefined(content)) { throw new Error( 'The creation of a Cairo Option with "Some" variant needs a content as input.' ); } - this.Some = someContent; + this.Some = content; this.None = undefined; } else { this.Some = undefined; diff --git a/src/utils/calldata/enum/CairoResult.ts b/src/utils/calldata/enum/CairoResult.ts index 654c746c7..e50063cc1 100644 --- a/src/utils/calldata/enum/CairoResult.ts +++ b/src/utils/calldata/enum/CairoResult.ts @@ -25,7 +25,7 @@ export class CairoResult { constructor(variant: CairoResultVariant | number, resultContent: T | U) { if (!(variant in Object.values(CairoResultVariant))) { - throw new Error('Wrong variant : should be CairoResultVariant.Ok or .Err.'); + throw new Error('Wrong variant! It should be CairoResultVariant.Ok or .Err.'); } if (variant === CairoResultVariant.Ok) { this.Ok = resultContent as T; diff --git a/src/utils/calldata/formatter.ts b/src/utils/calldata/formatter.ts index 5aa3d6a77..a7be17866 100644 --- a/src/utils/calldata/formatter.ts +++ b/src/utils/calldata/formatter.ts @@ -1,10 +1,10 @@ -import { isBigInt } from '../typed'; +import { isBigInt, isObject } from '../typed'; import { decodeShortString } from '../shortString'; const guard = { /** * Checks if the data is a BigInt (BN) and throws an error if not. - * + * * @param {Record} data - The data object containing the key to check. * @param {Record} type - The type definition object. * @param {string} key - The key in the data object to check. @@ -18,10 +18,9 @@ const guard = { } to be BN instead it is ${typeof data[key]}` ); }, - /** * Throws an error for unhandled formatter types. - * + * * @param {Record} data - The data object containing the key. * @param {Record} type - The type definition object. * @param {string} key - The key in the data object to check. @@ -39,27 +38,27 @@ const guard = { * @param {Record} type - The type definition for the data. * @param {any} [sameType] - The same type definition to be used (optional). * @returns {Record} The formatted data. - * + * * @example * // Example 1: Formatting a simple object - * const data = { value: '123', name: 'test' }; + * const data = { value: 1n, name: 2n }; * const type = { value: 'number', name: 'string' }; * const formatted = formatter(data, type); - * // formatted: { value: 123, name: 'test' } - * + * // formatted: { value: 1n, name: '2n' } + * * @example * // Example 2: Formatting an object with nested structures - * const data = { user: { id: '123', age: '30' }, active: '1' }; - * const type = { user: { id: 'number', age: 'number' }, active: 'number' }; + * const data = { test: { id: 1n, value: 30n }, active: 1n }; + * const type = { test: { id: 'number', value: 'number' }, active: 'number' }; * const formatted = formatter(data, type); - * // formatted: { user: { id: 123, age: 30 }, active: 1 } - * + * // formatted: { test: { id: 1n, value: 30n }, active: 1n } + * * @example * // Example 3: Handling arrays in the data object - * const data = { items: ['1', '2', '3'], name: 'test' }; - * const type = { items: ['number'], name: 'string' }; + * const data = { items: [1n, 2n, 3n], value: 4n }; + * const type = { items: ['number'], value: 'string' }; * const formatted = formatter(data, type); - * // formatted: { items: [1, 2, 3], name: 'test' } + * // formatted: { items: [1n, 2n, 3n], value: '4n' } */ export default function formatter( data: Record, @@ -105,7 +104,7 @@ export default function formatter( acc[key] = Object.values(arrayObj); return acc; } - if (typeof elType === 'object') { + if (isObject(elType)) { acc[key] = formatter(data[key], elType); return acc; } diff --git a/src/utils/calldata/parser/index.ts b/src/utils/calldata/parser/index.ts index 76a599378..098999f29 100644 --- a/src/utils/calldata/parser/index.ts +++ b/src/utils/calldata/parser/index.ts @@ -4,6 +4,19 @@ import { AbiParserInterface } from './interface'; import { AbiParser1 } from './parser-0-1.1.0'; import { AbiParser2 } from './parser-2.0.0'; +/** + * Creates ABI parser + * + * @param {Abi} abi + * @returns {AbiParserInterface} abi parser interface + * + * @example + * const abiParser2 = createAbiParser([getInterfaceAbi('struct')]); + * // abiParser2 instanceof AbiParser2 === true + * + * const abiParser1 = createAbiParser([getFunctionAbi('struct')]); + * // abiParser1 instanceof AbiParser1 === true + */ export function createAbiParser(abi: Abi): AbiParserInterface { const version = getAbiVersion(abi); if (version === 0 || version === 1) { @@ -15,17 +28,50 @@ export function createAbiParser(abi: Abi): AbiParserInterface { throw Error(`Unsupported ABI version ${version}`); } -export function getAbiVersion(abi: Abi) { +/** + * Retrieves ABI version + * + * @param {Abi} abi + * @returns {1 | 2 | 0} abi 1, 2 or 0 version + * + * @example + * // Example 1: Return ABI version 2 + * const version = getAbiVersion([getInterfaceAbi()]); + * // version === 2 + * + * // Example 2: Return ABI version 1 + * const version = getAbiVersion([getInterfaceAbi('core::bool')]); + * // version === 1 + * + * // Example 3: Return ABI version 0 + * const version = getAbiVersion([getInterfaceAbi('felt')]); + * // version === 0 + */ +export function getAbiVersion(abi: Abi): 1 | 2 | 0 { if (abi.find((it) => it.type === 'interface')) return 2; if (isCairo1Abi(abi)) return 1; return 0; } +/** + * Checks if no constructor valid + * + * @param {string} method + * @param {RawArgs} argsCalldata + * @param {FunctionAbi} abiMethod + * @returns boolean + * + * @example + * const result1 = isNoConstructorValid('constructor', []) + * // result1 === true + * const result2 = isNoConstructorValid('test', ['test']) + * // result2 === false + */ export function isNoConstructorValid( method: string, argsCalldata: RawArgs, abiMethod?: FunctionAbi -) { +): boolean { // No constructor in abi and validly empty args return method === 'constructor' && !abiMethod && !argsCalldata.length; } diff --git a/src/utils/calldata/parser/parser-2.0.0.ts b/src/utils/calldata/parser/parser-2.0.0.ts index 1a6cbe48e..f80102c2c 100644 --- a/src/utils/calldata/parser/parser-2.0.0.ts +++ b/src/utils/calldata/parser/parser-2.0.0.ts @@ -33,7 +33,7 @@ export class AbiParser2 implements AbiParserInterface { const intf = this.abi.find( (it: FunctionAbi | AbiEvent | AbiStruct | InterfaceAbi) => it.type === 'interface' ) as InterfaceAbi; - return intf.items.find((it) => it.name === name); + return intf?.items?.find((it) => it.name === name); } /** @@ -41,11 +41,8 @@ export class AbiParser2 implements AbiParserInterface { * @returns Abi */ public getLegacyFormat(): Abi { - return this.abi.flatMap((e: FunctionAbi | LegacyEvent | AbiStruct | InterfaceAbi) => { - if (e.type === 'interface') { - return e.items; - } - return e; + return this.abi.flatMap((it: FunctionAbi | LegacyEvent | AbiStruct | InterfaceAbi) => { + return it.type === 'interface' ? it.items : it; }); } } diff --git a/src/utils/calldata/requestParser.ts b/src/utils/calldata/requestParser.ts index 096fb8be1..913e2ad17 100644 --- a/src/utils/calldata/requestParser.ts +++ b/src/utils/calldata/requestParser.ts @@ -22,6 +22,7 @@ import { isTypeArray, isTypeBytes31, isTypeEnum, + isTypeEthAddress, isTypeNonZero, isTypeOption, isTypeResult, @@ -149,8 +150,7 @@ function parseCalldataValue( if (CairoUint512.isAbiType(type)) { return new CairoUint512(element as any).toApiRequest(); } - if (type === 'core::starknet::eth_address::EthAddress') - return parseBaseTypes(type, element as BigNumberish); + if (isTypeEthAddress(type)) return parseBaseTypes(type, element as BigNumberish); if (type === 'core::byte_array::ByteArray') return parseByteArray(element as string); @@ -304,7 +304,7 @@ export function parseCalldataField( return parseCalldataValue(value, input.type, structs, enums); case isTypeNonZero(type): return parseBaseTypes(getArrayType(type), value); - case type === 'core::starknet::eth_address::EthAddress': + case isTypeEthAddress(type): return parseBaseTypes(type, value); // Struct or Tuple case isTypeStruct(type, structs) || diff --git a/src/utils/calldata/tuple.ts b/src/utils/calldata/tuple.ts index a76f53bb1..bc0578e5f 100644 --- a/src/utils/calldata/tuple.ts +++ b/src/utils/calldata/tuple.ts @@ -106,22 +106,22 @@ function extractCairo1Tuple(type: string): string[] { /** * Convert a tuple string definition into an object-like definition. * Supports both Cairo 0 and Cairo 1 tuple formats. - * + * * @param type - The tuple string definition (e.g., "(u8, u8)" or "(x:u8, y:u8)"). * @returns An array of strings or objects representing the tuple components. - * + * * @example * // Cairo 0 Tuple * const cairo0Tuple = "(u8, u8)"; * const result = extractTupleMemberTypes(cairo0Tuple); * // result: ["u8", "u8"] - * + * * @example * // Named Cairo 0 Tuple * const namedCairo0Tuple = "(x:u8, y:u8)"; * const namedResult = extractTupleMemberTypes(namedCairo0Tuple); * // namedResult: [{ name: "x", type: "u8" }, { name: "y", type: "u8" }] - * + * * @example * // Cairo 1 Tuple * const cairo1Tuple = "(core::result::Result::, u8)"; @@ -129,8 +129,5 @@ function extractCairo1Tuple(type: string): string[] { * // cairo1Result: ["core::result::Result::", "u8"] */ export default function extractTupleMemberTypes(type: string): (string | object)[] { - if (isCairo1Type(type)) { - return extractCairo1Tuple(type); - } - return extractCairo0Tuple(type); + return isCairo1Type(type) ? extractCairo1Tuple(type) : extractCairo0Tuple(type); } diff --git a/src/utils/calldata/validate.ts b/src/utils/calldata/validate.ts index 45339d753..c239eb7fd 100644 --- a/src/utils/calldata/validate.ts +++ b/src/utils/calldata/validate.ts @@ -1,12 +1,9 @@ -/** - * Validate cairo contract method arguments - * Flow: Determine type from abi and than validate against parameter - */ import { AbiEntry, AbiEnums, AbiStructs, BigNumberish, + ETH_ADDRESS, FunctionAbi, Literal, Uint, @@ -16,7 +13,7 @@ import { CairoUint256 } from '../cairoDataTypes/uint256'; import { CairoUint512 } from '../cairoDataTypes/uint512'; import { isHex, toBigInt } from '../num'; import { isLongText } from '../shortString'; -import { isBoolean, isNumber, isString, isBigInt } from '../typed'; +import { isBoolean, isNumber, isString, isBigInt, isObject } from '../typed'; import { getArrayType, isLen, @@ -65,15 +62,15 @@ const validateUint = (parameter: any, input: AbiEntry) => { if (isNumber(parameter)) { assert( parameter <= Number.MAX_SAFE_INTEGER, - `Validation: Parameter is to large to be typed as Number use (BigInt or String)` + 'Validation: Parameter is too large to be typed as Number use (BigInt or String)' ); } assert( isString(parameter) || isNumber(parameter) || isBigInt(parameter) || - (typeof parameter === 'object' && 'low' in parameter && 'high' in parameter) || - (typeof parameter === 'object' && + (isObject(parameter) && 'low' in parameter && 'high' in parameter) || + (isObject(parameter) && ['limb0', 'limb1', 'limb2', 'limb3'].every((key) => key in parameter)), `Validate: arg ${input.name} of cairo type ${ input.type @@ -129,12 +126,15 @@ const validateUint = (parameter: any, input: AbiEntry) => { case Uint.u256: assert( param >= 0n && param <= 2n ** 256n - 1n, - `Validate: arg ${input.name} is ${input.type} 0 - 2^256-1` + `Validate: arg ${input.name} is ${input.type} should be in range 0 - 2^256-1` ); break; case Uint.u512: - assert(CairoUint512.is(param), `Validate: arg ${input.name} is ${input.type} 0 - 2^512-1`); + assert( + CairoUint512.is(param), + `Validate: arg ${input.name} is ${input.type} should be in range 0 - 2^512-1` + ); break; case Literal.ClassHash: @@ -179,11 +179,8 @@ const validateStruct = (parameter: any, input: AbiEntry, structs: AbiStructs) => return; } - if (input.type === 'core::starknet::eth_address::EthAddress') { - assert( - typeof parameter !== 'object', - `EthAddress type is waiting a BigNumberish. Got ${parameter}` - ); + if (input.type === ETH_ADDRESS) { + assert(!isObject(parameter), `EthAddress type is waiting a BigNumberish. Got "${parameter}"`); const param = BigInt(parameter.toString(10)); assert( // from : https://github.com/starkware-libs/starknet-specs/blob/29bab650be6b1847c92d4461d4c33008b5e50b1a/api/starknet_api_openrpc.json#L1259 @@ -194,8 +191,8 @@ const validateStruct = (parameter: any, input: AbiEntry, structs: AbiStructs) => } assert( - typeof parameter === 'object' && !Array.isArray(parameter), - `Validate: arg ${input.name} is cairo type struct (${input.type}), and should be defined as js object (not array)` + isObject(parameter), + `Validate: arg ${input.name} is cairo type struct (${input.type}), and should be defined as a js object (not array)` ); // shallow struct validation, only first depth level @@ -209,9 +206,10 @@ const validateStruct = (parameter: any, input: AbiEntry, structs: AbiStructs) => const validateEnum = (parameter: any, input: AbiEntry) => { assert( - typeof parameter === 'object' && !Array.isArray(parameter), - `Validate: arg ${input.name} is cairo type Enum (${input.type}), and should be defined as js object (not array)` + isObject(parameter), + `Validate: arg ${input.name} is cairo type Enum (${input.type}), and should be defined as a js object (not array)` ); + const methodsKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(parameter)); const keys = [...Object.getOwnPropertyNames(parameter), ...methodsKeys]; if (isTypeOption(input.type) && keys.includes('isSome') && keys.includes('isNone')) { @@ -224,15 +222,12 @@ const validateEnum = (parameter: any, input: AbiEntry) => { return; // Custom Enum } throw new Error( - `Validate Enum: argument ${input.name}, type ${input.type}, value received ${parameter}, is not an Enum.` + `Validate Enum: argument ${input.name}, type ${input.type}, value received "${parameter}", is not an Enum.` ); }; const validateTuple = (parameter: any, input: AbiEntry) => { - assert( - typeof parameter === 'object' && !Array.isArray(parameter), - `Validate: arg ${input.name} should be a tuple (defined as object)` - ); + assert(isObject(parameter), `Validate: arg ${input.name} should be a tuple (defined as object)`); // todo: skip tuple structural validation for now }; @@ -290,6 +285,7 @@ const validateNonZero = (parameter: any, input: AbiEntry) => { // so, are authorized here : u8, u16, u32, u64, u128, u256 and felt252. const baseType = getArrayType(input.type); + assert( (isTypeUint(baseType) && baseType !== CairoUint512.abiSelector) || isTypeFelt(baseType), `Validate: ${input.name} type is not authorized for NonZero type.` @@ -304,7 +300,8 @@ const validateNonZero = (parameter: any, input: AbiEntry) => { break; case isTypeUint(baseType): validateUint(parameter, { name: '', type: baseType }); - switch (input.type) { + + switch (baseType) { case Uint.u256: assert( new CairoUint256(parameter).toBigInt() > 0, @@ -320,17 +317,69 @@ const validateNonZero = (parameter: any, input: AbiEntry) => { break; default: throw new Error( - `Validate Unhandled: argument ${input.name}, type ${input.type}, value ${parameter}` + `Validate Unhandled: argument ${input.name}, type ${input.type}, value "${parameter}"` ); } }; +/** + * Validate cairo contract method arguments + * Flow: Determine type from abi and than validate against parameter + * + * @param {FunctionAbi} abiMethod - Abi method. + * @param {any[]} args - Arguments. + * @param {AbiStructs} structs - ABI structs. + * @param {AbiEnums} enums - ABI enums. + * @returns {void} - Return void if validation passes + * + * @example + * const functionAbi: FunctionAbi = { + * inputs: [{ name: 'test', type: 'felt' }], + * name: 'test', + * outputs: [{ name: 'test', type: 'felt' }], + * stateMutability: 'view', + * type: 'function', + * }; + * + * const abiStructs: AbiStructs = { + * abi_structs: { + * members: [ + * { + * name: 'test_name', + * type: 'test_type', + * offset: 1, + * }, + * ], + * size: 2, + * name: 'cairo_event_struct', + * type: 'struct', + * }, + * }; + * + * const abiEnums: AbiEnums = { + * abi_enums: { + * variants: [ + * { + * name: 'test_name', + * type: 'cairo_event_struct_variant', + * offset: 1, + * }, + * ], + * size: 2, + * name: 'test_cairo_event', + * type: 'enum', + * }, + * }; + * + * validateFields(functionAbi, [1n], abiStructs, abiEnums); // Returns void since validation passes + * validateFields(functionAbi, [{}], abiStructs, abiEnums); // Throw an error because paramters are not valid + */ export default function validateFields( abiMethod: FunctionAbi, - args: Array, + args: any[], structs: AbiStructs, enums: AbiEnums -) { +): void { abiMethod.inputs.reduce((acc, input) => { const parameter = args[acc]; diff --git a/src/utils/hash/classHash.ts b/src/utils/hash/classHash.ts index 44ce8749e..c1b92cc0a 100644 --- a/src/utils/hash/classHash.ts +++ b/src/utils/hash/classHash.ts @@ -160,7 +160,7 @@ export function computeHintedClassHash(compiledContract: LegacyCompiledContract) * // result = "0x4a5cae61fa8312b0a3d0c44658b403d3e4197be80027fd5020ffcdf0c803331" * ``` */ -export function computeLegacyContractClassHash(contract: LegacyCompiledContract | string) { +export function computeLegacyContractClassHash(contract: LegacyCompiledContract | string): string { const compiledContract = isString(contract) ? (parse(contract) as LegacyCompiledContract) : contract; @@ -243,7 +243,7 @@ export function hashByteCodeSegments(casm: CompiledSierraCasm): bigint { * Compute compiled class hash for contract (Cairo 1) * @param {CompiledSierraCasm} casm Cairo 1 compiled contract content * @returns {string} hex-string of class hash - * @example + * @example * ```typescript * const compiledCasm = json.parse(fs.readFileSync("./cairo260.casm.json").toString("ascii")); * const result = hash.computeCompiledClassHash(compiledCasm); @@ -297,7 +297,7 @@ function hashAbi(sierra: CompiledSierra) { * Compute sierra contract class hash (Cairo 1) * @param {CompiledSierra} sierra Cairo 1 Sierra contract content * @returns {string} hex-string of class hash - * @example + * @example * ```typescript * const compiledSierra = json.parse(fs.readFileSync("./cairo260.sierra.json").toString("ascii")); * const result = hash.computeSierraContractClassHash(compiledSierra); @@ -341,7 +341,7 @@ export function computeSierraContractClassHash(sierra: CompiledSierra): string { * Compute ClassHash (sierra or legacy) based on provided contract * @param {CompiledContract | string} contract Cairo 1 contract content * @returns {string} hex-string of class hash - * @example + * @example * ```typescript * const compiledSierra = json.parse(fs.readFileSync("./cairo260.sierra.json").toString("ascii")); * const result = hash.computeContractClassHash(compiledSierra); diff --git a/src/utils/typedData.ts b/src/utils/typedData.ts index c83f05aed..1d462283a 100644 --- a/src/utils/typedData.ts +++ b/src/utils/typedData.ts @@ -22,7 +22,7 @@ import { import { MerkleTree } from './merkle'; import { isBigNumberish, isHex, toHex } from './num'; import { encodeShortString } from './shortString'; -import { isString } from './typed'; +import { isBoolean, isString } from './typed'; /** @deprecated prefer importing from 'types' over 'typedData' */ export * from '../types/typedData'; @@ -436,7 +436,7 @@ export function encodeValue( } case 'bool': { if (revision === Revision.ACTIVE) { - assert(typeof data === 'boolean', `Type mismatch for ${type} ${data}`); + assert(isBoolean(data), `Type mismatch for ${type} ${data}`); } // else fall through to default return [type, getHex(data as string)]; }