@@ -1533,4 +1533,253 @@ mod tests {
15331533 "Should have getSomething1 method"
15341534 ) ;
15351535 }
1536+
1537+ /// Test that tuple/struct types in function inputs and outputs are handled correctly.
1538+ #[ test]
1539+ fn test_tuple_types_in_functions ( ) {
1540+ let abi_json = r#"[
1541+ {
1542+ "type": "function",
1543+ "name": "doSomething",
1544+ "inputs": [
1545+ {
1546+ "name": "data",
1547+ "type": "tuple",
1548+ "components": [
1549+ {"name": "owner", "type": "address"},
1550+ {"name": "value", "type": "uint256"}
1551+ ]
1552+ }
1553+ ],
1554+ "outputs": [
1555+ {
1556+ "name": "result",
1557+ "type": "tuple",
1558+ "components": [
1559+ {"name": "success", "type": "bool"},
1560+ {"name": "newValue", "type": "uint256"}
1561+ ]
1562+ }
1563+ ],
1564+ "stateMutability": "nonpayable"
1565+ }
1566+ ]"# ;
1567+
1568+ let contract = parse_abi ( abi_json) ;
1569+ let gen = AbiCodeGenerator :: new ( contract, "TestContract" ) ;
1570+ let types = gen. generate_types ( ) ;
1571+
1572+ // Get class names
1573+ let class_names: Vec < & str > = types. iter ( ) . map ( |c| c. name . as_str ( ) ) . collect ( ) ;
1574+
1575+ // Should have main contract class
1576+ assert ! (
1577+ class_names. contains( & "TestContract" ) ,
1578+ "Should have TestContract class"
1579+ ) ;
1580+
1581+ // Should have struct classes for input and output tuples
1582+ // Input struct: TestContract__doSomethingInputValue0Struct
1583+ // Output struct: TestContract__doSomethingResultValue0Struct
1584+ let has_input_struct = class_names
1585+ . iter ( )
1586+ . any ( |n| n. contains ( "Input" ) && n. contains ( "Struct" ) ) ;
1587+ let has_output_struct = class_names
1588+ . iter ( )
1589+ . any ( |n| n. contains ( "Result" ) && n. contains ( "Struct" ) ) ;
1590+
1591+ assert ! (
1592+ has_input_struct,
1593+ "Should have input struct class, found: {:?}" ,
1594+ class_names
1595+ ) ;
1596+ assert ! (
1597+ has_output_struct,
1598+ "Should have output/result struct class, found: {:?}" ,
1599+ class_names
1600+ ) ;
1601+
1602+ // Verify the output struct extends ethereum.Tuple
1603+ let output_struct = types
1604+ . iter ( )
1605+ . find ( |c| c. name . contains ( "Result" ) && c. name . contains ( "Struct" ) )
1606+ . expect ( "Should find output struct" ) ;
1607+ assert_eq ! (
1608+ output_struct. extends,
1609+ Some ( "ethereum.Tuple" . to_string( ) ) ,
1610+ "Output struct should extend ethereum.Tuple"
1611+ ) ;
1612+
1613+ // Verify the output struct has getters for its components
1614+ // The getters use "get valueN" naming for positional access to tuple elements
1615+ let method_names: Vec < & str > = output_struct
1616+ . methods
1617+ . iter ( )
1618+ . map ( |m| m. name . as_str ( ) )
1619+ . collect ( ) ;
1620+ assert ! (
1621+ method_names. iter( ) . any( |n| n. contains( "value0" ) ) ,
1622+ "Should have value0 getter for first component, found methods: {:?}" ,
1623+ method_names
1624+ ) ;
1625+ assert ! (
1626+ method_names. iter( ) . any( |n| n. contains( "value1" ) ) ,
1627+ "Should have value1 getter for second component, found methods: {:?}" ,
1628+ method_names
1629+ ) ;
1630+ }
1631+
1632+ /// Test that tuple types in events are handled correctly.
1633+ #[ test]
1634+ fn test_tuple_types_in_events ( ) {
1635+ let abi_json = r#"[
1636+ {
1637+ "type": "event",
1638+ "name": "DataUpdated",
1639+ "inputs": [
1640+ {"name": "id", "type": "uint256", "indexed": true},
1641+ {
1642+ "name": "data",
1643+ "type": "tuple",
1644+ "indexed": false,
1645+ "components": [
1646+ {"name": "timestamp", "type": "uint256"},
1647+ {"name": "value", "type": "bytes32"}
1648+ ]
1649+ }
1650+ ],
1651+ "anonymous": false
1652+ }
1653+ ]"# ;
1654+
1655+ let contract = parse_abi ( abi_json) ;
1656+ let gen = AbiCodeGenerator :: new ( contract, "TestContract" ) ;
1657+ let types = gen. generate_types ( ) ;
1658+
1659+ // Get class names
1660+ let class_names: Vec < & str > = types. iter ( ) . map ( |c| c. name . as_str ( ) ) . collect ( ) ;
1661+
1662+ // Should have event class
1663+ assert ! (
1664+ class_names. contains( & "DataUpdated" ) ,
1665+ "Should have DataUpdated event class"
1666+ ) ;
1667+
1668+ // Should have params class
1669+ assert ! (
1670+ class_names. contains( & "DataUpdated__Params" ) ,
1671+ "Should have DataUpdated__Params class"
1672+ ) ;
1673+
1674+ // Should have struct class for the tuple parameter
1675+ let has_struct = class_names
1676+ . iter ( )
1677+ . any ( |n| n. contains ( "Struct" ) && n. contains ( "DataUpdated" ) ) ;
1678+ assert ! (
1679+ has_struct,
1680+ "Should have struct class for tuple parameter, found: {:?}" ,
1681+ class_names
1682+ ) ;
1683+ }
1684+
1685+ /// Test that nested tuple types (struct with struct field) are handled.
1686+ #[ test]
1687+ fn test_nested_tuple_types ( ) {
1688+ let abi_json = r#"[
1689+ {
1690+ "type": "function",
1691+ "name": "getNestedData",
1692+ "inputs": [],
1693+ "outputs": [
1694+ {
1695+ "name": "result",
1696+ "type": "tuple",
1697+ "components": [
1698+ {"name": "id", "type": "uint256"},
1699+ {
1700+ "name": "inner",
1701+ "type": "tuple",
1702+ "components": [
1703+ {"name": "x", "type": "uint256"},
1704+ {"name": "y", "type": "uint256"}
1705+ ]
1706+ }
1707+ ]
1708+ }
1709+ ],
1710+ "stateMutability": "view"
1711+ }
1712+ ]"# ;
1713+
1714+ let contract = parse_abi ( abi_json) ;
1715+ let gen = AbiCodeGenerator :: new ( contract, "TestContract" ) ;
1716+ let types = gen. generate_types ( ) ;
1717+
1718+ // Get class names
1719+ let class_names: Vec < & str > = types. iter ( ) . map ( |c| c. name . as_str ( ) ) . collect ( ) ;
1720+
1721+ // Should have main contract class
1722+ assert ! (
1723+ class_names. contains( & "TestContract" ) ,
1724+ "Should have TestContract class"
1725+ ) ;
1726+
1727+ // Should have at least two struct classes (outer and inner)
1728+ let struct_count = class_names. iter ( ) . filter ( |n| n. contains ( "Struct" ) ) . count ( ) ;
1729+ assert ! (
1730+ struct_count >= 2 ,
1731+ "Should have at least 2 struct classes for nested tuple, found {} in {:?}" ,
1732+ struct_count,
1733+ class_names
1734+ ) ;
1735+ }
1736+
1737+ /// Test that tuple arrays are handled correctly.
1738+ #[ test]
1739+ fn test_tuple_array_types ( ) {
1740+ let abi_json = r#"[
1741+ {
1742+ "type": "function",
1743+ "name": "getAllItems",
1744+ "inputs": [],
1745+ "outputs": [
1746+ {
1747+ "name": "items",
1748+ "type": "tuple[]",
1749+ "components": [
1750+ {"name": "id", "type": "uint256"},
1751+ {"name": "name", "type": "string"}
1752+ ]
1753+ }
1754+ ],
1755+ "stateMutability": "view"
1756+ }
1757+ ]"# ;
1758+
1759+ let contract = parse_abi ( abi_json) ;
1760+ let gen = AbiCodeGenerator :: new ( contract, "TestContract" ) ;
1761+ let types = gen. generate_types ( ) ;
1762+
1763+ // Find the contract class
1764+ let contract_class = types. iter ( ) . find ( |c| c. name == "TestContract" ) . unwrap ( ) ;
1765+
1766+ // Find the getAllItems method
1767+ let method = contract_class
1768+ . methods
1769+ . iter ( )
1770+ . find ( |m| m. name == "getAllItems" )
1771+ . expect ( "Should have getAllItems method" ) ;
1772+
1773+ // The return type should be an Array of the struct type
1774+ let return_type = method
1775+ . return_type
1776+ . as_ref ( )
1777+ . expect ( "Should have return type" ) ;
1778+ let return_type_str = return_type. to_string ( ) ;
1779+ assert ! (
1780+ return_type_str. starts_with( "Array<" ) ,
1781+ "Return type should be Array<...>, got: {}" ,
1782+ return_type_str
1783+ ) ;
1784+ }
15361785}
0 commit comments