Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement set_code_transaction for EIP-7702 #316

Merged
merged 6 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/eth_account.typed_transactions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,11 @@ Blob Transaction
:members:
:undoc-members:
:show-inheritance:

Set Code Transaction
--------------------

.. automodule:: eth_account.typed_transactions.set_code_transaction
:members:
:undoc-members:
:show-inheritance:
92 changes: 91 additions & 1 deletion eth_account/_utils/transaction_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@

from eth_account._utils.validation import (
is_rlp_structured_access_list,
is_rlp_structured_authorization_list,
is_rpc_structured_access_list,
is_rpc_structured_authorization_list,
)
from eth_account.datastructures import (
CustomPydanticModel,
)
from eth_account.types import (
AccessList,
AuthorizationList,
RLPStructuredAccessList,
RLPStructuredAuthorizationList,
TransactionDictType,
)

Expand Down Expand Up @@ -56,12 +63,36 @@ def set_transaction_type_if_needed(
):
# blob txn - type 3
transaction_dict = assoc(transaction_dict, "type", "0x3")
elif "authorizationList" in transaction_dict:
# set code txn - type 4
transaction_dict = assoc(transaction_dict, "type", "0x4")
else:
# dynamic fee txn - type 2
transaction_dict = assoc(transaction_dict, "type", "0x2")
return transaction_dict


def json_serialize_classes_in_transaction(val: Any) -> Any:
"""
Serialize class objects in a transaction using expected defined instructions.
Pydantic models are serialized with:

- ``mode="json"`` Uses the json encoder to serialize the model.
- ``by_alias=True``: Uses the alias generator to turn all non-excluded fields
into lowerCamelCase dicts.
- ``exclude=val._exclude: Fields excluded for serialization are defined within a
``_exclude`` property on the pydantic model.
"""
if isinstance(val, CustomPydanticModel):
return val.recursive_model_dump()
elif isinstance(val, dict):
return {k: json_serialize_classes_in_transaction(v) for k, v in val.items()}
elif isinstance(val, (list, tuple)):
return val.__class__(json_serialize_classes_in_transaction(v) for v in val)
else:
return val


# JSON-RPC to rlp transaction structure
def transaction_rpc_to_rlp_structure(dictionary: Dict[str, Any]) -> Dict[str, Any]:
"""
Expand All @@ -72,6 +103,16 @@ def transaction_rpc_to_rlp_structure(dictionary: Dict[str, Any]) -> Dict[str, An
dictionary = dissoc(dictionary, "accessList")
rlp_structured_access_list = _access_list_rpc_to_rlp_structure(access_list)
dictionary = assoc(dictionary, "accessList", rlp_structured_access_list)

authorization_list = dictionary.get("authorizationList")
if authorization_list:
dictionary = dissoc(dictionary, "authorizationList")
rlp_structured_authorization_list = _authorization_list_rpc_to_rlp_structure(
authorization_list
)
dictionary = assoc(
dictionary, "authorizationList", rlp_structured_authorization_list
)
return dictionary


Expand All @@ -93,17 +134,46 @@ def _access_list_rpc_to_rlp_structure(
)


def _authorization_list_rpc_to_rlp_structure(
authorization_list: AuthorizationList,
) -> RLPStructuredAuthorizationList:
if not is_rpc_structured_authorization_list(authorization_list):
raise ValueError(
"provided object not formatted as JSON-RPC-structured authorization list"
)
# flatten each dict into a tuple of its values
return tuple(
(
d["chainId"],
d["address"],
d["nonce"],
d["yParity"],
d["r"],
d["s"],
)
for d in authorization_list
)


# rlp to JSON-RPC transaction structure
def transaction_rlp_to_rpc_structure(dictionary: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert an rlp-structured transaction to a JSON-RPC-structured transaction.
"""
access_list = dictionary.get("accessList")
authorization_list = dictionary.get("authorizationList")
if access_list:
dictionary = dissoc(dictionary, "accessList")
rpc_structured_access_list = _access_list_rlp_to_rpc_structure(access_list)
dictionary = assoc(dictionary, "accessList", rpc_structured_access_list)

if authorization_list:
dictionary = dissoc(dictionary, "authorizationList")
rpc_structured_authorization_list = _authorization_list_rlp_to_rpc_structure(
authorization_list
)
dictionary = assoc(
dictionary, "authorizationList", rpc_structured_authorization_list
)
return dictionary


Expand All @@ -115,3 +185,23 @@ def _access_list_rlp_to_rpc_structure(

# build a dictionary with appropriate keys for each tuple
return tuple({"address": t[0], "storageKeys": t[1]} for t in access_list)


def _authorization_list_rlp_to_rpc_structure(
authorization_list: RLPStructuredAuthorizationList,
) -> AuthorizationList:
if not is_rlp_structured_authorization_list(authorization_list):
raise ValueError(
"provided object not formatted as rlp-structured authorization list"
)
return tuple(
{
"chainId": t[0],
"address": t[1],
"nonce": t[2],
"yParity": t[3],
"r": t[4],
"s": t[5],
}
for t in authorization_list
)
77 changes: 77 additions & 0 deletions eth_account/_utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,83 @@ def is_rlp_structured_access_list(val: Any) -> bool:
return True


def is_rpc_structured_authorization_list(val: Any) -> bool:
"""Returns true if 'val' is a valid JSON-RPC structured access list."""
if not is_list_like(val):
return False
if len(val) == 0:
return False
for d in val:
if not is_dict(d):
return False
if len(d) != 6:
return False
chain_id = d.get("chainId")
address = d.get("address")
nonce = d.get("nonce")
y_parity = d.get("yParity")
signer_r = d.get("r")
signer_s = d.get("s")
if chain_id is None:
return False
if not is_int_or_prefixed_hexstr(chain_id):
return False
if nonce is None:
return False
if not is_int_or_prefixed_hexstr(nonce):
return False
if not is_address(address):
return False
if y_parity is None:
return False
if y_parity not in (0, 1, "0x0", "0x1"):
return False
if signer_r is None:
return False
if not is_int_or_prefixed_hexstr(signer_r):
return False
if signer_s is None:
return False
if not is_int_or_prefixed_hexstr(signer_s):
return False
return True


def is_rlp_structured_authorization_list(val: Any) -> bool:
"""Returns true if 'val' is a valid rlp-structured access list."""
if not is_list_like(val):
return False
for item in val:
if not is_list_like(item):
return False
if len(item) != 6:
return False
chain_id, address, nonce, y_parity, signer_r, signer_s = item
if chain_id is None:
return False
if not is_int_or_prefixed_hexstr(chain_id):
return False
if nonce is None:
return False
if not is_int_or_prefixed_hexstr(nonce):
return False
if not is_address(address):
return False
if y_parity is None:
return False
if y_parity not in ("0x0", "0x1", 0, 1):
return False
if signer_r is None:
return False
if not is_int_or_prefixed_hexstr(signer_r):
return False
if signer_s is None:
return False
if not is_int_or_prefixed_hexstr(signer_s):
return False
return True


# type ignored because curry doesn't preserve typing
@curry # type: ignore[misc]
def is_sequence_of_bytes_or_hexstr(
Expand Down
Loading