diff --git a/__tests__/cairo1v2.test.ts b/__tests__/cairo1v2.test.ts index e2ecd9fcd..14281ac88 100644 --- a/__tests__/cairo1v2.test.ts +++ b/__tests__/cairo1v2.test.ts @@ -31,6 +31,7 @@ import { compiledC210, compiledC210Casm, compiledComplexSierra, + compiledHelloSierra, getTestAccount, getTestProvider, } from './config/fixtures'; @@ -673,6 +674,83 @@ describe('Cairo 1', () => { expect(callDataFromObject).toStrictEqual(expectedResult); expect(callDataFromArray).toStrictEqual(expectedResult); }); + + test('myCallData.decodeParameters for Cairo 1', async () => { + const Cairo1HelloAbi = compiledHelloSierra; + const Cairo1Abi = compiledC1v2; + const helloCallData = new CallData(Cairo1HelloAbi.abi); + const c1v2CallData = new CallData(Cairo1Abi.abi); + + const res2 = helloCallData.decodeParameters('hello::hello::UserData', ['0x123456', '0x1']); + expect(res2).toEqual({ address: 1193046n, is_claimed: true }); + const res3 = helloCallData.decodeParameters( + ['hello::hello::UserData', 'hello::hello::UserData'], + ['0x123456', '0x1', '0x98765', '0x0'] + ); + expect(res3).toEqual([ + { address: 1193046n, is_claimed: true }, + { address: 624485n, is_claimed: false }, + ]); + const res4 = helloCallData.decodeParameters('core::integer::u8', ['0x123456']); + expect(res4).toBe(1193046n); + const res5 = helloCallData.decodeParameters('core::bool', ['0x1']); + expect(res5).toBe(true); + const res6 = helloCallData.decodeParameters('core::felt252', ['0x123456']); + expect(res6).toBe(1193046n); + const res7 = helloCallData.decodeParameters('core::integer::u256', ['0x123456', '0x789']); + expect(num.toHex(res7.toString())).toBe('0x78900000000000000000000000000123456'); + const res8 = helloCallData.decodeParameters('core::array::Array::', [ + '2', + '0x123456', + '0x789', + ]); + expect(res8).toEqual([1193046n, 1929n]); + const res9 = helloCallData.decodeParameters('core::array::Span::', [ + '2', + '0x123456', + '0x789', + ]); + expect(res9).toEqual([1193046n, 1929n]); + const res10 = helloCallData.decodeParameters('(core::felt252, core::integer::u16)', [ + '0x123456', + '0x789', + ]); + expect(res10).toEqual({ '0': 1193046n, '1': 1929n }); + const res11 = helloCallData.decodeParameters('core::starknet::eth_address::EthAddress', [ + '0x123456', + ]); + expect(res11).toBe(1193046n); + const res12 = helloCallData.decodeParameters( + 'core::starknet::contract_address::ContractAddress', + ['0x123456'] + ); + expect(res12).toBe(1193046n); + const res13 = helloCallData.decodeParameters('core::starknet::class_hash::ClassHash', [ + '0x123456', + ]); + expect(res13).toBe(1193046n); + const res14 = c1v2CallData.decodeParameters('core::option::Option::', [ + '0', + '0x12', + ]); + expect(res14).toEqual({ Some: 18n, None: undefined }); + const res15 = c1v2CallData.decodeParameters( + 'core::result::Result::', + ['0', '0x12', '0x345'] + ); + expect(res15).toEqual({ Ok: { p1: 18n, p2: 837n }, Err: undefined }); + const res16 = c1v2CallData.decodeParameters( + 'hello_res_events_newTypes::hello_res_events_newTypes::MyEnum', + ['0', '0x12', '0x5678'] + ); + expect(res16).toEqual({ + variant: { + Response: { p1: 18n, p2: 22136n }, + Warning: undefined, + Error: undefined, + }, + }); + }); }); describe('Cairo1 Account contract', () => { diff --git a/__tests__/contract.test.ts b/__tests__/contract.test.ts index 773978313..545be41da 100644 --- a/__tests__/contract.test.ts +++ b/__tests__/contract.test.ts @@ -717,6 +717,75 @@ describe('Complex interaction', () => { expect(callDataFromArray).toStrictEqual(expectedResult); }); + test('myCallData.decodeParameters for Cairo 0', async () => { + const myCallData = new CallData(erc20Echo20Contract.abi); + + const res0 = myCallData.decodeParameters('felt', ['474107654995566025798705']); + expect(res0).toBe(474107654995566025798705n); + const res1 = myCallData.decodeParameters('StructY', [ + '474107654995566025798705', + '3534634645645', + ]); + expect(res1).toEqual({ y1: 474107654995566025798705n, y2: 3534634645645n }); + + const res2 = myCallData.decodeParameters('Uint256', ['47410765', '35346645']); + expect(res2).toEqual({ low: 47410765n, high: 35346645n }); + const res3 = myCallData.decodeParameters('Struct32', ['47410765', '35346645', '1', '2', '3']); + expect(res3).toEqual({ b: 47410765n, c: { '0': 35346645n, '1': 1n, '2': 2n, '3': 3n } }); + + const res4 = myCallData.decodeParameters('(felt, felt, felt, felt)', [ + '47410765', + '35346645', + '1', + '2', + ]); + expect(res4).toEqual({ '0': 47410765n, '1': 35346645n, '2': 1n, '3': 2n }); + + const res5 = myCallData.decodeParameters('Struct2', ['47410765', '35346645', '1', '2', '3']); + expect(res5).toEqual({ + info: { discount_fix_bps: 47410765n, discount_transfer_bps: 35346645n }, + data: 1n, + data2: { min: 2n, max: 3n }, + }); + const res6 = myCallData.decodeParameters('Struct3', [ + '47410765', + '35346645', + '1', + '2', + '3', + '4', + ]); + expect(res6).toEqual({ + a: 47410765n, + b: { b: 35346645n, c: { '0': 1n, '1': 2n, '2': 3n, '3': 4n } }, + }); + const res7 = myCallData.decodeParameters('(t1: felt, t2: StructX, t3: felt)', [ + '47410765', + '35346645', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + ]); + expect(res7).toEqual({ + t1: 47410765n, + t2: { + x1: 35346645n, + x2: { y1: 1n, y2: 2n }, + x3: { + tx1: { '0': 3n, '1': 4n }, + tx2: { tx21: { tx211: 5n, tx212: 6n }, tx22: { '0': 7n, '1': 8n } }, + }, + }, + t3: 9n, + }); + }); + test('invoke compiled data', async () => { const result = await erc20Echo20Contract.iecho(CallData.compile(request)); const transaction = await provider.waitForTransaction(result.transaction_hash); diff --git a/src/utils/calldata/index.ts b/src/utils/calldata/index.ts index 4e962e41b..cdfba2845 100644 --- a/src/utils/calldata/index.ts +++ b/src/utils/calldata/index.ts @@ -3,6 +3,7 @@ import { Abi, AbiEnums, AbiStructs, + AllowArray, Args, ArgsOrCalldata, Calldata, @@ -325,4 +326,29 @@ export class CallData { const calldata = CallData.compile(raw); return calldata.map((it) => toHex(it)); } + + /** + * Parse the elements of a contract response and structure them into one or several Result. + * In Cairo 0, arrays are not supported. + * @param typeCairo string or string[] - Cairo type name, ex : "hello::hello::UserData" + * @param response string[] - serialized data corresponding to typeCairo. + * @return Result or Result[] - parsed response corresponding to typeData. + * @example + * const res2=helloCallData.decodeParameters("hello::hello::UserData",["0x123456","0x1"]); + * result = { address: 1193046n, is_claimed: true } + */ + public decodeParameters(typeCairo: AllowArray, response: string[]): AllowArray { + const typeCairoArray = Array.isArray(typeCairo) ? typeCairo : [typeCairo]; + const responseIterator = response.flat()[Symbol.iterator](); + const decodedArray = typeCairoArray.map( + (typeParam) => + responseParser( + responseIterator, + { name: '', type: typeParam }, + this.structs, + this.enums + ) as Result + ); + return decodedArray.length === 1 ? decodedArray[0] : decodedArray; + } }