Skip to content

Commit

Permalink
Removing _len parameter for wildcard syntax ABIs
Browse files Browse the repository at this point in the history
  • Loading branch information
elicbarbieri committed Apr 25, 2024
1 parent 51bc4c4 commit 6153822
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 143 deletions.
177 changes: 89 additions & 88 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ python = ">=3.10,<4.0"
pycryptodome = ">=3.4.6" # https://www.pycryptodome.org/src/changelog#id98 Last Change Affecting Keccak


[tool.poetry.group.dev]
optional = true

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.2"
pre-commit = "^3.6.2"
Expand All @@ -22,6 +25,9 @@ mypy = "^1.8.0"
pylint = "^3.1.0"
pyperf = "^2.6.3"

[tool.poetry.group.docs]
optional = true

[tool.poetry.group.docs.dependencies]
Sphinx = "^6.1.3"
sphinx-book-theme = "^1.0.1"
Expand Down
32 changes: 8 additions & 24 deletions starknet_abi/abi_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from typing import Literal, Sequence, Union

# Disable Invalid Name check to allow lower-cased enum names

# pylint: disable=invalid-name

AbiMemberType = Literal[
Expand Down Expand Up @@ -48,7 +47,9 @@ class StarknetCoreType(Enum):
ContractAddress = 6
EthAddress = 7
ClassHash = 9
NoneType = 10 # No decoder, used in enums literals
StorageAddress = 10
Bytes31 = 11
NoneType = 12 # No decoder, used in enums literals

def __repr__(self):
# Override __repr__ to return the Enum Name without value
Expand Down Expand Up @@ -100,6 +101,8 @@ def id_str(self):
'ContractAddress'
>>> StarknetCoreType.NoneType.id_str()
'NoneType'
>>> StarknetCoreType.StorageAddress.id_str()
'StorageAddress'
:return:
"""
Expand All @@ -126,6 +129,9 @@ def max_value(self):
if self.name == "EthAddress":
return 2**160 - 1

if self.name == "Bytes31":
return 2**248 - 1

raise ValueError(f"Cannot get max value for type: {self.name}")


Expand Down Expand Up @@ -323,25 +329,3 @@ def id_str(self):
:return:
"""
return f"{self.name}:{self.type.id_str()}"


# Constant Types

STARKNET_ACCOUNT_CALL = StarknetStruct(
name="Call",
members=[
AbiParameter("to", StarknetCoreType.ContractAddress),
AbiParameter("selector", StarknetCoreType.Felt),
AbiParameter("calldata", StarknetArray(StarknetCoreType.Felt)),
],
)

STARKNET_V0_CALL = StarknetStruct(
name="CallArray",
members=[
AbiParameter("to", StarknetCoreType.Felt),
AbiParameter("selector", StarknetCoreType.Felt),
AbiParameter("data_offset", StarknetCoreType.U128),
AbiParameter("data_len", StarknetCoreType.U128),
],
)
8 changes: 7 additions & 1 deletion starknet_abi/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# fmt: off


def decode_core_type(
def decode_core_type( # pylint: disable=too-many-return-statements
decode_type: StarknetCoreType, calldata: list[int]
) -> str | int | bool:
"""
Expand Down Expand Up @@ -71,6 +71,7 @@ def decode_core_type(
StarknetCoreType.Felt
| StarknetCoreType.ClassHash
| StarknetCoreType.ContractAddress
| StarknetCoreType.StorageAddress
):
encoded_int = calldata.pop(0)

Expand All @@ -84,6 +85,11 @@ def decode_core_type(
assert 0 <= encoded_int <= decode_type.max_value(), f"{encoded_int:0x} larger than EthAddress"
return f"0x{encoded_int:040x}"

case StarknetCoreType.Bytes31:
encoded_int = calldata.pop(0)
assert 0 <= encoded_int <= decode_type.max_value(), f"{encoded_int:0x} larger than Bytes31"
return f"0x{encoded_int:062x}"

case StarknetCoreType.NoneType:
return ""

Expand Down
10 changes: 9 additions & 1 deletion starknet_abi/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _get_enum_index(enum_type: StarknetEnum, enum_key: str) -> tuple[int, Starkn
raise ValueError(f"Enum Key {enum_key} not found in Enum {enum_type}")


def encode_core_type( # pylint: disable=too-many-return-statements
def encode_core_type( # pylint: disable=too-many-return-statements,too-many-branches
encode_type: StarknetCoreType,
value: bytes | int | bool | str,
) -> list[int]:
Expand Down Expand Up @@ -76,12 +76,16 @@ def encode_core_type( # pylint: disable=too-many-return-statements
| StarknetCoreType.ClassHash
| StarknetCoreType.ContractAddress
| StarknetCoreType.EthAddress
| StarknetCoreType.StorageAddress
| StarknetCoreType.Bytes31
):
if isinstance(value, str):
assert value.startswith("0x"), "Hex Strings must be 0x Prefixed"
int_encoded = int(value, 16)
if encode_type == StarknetCoreType.EthAddress:
assert int_encoded <= encode_type.max_value(), f"{value!r} Is larger than an Eth Address"
elif encode_type == StarknetCoreType.Bytes31:
assert 0 <= int_encoded <= encode_type.max_value(), f"{value!r} Does not Fit into 31 Bytes"
else:
assert int_encoded <= encode_type.max_value(), f"{value!r} Does not Fit into Starknet Felt"

Expand All @@ -90,6 +94,8 @@ def encode_core_type( # pylint: disable=too-many-return-statements
if isinstance(value, int):
if encode_type == StarknetCoreType.EthAddress:
assert value <= encode_type.max_value(), f"{value!r} Is larger than an Eth Address"
elif encode_type == StarknetCoreType.Bytes31:
assert 0 <= value <= encode_type.max_value(), f"{value!r} Does not Fit into 31 Bytes"
else:
assert 0 <= value <= encode_type.max_value(), f"{value!r} Does not Fit into Starknet Felt"

Expand All @@ -99,6 +105,8 @@ def encode_core_type( # pylint: disable=too-many-return-statements
int_encoded = int.from_bytes(value, "big")
if encode_type == StarknetCoreType.EthAddress:
assert int_encoded <= encode_type.max_value(), f"{value!r} Is larger than an Eth Address"
elif encode_type == StarknetCoreType.Bytes31:
assert 0 <= int_encoded <= encode_type.max_value(), f"{value!r} Does not Fit into 31 Bytes"
else:
assert value <= encode_type.max_value(), f"{value!r} Does not Fit into Starknet Felt"

Expand Down
102 changes: 76 additions & 26 deletions starknet_abi/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing import Any

from starknet_abi.abi_types import (
STARKNET_ACCOUNT_CALL,
AbiMemberType,
AbiParameter,
StarknetArray,
Expand Down Expand Up @@ -129,8 +128,6 @@ def parse_enums_and_structs(
case ["core", "array" | "integer" | "bool" | "option", *_]:
# Automatically parses Array/Span, u256, bool, and Option types as StarknetCoreType
continue
case ["core", "starknet", "account", "Call"]:
continue

# Can Hard code in structs like openzeppelin's ERC20 & Events for faster parsing

Expand Down Expand Up @@ -272,6 +269,12 @@ def _parse_type( # pylint: disable=too-many-return-statements
case ["starknet", "eth_address", "EthAddress"]:
return StarknetCoreType.EthAddress

case ["bytes_31", "bytes31"]:
return StarknetCoreType.Bytes31

case ["starknet", "storage_access", "StorageAddress"]:
return StarknetCoreType.StorageAddress

############################################################
# Complex Types: Structs, Arrays, etc.
############################################################
Expand All @@ -288,10 +291,6 @@ def _parse_type( # pylint: disable=too-many-return-statements
_parse_type(extract_inner_type(abi_type), custom_types)
)

# Matches 'core::starknet::account::Call'
case ["starknet", "account", "Call"]:
return STARKNET_ACCOUNT_CALL

case _:
# If unknown type is defined in struct context, return struct
if abi_type in custom_types:
Expand All @@ -300,14 +299,63 @@ def _parse_type( # pylint: disable=too-many-return-statements
# Fallback for rarely encountered types
if abi_type == "felt": # Only present in L1 Handler ABIs?
return StarknetCoreType.Felt
if abi_type.endswith("*"): # Old Syntax for Arrays ?
if abi_type.endswith("*"): # Old Syntax for Arrays
return StarknetArray(_parse_type(abi_type[:-1], custom_types))
if abi_type == "Uint256": # Only present in L1 Handler ABIs?
return StarknetCoreType.U256

raise InvalidAbiError(f"Invalid ABI Type: {abi_type}")


def parse_abi_parameters(
names: list[str],
types: list[str],
custom_types: dict[str, StarknetStruct | StarknetEnum],
):
"""
Parses ABIs with wildcard syntax. If felt* is detected, it is parsed as an array of Felt, and the felt_len
parameter is omitted from the parsed type
"""
output_parameters: list[AbiParameter] = []

for name, json_type_str in zip(names, types, strict=True):
if json_type_str.endswith("*"):
len_param = output_parameters.pop(-1)
assert len_param.name.endswith(
"_len"
), f"Type {json_type_str} not preceeded by a length parameter"

output_parameters.append(
AbiParameter(
name=name,
type=_parse_type(json_type_str, custom_types),
)
)

return output_parameters


def parse_abi_types(
types: list[str],
custom_types: dict[str, StarknetStruct | StarknetEnum],
):
"""
Parses a list of ABI types into StarknetTypes while maintaining wildcard syntax definitions.
"""
output_types: list[StarknetType] = []

for json_type_str in types:
if json_type_str.endswith("*"):
len_type = output_types.pop(-1)
assert (
len_type == StarknetCoreType.Felt
), f"Type {json_type_str} not preceeded by a Felt Length Param"

output_types.append(_parse_type(json_type_str, custom_types))

return output_types


def parse_abi_function(
abi_function: dict[str, Any],
custom_types: dict[str, StarknetStruct | StarknetEnum],
Expand All @@ -319,19 +367,21 @@ def parse_abi_function(
:param custom_types:
:return:
"""

parsed_inputs = parse_abi_parameters(
names=[abi_input["name"] for abi_input in abi_function["inputs"]],
types=[abi_input["type"] for abi_input in abi_function["inputs"]],
custom_types=custom_types,
)
parsed_outputs = parse_abi_types(
types=[abi_output["type"] for abi_output in abi_function["outputs"]],
custom_types=custom_types,
)

return AbiFunction(
name=abi_function["name"],
inputs=[
AbiParameter(
name=abi_input["name"],
type=_parse_type(abi_input["type"], custom_types),
)
for abi_input in abi_function["inputs"]
],
outputs=[
_parse_type(abi_output["type"], custom_types)
for abi_output in abi_function["outputs"]
],
inputs=parsed_inputs,
outputs=parsed_outputs,
)


Expand Down Expand Up @@ -360,15 +410,15 @@ def parse_abi_event(
else:
return None

parsed_data = parse_abi_parameters(
names=[abi_input["name"] for abi_input in event_parameters],
types=[abi_input["type"] for abi_input in event_parameters],
custom_types=custom_types,
)

return AbiEvent(
name=abi_event["name"],
data=[
AbiParameter(
name=abi_input["name"],
type=_parse_type(abi_input["type"], custom_types),
)
for abi_input in event_parameters
],
data=parsed_data,
)


Expand Down
Loading

0 comments on commit 6153822

Please sign in to comment.