diff --git a/.changeset/plenty-pandas-kneel.md b/.changeset/plenty-pandas-kneel.md new file mode 100644 index 00000000..8ffcf544 --- /dev/null +++ b/.changeset/plenty-pandas-kneel.md @@ -0,0 +1,5 @@ +--- +"abitype": patch +--- + +Fixed human-readable ABI item parsing with nested tuple parameters. diff --git a/packages/abitype/src/human-readable/parseAbiItem.test-d.ts b/packages/abitype/src/human-readable/parseAbiItem.test-d.ts index 06afde30..a98f0548 100644 --- a/packages/abitype/src/human-readable/parseAbiItem.test-d.ts +++ b/packages/abitype/src/human-readable/parseAbiItem.test-d.ts @@ -156,3 +156,12 @@ test('parseAbiItem', () => { const signature: string = 'function foo()' expectTypeOf(parseAbiItem(signature)).toEqualTypeOf() }) + +test('nested tuples', () => { + const formattedAbiItem = + 'function stepChanges((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle) stateChanges, uint256 action, bool revetOnInvalidMoves) pure returns ((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle))' + + const abiItem = parseAbiItem(formattedAbiItem) + expectTypeOf(abiItem.stateMutability).toEqualTypeOf<'pure'>() + expectTypeOf(abiItem.inputs.length).toEqualTypeOf<3>() +}) diff --git a/packages/abitype/src/human-readable/parseAbiItem.test.ts b/packages/abitype/src/human-readable/parseAbiItem.test.ts index dc409a3b..d09b095c 100644 --- a/packages/abitype/src/human-readable/parseAbiItem.test.ts +++ b/packages/abitype/src/human-readable/parseAbiItem.test.ts @@ -87,3 +87,176 @@ test.each([ ])('parseAbiItem($signature)', ({ signature, expected }) => { expect(parseAbiItem(signature)).toEqual(expected) }) + +test('nested tuples', () => { + const formattedAbiItem = + 'function stepChanges((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle) stateChanges, uint256 action, bool revetOnInvalidMoves) pure returns ((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle))' + expect(parseAbiItem(formattedAbiItem)).toMatchInlineSnapshot( + ` + { + "inputs": [ + { + "components": [ + { + "name": "characterID", + "type": "uint256", + }, + { + "name": "newPosition", + "type": "uint64", + }, + { + "name": "xp", + "type": "uint24", + }, + { + "name": "epoch", + "type": "uint24", + }, + { + "name": "hp", + "type": "uint8", + }, + { + "components": [ + { + "name": "x", + "type": "int32", + }, + { + "name": "y", + "type": "int32", + }, + { + "name": "hp", + "type": "uint8", + }, + { + "name": "kind", + "type": "uint8", + }, + ], + "name": "monsters", + "type": "tuple[5]", + }, + { + "components": [ + { + "name": "monsterIndexPlus1", + "type": "uint8", + }, + { + "name": "attackCardsUsed1", + "type": "uint8", + }, + { + "name": "attackCardsUsed2", + "type": "uint8", + }, + { + "name": "defenseCardsUsed1", + "type": "uint8", + }, + { + "name": "defenseCardsUsed2", + "type": "uint8", + }, + ], + "name": "battle", + "type": "tuple", + }, + ], + "name": "stateChanges", + "type": "tuple", + }, + { + "name": "action", + "type": "uint256", + }, + { + "name": "revetOnInvalidMoves", + "type": "bool", + }, + ], + "name": "stepChanges", + "outputs": [ + { + "components": [ + { + "name": "characterID", + "type": "uint256", + }, + { + "name": "newPosition", + "type": "uint64", + }, + { + "name": "xp", + "type": "uint24", + }, + { + "name": "epoch", + "type": "uint24", + }, + { + "name": "hp", + "type": "uint8", + }, + { + "components": [ + { + "name": "x", + "type": "int32", + }, + { + "name": "y", + "type": "int32", + }, + { + "name": "hp", + "type": "uint8", + }, + { + "name": "kind", + "type": "uint8", + }, + ], + "name": "monsters", + "type": "tuple[5]", + }, + { + "components": [ + { + "name": "monsterIndexPlus1", + "type": "uint8", + }, + { + "name": "attackCardsUsed1", + "type": "uint8", + }, + { + "name": "attackCardsUsed2", + "type": "uint8", + }, + { + "name": "defenseCardsUsed1", + "type": "uint8", + }, + { + "name": "defenseCardsUsed2", + "type": "uint8", + }, + ], + "name": "battle", + "type": "tuple", + }, + ], + "type": "tuple", + }, + ], + "stateMutability": "pure", + "type": "function", + } + `, + ) +}) diff --git a/packages/abitype/src/human-readable/types/utils.test-d.ts b/packages/abitype/src/human-readable/types/utils.test-d.ts index d3bedd60..64deb43f 100644 --- a/packages/abitype/src/human-readable/types/utils.test-d.ts +++ b/packages/abitype/src/human-readable/types/utils.test-d.ts @@ -676,6 +676,20 @@ test('_ParseFunctionParametersAndStateMutability', () => { Inputs: 'string bar' StateMutability: 'view' }>() + + expectTypeOf< + _ParseFunctionParametersAndStateMutability<'function foo(string bar, uint256) external view'> + >().toEqualTypeOf<{ + Inputs: 'string bar, uint256' + StateMutability: 'view' + }>() + + expectTypeOf< + _ParseFunctionParametersAndStateMutability<'function stepChanges((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle) stateChanges, uint256 action, bool revetOnInvalidMoves) pure returns ((uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle))'> + >().toEqualTypeOf<{ + Inputs: '(uint256 characterID, uint64 newPosition, uint24 xp, uint24 epoch, uint8 hp, (int32 x, int32 y, uint8 hp, uint8 kind)[5] monsters, (uint8 monsterIndexPlus1, uint8 attackCardsUsed1, uint8 attackCardsUsed2, uint8 defenseCardsUsed1, uint8 defenseCardsUsed2) battle) stateChanges, uint256 action, bool revetOnInvalidMoves' + StateMutability: 'pure' + }>() }) test('_ParseTuple', () => { diff --git a/packages/abitype/src/human-readable/types/utils.ts b/packages/abitype/src/human-readable/types/utils.ts index ccefb1af..7979e05d 100644 --- a/packages/abitype/src/human-readable/types/utils.ts +++ b/packages/abitype/src/human-readable/types/utils.ts @@ -255,13 +255,26 @@ export type _ParseFunctionParametersAndStateMutability< | `${Scope} ${AbiStateMutability}`}` ? { Inputs: parameters - StateMutability: scopeOrStateMutability extends `${Scope} ${infer stateMutability extends AbiStateMutability}` - ? stateMutability - : scopeOrStateMutability extends AbiStateMutability - ? scopeOrStateMutability - : 'nonpayable' + StateMutability: _ParseStateMutability } - : never + : signature extends `function ${string}(${infer tail}` + ? _UnwrapNameOrModifier extends { + nameOrModifier: infer scopeOrStateMutability extends string + End: infer parameters + } + ? { + Inputs: parameters + StateMutability: _ParseStateMutability + } + : never + : never + +type _ParseStateMutability = + signature extends `${Scope} ${infer stateMutability extends AbiStateMutability}` + ? stateMutability + : signature extends AbiStateMutability + ? signature + : 'nonpayable' type _ParseConstructorParametersAndStateMutability = signature extends `constructor(${infer parameters}) payable`