Skip to content

Commit 0f42429

Browse files
ChaituVRwa0x6e
andauthored
fix: parseStarknetResult to handle multiple outputs (#1180)
* fix: parseStarknetResult to handle multiple outputs * refactor * use getProvider * fix test cases * Update test/integration/multicall/starknet-contracts.spec.ts Co-authored-by: Wan <[email protected]> * add snap * Update test/integration/multicall/starknet-contracts.spec.ts Co-authored-by: Wan <[email protected]> * Update test/integration/multicall/starknet-contracts.spec.ts Co-authored-by: Wan <[email protected]> * Update test/integration/multicall/starknet-contracts.spec.ts Co-authored-by: Wan <[email protected]> * Update test/integration/multicall/__snapshots__/starknet-contracts.spec.ts.snap --------- Co-authored-by: Wan <[email protected]>
1 parent 3de3df8 commit 0f42429

File tree

3 files changed

+193
-58
lines changed

3 files changed

+193
-58
lines changed

src/multicall/starknet.ts

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,67 +17,78 @@ function parseStarknetResult(rawResult: string[], functionAbi: any): any {
1717
return rawResult;
1818
}
1919

20-
const output = functionAbi.outputs[0];
21-
const rawValue = rawResult[0];
20+
// Parse each output according to its type
21+
const outputs = functionAbi.outputs;
22+
const results: any[] = [];
23+
let rawIndex = 0; // Track position in rawResult array
2224

2325
try {
24-
switch (output.type) {
25-
case 'core::felt252':
26-
// Try to decode as shortString (for name, symbol)
27-
try {
28-
return shortString.decodeShortString(rawValue);
29-
} catch {
30-
// If shortString decode fails, return as hex
31-
return rawValue;
32-
}
33-
34-
// Unsigned integers
35-
case 'core::integer::u8':
36-
case 'core::integer::u16':
37-
case 'core::integer::u32':
38-
case 'core::integer::u64':
39-
return parseInt(rawValue, 16);
40-
41-
case 'core::integer::u128':
42-
case 'core::integer::usize':
43-
return BigInt(rawValue).toString();
44-
45-
case 'core::integer::u256':
46-
return uint256.uint256ToBN({
47-
low: rawValue,
48-
high: rawResult[1] || '0x0'
49-
});
50-
51-
// Signed integers
52-
case 'core::integer::i8':
53-
case 'core::integer::i16':
54-
case 'core::integer::i32':
55-
case 'core::integer::i64':
56-
return parseInt(rawValue, 16);
57-
58-
case 'core::integer::i128':
59-
return BigInt(rawValue).toString();
60-
61-
// Boolean type
62-
case 'core::bool':
63-
return rawValue === '0x1' || rawValue === '0x01';
64-
65-
// Address types
66-
case 'core::starknet::contract_address::ContractAddress':
67-
case 'core::starknet::class_hash::ClassHash':
68-
case 'core::starknet::storage_access::StorageAddress':
69-
return rawValue;
70-
71-
// Byte array
72-
case 'core::bytes_31::bytes31':
73-
return rawValue;
74-
75-
default:
76-
// Return raw value for unknown types
77-
return rawValue;
26+
for (let outputIndex = 0; outputIndex < outputs.length; outputIndex++) {
27+
const output = outputs[outputIndex];
28+
const rawValue = rawResult[rawIndex];
29+
30+
switch (output.type) {
31+
case 'core::felt252':
32+
try {
33+
results.push(shortString.decodeShortString(rawValue));
34+
} catch {
35+
results.push(rawValue);
36+
}
37+
rawIndex++;
38+
break;
39+
case 'core::integer::u8':
40+
case 'core::integer::u16':
41+
case 'core::integer::u32':
42+
case 'core::integer::u64':
43+
results.push(parseInt(rawValue, 16));
44+
rawIndex++;
45+
break;
46+
case 'core::integer::u128':
47+
case 'core::integer::usize':
48+
results.push(BigInt(rawValue).toString());
49+
rawIndex++;
50+
break;
51+
case 'core::integer::u256':
52+
results.push(
53+
uint256.uint256ToBN({
54+
low: rawValue,
55+
high: rawResult[rawIndex + 1] || '0x0'
56+
})
57+
);
58+
rawIndex += 2; // u256 uses two slots
59+
break;
60+
case 'core::integer::i8':
61+
case 'core::integer::i16':
62+
case 'core::integer::i32':
63+
case 'core::integer::i64':
64+
results.push(parseInt(rawValue, 16));
65+
rawIndex++;
66+
break;
67+
case 'core::integer::i128':
68+
results.push(BigInt(rawValue).toString());
69+
rawIndex++;
70+
break;
71+
case 'core::bool':
72+
results.push(rawValue === '0x1' || rawValue === '0x01');
73+
rawIndex++;
74+
break;
75+
case 'core::starknet::contract_address::ContractAddress':
76+
case 'core::starknet::class_hash::ClassHash':
77+
case 'core::starknet::storage_access::StorageAddress':
78+
results.push(rawValue);
79+
rawIndex++;
80+
break;
81+
case 'core::bytes_31::bytes31':
82+
results.push(rawValue);
83+
rawIndex++;
84+
break;
85+
default:
86+
results.push(rawValue);
87+
rawIndex++;
88+
}
7889
}
90+
return results;
7991
} catch {
80-
// Fallback to raw result if parsing fails
8192
return rawResult;
8293
}
8394
}
@@ -149,6 +160,7 @@ export default async function multicall(
149160
const [, functionName] = calls[index];
150161
const functionAbi = abi.find((item) => item.name === functionName);
151162

152-
return [parseStarknetResult(result, functionAbi)];
163+
const parsedResult = parseStarknetResult(result, functionAbi);
164+
return parsedResult;
153165
});
154166
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Starknet multicall > should return responses from different contract calls 1`] = `
4+
[
5+
[
6+
"Staking Delegation Pool",
7+
],
8+
[
9+
"0x0",
10+
"0x1c6a9f1f3f97a0a3b176fac97b69c3cd21ba31a522fca111b0e49e46975a048",
11+
"0x29fa8220372487cb4e1",
12+
"0x41a4db19d798c5601b",
13+
"0x0",
14+
"0x0",
15+
"0x1",
16+
],
17+
[
18+
"0x0",
19+
"0xd3b910d8c528bf0216866053c3821ac6c97983dc096bff642e9a3549210ee7",
20+
"0x6012516a3ae0e8e8367abdb1db76ba56f7cb221aa3c1e4c02f52ab9d4d1ebc6",
21+
"0x1",
22+
"0x175a7be10a64c27440000",
23+
"0x24bbe83f0dac46c56c50",
24+
"0x0",
25+
"0x2cb02c72e8a0975e69e88298443e984d965a49eab38f5bdde1f5072daa09cfe",
26+
"0x4054bc140aecf17b7b7c48",
27+
"0x0",
28+
],
29+
]
30+
`;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// To run this test:
2+
// npm test -- --run test/integration/multicall/starknet-contracts.spec.ts
3+
4+
import { describe, it, expect } from 'vitest';
5+
import multicall from '../../../src/multicall/starknet';
6+
import getProvider from '../../../src/utils/provider';
7+
8+
describe('Starknet multicall', () => {
9+
// Contract addresses
10+
const STAKING_DELEGATION_POOL =
11+
'0x02cb02c72e8a0975e69e88298443e984d965a49eab38f5bdde1f5072daa09cfe';
12+
13+
const STARKNET_STAKING =
14+
'0x00ca1702e64c81d9a07b86bd2c540188d92a2c73cf5cc0e508d949015e7e84a7';
15+
16+
// Multicall contract address for Starknet mainnet
17+
const MULTICALL_ADDRESS =
18+
'0x05754af3760f3356da99aea5c3ec39ccac7783d925a19666ebbeca58ff0087f4';
19+
20+
// Input parameters
21+
const POOL_MEMBER_ADDRESS =
22+
'0x01c6a9f1f3f97a0a3b176fac97b69c3cd21ba31a522fca111b0e49e46975a048';
23+
24+
const STAKER_ADDRESS =
25+
'0x00d3b910d8c528bf0216866053c3821ac6c97983dc096bff642e9a3549210ee7';
26+
27+
// Initialize provider
28+
const provider = getProvider('0x534e5f4d41494e');
29+
30+
/**
31+
* Contract ABI definitions for the functions we're calling
32+
*/
33+
const contractAbi = [
34+
{
35+
name: 'identify',
36+
outputs: [{ type: 'core::felt252' }]
37+
},
38+
{
39+
name: 'get_pool_member_info_v1',
40+
outputs: [
41+
{ type: 'core::starknet::contract_address::ContractAddress' }, // reward_address
42+
{ type: 'core::integer::u256' }, // amount
43+
{ type: 'core::integer::u256' }, // unclaimed_rewards
44+
{ type: 'core::integer::u256' }, // commission
45+
{ type: 'core::integer::u256' }, // unpool_amount
46+
{ type: 'core::integer::u64' }, // unpool_time
47+
{ type: 'core::integer::u8' } // status
48+
]
49+
},
50+
{
51+
name: 'get_staker_info_v1',
52+
outputs: [
53+
{ type: 'core::starknet::contract_address::ContractAddress' }, // reward_address
54+
{ type: 'core::starknet::contract_address::ContractAddress' }, // operational_address
55+
{ type: 'core::integer::u256' }, // unstake_time (u256)
56+
{ type: 'core::integer::u256' }, // amount_own
57+
{ type: 'core::integer::u256' }, // unclaimed_rewards_own
58+
{ type: 'core::integer::u8' }, // pool_info variant (0x0 = None, 0x1 = Some)
59+
{ type: 'core::starknet::contract_address::ContractAddress' }, // pool_contract
60+
{ type: 'core::integer::u256' }, // pool amount
61+
{ type: 'core::integer::u64' } // pool commission
62+
]
63+
}
64+
];
65+
66+
it('should return responses from different contract calls', async () => {
67+
const calls = [
68+
// Call 1: identify() on Staking Delegation Pool
69+
[STAKING_DELEGATION_POOL, 'identify', []],
70+
71+
// Call 2: get_pool_member_info_v1() on Staking Delegation Pool
72+
[
73+
STAKING_DELEGATION_POOL,
74+
'get_pool_member_info_v1',
75+
[POOL_MEMBER_ADDRESS]
76+
],
77+
78+
// Call 3: get_staker_info_v1() on Starknet Staking
79+
[STARKNET_STAKING, 'get_staker_info_v1', [STAKER_ADDRESS]]
80+
];
81+
82+
const results = await multicall(
83+
MULTICALL_ADDRESS,
84+
provider,
85+
contractAbi,
86+
calls,
87+
10,
88+
{ blockTag: 1980732 }
89+
);
90+
91+
expect(results).toMatchSnapshot();
92+
}, 30000);
93+
});

0 commit comments

Comments
 (0)