Skip to content

Commit

Permalink
Fix struct references. Version bump to v0.0.43 (#135)
Browse files Browse the repository at this point in the history
Fixes an issue where struct references don't get matched up correctly.
See new `struct_references` test that highlights this issue.

- When finding what type files to import when building type files (for
struct references in other files), we look at the contract name of
structs instead of looking for structs in abis.
- Always reference a fully qualified name for nested structs. This
solves issues around creating the return type data structure when doing
`function.call()`.
- Define the python type as the fully qualified name. This solves an
issue with structures in different file references and functions
returning a vector of the struct.
- Handle the case when a struct doesn't get referenced with a contract
name in the abi.
- Fix to handling of struct arrays as input/output to functions with
fully qualified names for structs.

This might solve #98
  • Loading branch information
slundqui authored Oct 8, 2024
1 parent 67cde22 commit 4873f57
Show file tree
Hide file tree
Showing 61 changed files with 1,084 additions and 187 deletions.
73 changes: 37 additions & 36 deletions example/types/ExampleContract.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""A web3.py Contract class for the Example contract.
DO NOT EDIT. This file was generated by pypechain v0.0.41.
DO NOT EDIT. This file was generated by pypechain v0.0.43.
See documentation at https://github.com/delvtech/pypechain """

# contracts have PascalCase names
Expand Down Expand Up @@ -50,12 +50,13 @@

from pypechain.core import combomethod_typed, dataclass_to_tuple, get_abi_input_types, rename_returned_types

from .ExampleTypes import FlipEvent, FlopEvent, InnerStruct, NestedStruct, SimpleStruct
from . import ExampleTypes as Example
from .ExampleTypes import FlipEvent, FlopEvent

structs = {
"SimpleStruct": SimpleStruct,
"InnerStruct": InnerStruct,
"NestedStruct": NestedStruct,
"Example.SimpleStruct": Example.SimpleStruct,
"Example.InnerStruct": Example.InnerStruct,
"Example.NestedStruct": Example.NestedStruct,
}


Expand Down Expand Up @@ -120,8 +121,8 @@ class ExampleMixStructsAndPrimitivesContractFunction(ContractFunction):
class ReturnValues(NamedTuple):
"""The return named tuple for MixStructsAndPrimitives."""

simpleStruct: SimpleStruct
arg2: NestedStruct
simpleStruct: Example.SimpleStruct
arg2: Example.NestedStruct
arg3: int
name: str
YesOrNo: bool
Expand All @@ -142,7 +143,7 @@ def call(
"""returns ReturnValues."""
# Define the expected return types from the smart contract call

return_types = [SimpleStruct, NestedStruct, int, str, bool]
return_types = [Example.SimpleStruct, Example.NestedStruct, int, str, bool]

# Call the function

Expand All @@ -165,16 +166,16 @@ def call(
block_identifier: BlockIdentifier = "latest",
state_override: StateOverride | None = None,
ccip_read_enabled: bool | None = None,
) -> SimpleStruct:
"""returns SimpleStruct."""
) -> Example.SimpleStruct:
"""returns Example.SimpleStruct."""
# Define the expected return types from the smart contract call

return_types = SimpleStruct
return_types = Example.SimpleStruct

# Call the function

raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
return cast(SimpleStruct, rename_returned_types(structs, return_types, raw_values))
return cast(Example.SimpleStruct, rename_returned_types(structs, return_types, raw_values))


class ExampleNamedTwoMixedStructsContractFunction(ContractFunction):
Expand All @@ -183,8 +184,8 @@ class ExampleNamedTwoMixedStructsContractFunction(ContractFunction):
class ReturnValues(NamedTuple):
"""The return named tuple for NamedTwoMixedStructs."""

simpleStruct: SimpleStruct
nestedStruct: NestedStruct
simpleStruct: Example.SimpleStruct
nestedStruct: Example.NestedStruct

def __call__(self) -> ExampleNamedTwoMixedStructsContractFunction: # type: ignore
clone = super().__call__()
Expand All @@ -202,7 +203,7 @@ def call(
"""returns ReturnValues."""
# Define the expected return types from the smart contract call

return_types = [SimpleStruct, NestedStruct]
return_types = [Example.SimpleStruct, Example.NestedStruct]

# Call the function

Expand All @@ -213,7 +214,7 @@ def call(
class ExampleSingleNestedStructContractFunction(ContractFunction):
"""ContractFunction for the singleNestedStruct method."""

def __call__(self, nestedStruct: NestedStruct) -> ExampleSingleNestedStructContractFunction: # type: ignore
def __call__(self, nestedStruct: Example.NestedStruct) -> ExampleSingleNestedStructContractFunction: # type: ignore
clone = super().__call__(dataclass_to_tuple(nestedStruct))
self.kwargs = clone.kwargs
self.args = clone.args
Expand All @@ -225,22 +226,22 @@ def call(
block_identifier: BlockIdentifier = "latest",
state_override: StateOverride | None = None,
ccip_read_enabled: bool | None = None,
) -> NestedStruct:
"""returns NestedStruct."""
) -> Example.NestedStruct:
"""returns Example.NestedStruct."""
# Define the expected return types from the smart contract call

return_types = NestedStruct
return_types = Example.NestedStruct

# Call the function

raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
return cast(NestedStruct, rename_returned_types(structs, return_types, raw_values))
return cast(Example.NestedStruct, rename_returned_types(structs, return_types, raw_values))


class ExampleSingleSimpleStructContractFunction(ContractFunction):
"""ContractFunction for the singleSimpleStruct method."""

def __call__(self, simpleStruct: SimpleStruct) -> ExampleSingleSimpleStructContractFunction: # type: ignore
def __call__(self, simpleStruct: Example.SimpleStruct) -> ExampleSingleSimpleStructContractFunction: # type: ignore
clone = super().__call__(dataclass_to_tuple(simpleStruct))
self.kwargs = clone.kwargs
self.args = clone.args
Expand All @@ -252,16 +253,16 @@ def call(
block_identifier: BlockIdentifier = "latest",
state_override: StateOverride | None = None,
ccip_read_enabled: bool | None = None,
) -> SimpleStruct:
"""returns SimpleStruct."""
) -> Example.SimpleStruct:
"""returns Example.SimpleStruct."""
# Define the expected return types from the smart contract call

return_types = SimpleStruct
return_types = Example.SimpleStruct

# Call the function

raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
return cast(SimpleStruct, rename_returned_types(structs, return_types, raw_values))
return cast(Example.SimpleStruct, rename_returned_types(structs, return_types, raw_values))


class ExampleTwoMixedStructsContractFunction(ContractFunction):
Expand All @@ -270,8 +271,8 @@ class ExampleTwoMixedStructsContractFunction(ContractFunction):
class ReturnValues(NamedTuple):
"""The return named tuple for TwoMixedStructs."""

arg1: SimpleStruct
arg2: NestedStruct
arg1: Example.SimpleStruct
arg2: Example.NestedStruct

def __call__(self) -> ExampleTwoMixedStructsContractFunction: # type: ignore
clone = super().__call__()
Expand All @@ -289,7 +290,7 @@ def call(
"""returns ReturnValues."""
# Define the expected return types from the smart contract call

return_types = [SimpleStruct, NestedStruct]
return_types = [Example.SimpleStruct, Example.NestedStruct]

# Call the function

Expand All @@ -303,8 +304,8 @@ class ExampleTwoSimpleStructsContractFunction(ContractFunction):
class ReturnValues(NamedTuple):
"""The return named tuple for TwoSimpleStructs."""

arg1: SimpleStruct
arg2: SimpleStruct
arg1: Example.SimpleStruct
arg2: Example.SimpleStruct

def __call__(self) -> ExampleTwoSimpleStructsContractFunction: # type: ignore
clone = super().__call__()
Expand All @@ -322,7 +323,7 @@ def call(
"""returns ReturnValues."""
# Define the expected return types from the smart contract call

return_types = [SimpleStruct, SimpleStruct]
return_types = [Example.SimpleStruct, Example.SimpleStruct]

# Call the function

Expand All @@ -333,7 +334,7 @@ def call(
class ExampleVecOfStructContractFunction(ContractFunction):
"""ContractFunction for the vecOfStruct method."""

def __call__(self, inVecSimpleStruct: list[SimpleStruct]) -> ExampleVecOfStructContractFunction: # type: ignore
def __call__(self, inVecSimpleStruct: list[Example.SimpleStruct]) -> ExampleVecOfStructContractFunction: # type: ignore
clone = super().__call__(dataclass_to_tuple(inVecSimpleStruct))
self.kwargs = clone.kwargs
self.args = clone.args
Expand All @@ -345,16 +346,16 @@ def call(
block_identifier: BlockIdentifier = "latest",
state_override: StateOverride | None = None,
ccip_read_enabled: bool | None = None,
) -> list[SimpleStruct]:
"""returns list[SimpleStruct]."""
) -> list[Example.SimpleStruct]:
"""returns list[Example.SimpleStruct]."""
# Define the expected return types from the smart contract call

return_types = list[SimpleStruct]
return_types = list[Example.SimpleStruct]

# Call the function

raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
return cast(list[SimpleStruct], rename_returned_types(structs, return_types, raw_values))
return cast(list[Example.SimpleStruct], rename_returned_types(structs, return_types, raw_values))


class ExampleContractFunctions(ContractFunctions):
Expand Down
16 changes: 14 additions & 2 deletions example/types/ExampleTypes.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
"""Dataclasses for all structs in the Example contract.
DO NOT EDIT. This file was generated by pypechain v0.0.41.
DO NOT EDIT. This file was generated by pypechain v0.0.43.
See documentation at https://github.com/delvtech/pypechain """

# super() call methods are generic, while our version adds values & types
# pylint: disable=arguments-differ

# contracts have PascalCase names
# pylint: disable=invalid-name

# contracts control how many attributes and arguments we have in generated code
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments

# unable to determine which imports will be used in the generated code
# pylint: disable=unused-import

# we don't need else statement if the other conditionals all have return,
# but it's easier to generate
# pylint: disable=no-else-return

# We import this contract itself to ensure all nested structs have a fully qualified name.
# We use this to avoid namespace collisions, as well as having a uniform
# type structure to do lookups when functions return these structs.
# pylint: disable=import-self


from __future__ import annotations

from dataclasses import dataclass

from pypechain.core import BaseEvent, BaseEventArgs, ErrorInfo, ErrorParams

from . import ExampleTypes as Example


@dataclass(kw_only=True)
class FlipEvent(BaseEvent):
Expand Down Expand Up @@ -76,7 +88,7 @@ class NestedStruct:

intVal: int
strVal: str
innerStruct: InnerStruct
innerStruct: Example.InnerStruct


WrongChoiceError = ErrorInfo(
Expand Down
2 changes: 1 addition & 1 deletion example/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Export all types from generated files.
DO NOT EDIT. This file was generated by pypechain v0.0.41.
DO NOT EDIT. This file was generated by pypechain v0.0.43.
See documentation at https://github.com/delvtech/pypechain """

from .ExampleContract import ExampleContract
2 changes: 1 addition & 1 deletion example/types/pypechain.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pypechain == 0.0.41
pypechain == 0.0.43
2 changes: 1 addition & 1 deletion pypechain/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from pathlib import Path
from typing import NamedTuple, Sequence

from pypechain.render import render_contracts
from pypechain.render.init import render_init_file
from pypechain.render.main import render_contracts
from pypechain.utilities.abi import AbiInfo, load_abi_infos_from_file
from pypechain.utilities.types import RenderOutput

Expand Down
3 changes: 3 additions & 0 deletions pypechain/render/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Module for rendering contracts."""

from .render import render_contracts
16 changes: 13 additions & 3 deletions pypechain/render/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ def _add_structs(contract_infos: dict[str, ContractInfo], structs: StructInfo |
if not isinstance(structs, list):
structs = [structs]
for struct in structs:
info = contract_infos.get(struct.contract_name)
if struct.contract_name is None:
contract_name = struct.name
else:
contract_name = struct.contract_name
info = contract_infos.get(contract_name)
if info:
# Sanity check, if this structure already exists, we compare the two and ensure
# it's the same structure
Expand All @@ -129,10 +133,10 @@ def _add_structs(contract_infos: dict[str, ContractInfo], structs: StructInfo |
else:
info.structs[struct.name] = struct
else:
contract_infos[struct.contract_name] = ContractInfo(
contract_infos[contract_name] = ContractInfo(
abi=[],
bytecode="",
contract_name=struct.contract_name,
contract_name=contract_name,
link_references=[],
structs={struct.name: struct},
events={},
Expand Down Expand Up @@ -266,6 +270,10 @@ def render_contract_file(contract_info: ContractInfo) -> str | None:
has_overloading = any(function_data["has_overloading"] for function_data in function_datas.values())

structs_used = get_structs_for_abi(contract_info.abi)
structs_filenames = list({struct.contract_name for struct in structs_used if struct.contract_name is not None})

# Special handling for structs without a contract name
structs_without_filenames = list({struct.name for struct in structs_used if struct.contract_name is None})

link_reference_data = get_link_reference_data(contract_info.link_references)

Expand Down Expand Up @@ -314,6 +322,8 @@ def render_contract_file(contract_info: ContractInfo) -> str | None:
pypechain_version=importlib.metadata.version("pypechain"),
contract_name=contract_info.contract_name,
structs_used=structs_used,
structs_filenames=structs_filenames,
structs_without_filenames=structs_without_filenames,
has_overloading=has_overloading,
has_multiple_return_values=has_multiple_return_values,
has_bytecode=has_bytecode,
Expand Down
File renamed without changes.
18 changes: 13 additions & 5 deletions pypechain/render/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import importlib.metadata

from pypechain.render.contract import ContractInfo
from pypechain.utilities.abi import get_structs_for_abi
from pypechain.utilities.templates import get_jinja_env


Expand All @@ -31,10 +30,19 @@ def render_types_file(contract_info: ContractInfo) -> str | None:
has_events = len(events) > 0
has_structs = len(structs) > 0

structs_used = get_structs_for_abi(contract_info.abi)
types_files_imported = {
struct.contract_name for struct in structs_used if struct.contract_name != contract_info.contract_name
}
# We need to identify any inner structs that are defined in other contracts.
types_files_imported = []
# Iterate through all structs and look at the contract_name of each struct value
for struct in structs:
for struct_value in struct.values:
# Add an import if it's a struct
if struct_value.is_struct:
if struct_value.contract_name is not None:
types_files_imported.append(struct_value.contract_name)
# There's a case where a struct is imported locally without a contract name.
# In this case, we use the name of the struct as the contract name.
else:
types_files_imported.append(struct_value.name)

if not has_events and not has_structs:
return None
Expand Down
Loading

0 comments on commit 4873f57

Please sign in to comment.