Skip to content

Commit

Permalink
feat(docs): partially document structs module (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
BobTheBuidler authored Aug 15, 2024
1 parent 156c502 commit 59b947f
Show file tree
Hide file tree
Showing 2 changed files with 302 additions and 5 deletions.
4 changes: 2 additions & 2 deletions eth_portfolio/_loaders/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from eth_portfolio._db import utils as db
from eth_portfolio._loaders.utils import get_transaction_receipt, underscore
from eth_portfolio.structs import Transaction, _AccessListEntry
from eth_portfolio.structs import AccessListEntry, Transaction

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -60,7 +60,7 @@ async def load_transaction(address: Address, nonce: Nonce, load_prices: bool) ->
tx['price'] = round(Decimal(await get_price(EEE_ADDRESS, block = tx['blockNumber'], sync=False)), 18)
tx['value_usd'] = tx['value'] * tx['price']
if access := tx.pop('accessList', None):
tx['access_list'] = tuple(_AccessListEntry(address=obj["address"], storage_keys=obj["storageKeys"]) for obj in access)
tx['access_list'] = tuple(AccessListEntry(address=obj["address"], storage_keys=obj["storageKeys"]) for obj in access)
try:
transaction = Transaction(**{underscore(k): v for k, v in tx.items()})
except TypeError as e:
Expand Down
303 changes: 300 additions & 3 deletions eth_portfolio/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,67 +9,364 @@
logger = logging.getLogger(__name__)

class _DictStruct(Struct):
"""
A base class that extends the :class:`~msgspec.Struct` class to provide dictionary-like access to struct fields.
Allows iteration over the fields of a struct and provides a dictionary-like interface for retrieving values by field name.
Example:
>>> class MyStruct(_DictStruct):
... field1: str
... field2: int
>>> s = MyStruct(field1="value", field2=42)
>>> list(s.keys())
['field1', 'field2']
>>> s['field1']
'value'
"""

def keys(self) -> Iterator[str]:
"""
Returns an iterator over the field names of the struct.
Returns:
An iterator over the field names.
"""
return iter(self.__struct_fields__)

def __getitem__(self, item: str) -> Any:
return getattr(self, item)
"""
Retrieves the value of the specified field.
Args:
item: The name of the field to retrieve.
Returns:
The value of the specified field.
Raises:
KeyError: If the provided key is not a member of the struct.
"""
try:
return getattr(self, item)
except AttributeError:
raise KeyError(item) from None

class _LedgerEntryBase(_DictStruct, kw_only=True, frozen=True):
"""
The :class:`~structs._LedgerEntryBase` class is a base class for ledger entries representing on-chain actions in a blockchain.
Provides common attributes for transactions, internal transfers, and token transfers.
Extended by specific ledger entry types :class:`~structs.Transaction`, :class:`~structs.InternalTransfer`, and :class:`~structs.TokenTransfer`.
"""

chainid: Network
block_number: Block
transaction_index: Optional[int] = None
"""
The index of the transaction within its block, if applicable.
"""

hash: str
from_address: Optional[str] = None
value: Decimal
to_address: Optional[str] = None
price: Optional[Decimal] = None
"""
The price of the cryptocurrency at the time of the action, if known.
"""

value_usd: Optional[Decimal] = None
"""
The USD value of the cryptocurrency transferred, if price is known.
"""


class _AccessListEntry(Struct, frozen=True):
class AccessListEntry(Struct, frozen=True):
"""
The :class:`~structs.AccessListEntry` class represents an entry in an Ethereum transaction access list.
Access lists are used in EIP-2930 and EIP-1559 transactions to specify storage slots
that the transaction plans to access, potentially reducing gas costs.
Example:
>>> entry = AccessListEntry(
... address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
... storage_keys=(b'key1', b'key2')
... )
>>> entry.address
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
>>> len(entry.storage_keys)
2
"""

address: str
"""
The Ethereum address of the contract whose storage is being accessed.
"""

storage_keys: Tuple[bytes, ...]
"""
The specific storage slot keys within the contract that will be accessed.
"""

class Transaction(_LedgerEntryBase, kw_only=True, frozen=True):
"""
The :class:`~structs.Transaction` class represents a complete on-chain blockchain transaction.
Contains detailed information about a single executed transaction on the blockchain,
including gas parameters, signature components, and transaction-specific data.
Example:
>>> tx = Transaction(
... chainid=Network.Mainnet,
... block_number=Block(15537393),
... hash="0x123...",
... from_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
... to_address="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
... value=Decimal("0.1"),
... block_hash="0xabc...",
... nonce=42,
... type=2, # EIP-1559 transaction
... gas=21000,
... gas_price=20000000000,
... max_fee_per_gas=30000000000,
... max_priority_fee_per_gas=1000000000,
... input="0x",
... r="0x123...",
... s="0x456...",
... v=27
... )
>>> tx.chainid
Network.Mainnet
>>> tx.type
2
>>> tx.max_fee_per_gas
30000000000
"""

entry_type: ClassVar[Literal['transaction']] = 'transaction'
block_hash: str
"""
The hash of the block that includes this transaction.
"""

nonce: int
"""
The sender's transaction count at the time of this transaction.
"""

type: Optional[int]
"""
The transaction type (e.g., 0 for legacy, 1 for EIP-2930, 2 for EIP-1559).
None for chains that don't specify transaction types.
"""

gas: int
"""
The maximum amount of gas the sender is willing to use for the transaction.
"""

gas_price: int
"""
The price per unit of gas the sender is willing to pay (for legacy and EIP-2930 transactions).
"""

max_fee_per_gas: Optional[int] = None
"""
The maximum total fee per gas the sender is willing to pay (for EIP-1559 transactions).
"""

max_priority_fee_per_gas: Optional[int] = None
"""
The maximum priority fee per gas the sender is willing to pay (for EIP-1559 transactions).
"""

input: str
"""
The data payload sent with the transaction, often used for contract interactions.
"""

r: str
"""
The R component of the transaction's ECDSA signature.
"""

s: str
"""
The S component of the transaction's ECDSA signature.
"""

v: int
access_list: Optional[Tuple[_AccessListEntry, ...]] = None
"""
The V component of the transaction's ECDSA signature, used for replay protection.
"""

access_list: Optional[Tuple[AccessListEntry, ...]] = None
"""
List of addresses and storage keys the transaction plans to access (for EIP-2930 and EIP-1559 transactions).
"""

y_parity: Optional[int] = None
"""
The Y parity of the transaction signature, used in EIP-2718 typed transactions.
"""


class InternalTransfer(_LedgerEntryBase, kw_only=True, frozen=True):
"""
The :class:`~structs.InternalTransfer`class represents an internal transfer or call within a blockchain transaction.
Captures operations that occur during transaction execution, such as contract-to-contract calls,
contract creations, or self-destructs. These are not separate on-chain transactions but part of
the execution of a single transaction.
Example:
>>> internal_tx = InternalTransfer(
... chainid=Network.Mainnet,
... block_number=Block(15537393),
... hash="0x123...",
... from_address="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
... to_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
... value=Decimal("0"),
... block_hash="0xabc...",
... type="call",
... trace_address="0.1",
... gas=100000,
... gas_used=21000,
... subtraces=1,
... call_type="call",
... input="0x123...",
... output="0x456..."
... )
>>> internal_tx.type
'call'
>>> internal_tx.trace_address
'0.1'
>>> internal_tx.gas_used
21000
"""

entry_type: ClassVar[Literal['internal_transfer']] = 'internal_transfer'
block_hash: str
"""
The hash of the block containing the transaction that includes this internal transfer.
"""

type: str
"""
The type of internal operation (e.g., "call" for contract calls, "create" for contract creation,
"suicide" for contract self-destruct).
"""

trace_address: str
"""
The path of sub-calls to reach this internal transfer within the transaction.
Represented as period-separated integers, e.g., "0.1.2" for the third sub-call of the second sub-call
of the first top-level call.
"""

gas: int
"""
The amount of gas allocated for this internal operation.
"""

gas_used: Optional[int]
"""
The amount of gas actually consumed by this internal operation, if known.
"""

subtraces: int
"""
The number of sub-operations spawned by this internal transfer.
"""

call_type: Optional[str] = None
"""
The type of call made in this internal transfer (e.g., "call", "delegatecall", "staticcall").
"""

input: Optional[str] = None
"""
The input data for this internal operation, if any.
"""

output: Optional[str] = None
"""
The output data from this internal operation, if any.
"""

init: Optional[str] = None
"""
The initialization code for contract creation, if this is a create operation.
"""

address: Optional[str] = None
"""
The address of the account or contract involved in this internal transfer.
"""

code: Optional[str] = None
"""
The code of the contract involved in this internal transfer, if applicable.
"""


class TokenTransfer(_LedgerEntryBase, kw_only=True, frozen=True):
"""
The :class:`~structs.TokenTransfer` class represents a token transfer event within a blockchain transaction.
Captures the movement of ERC20-like tokens between addresses. These are typically
emitted as events by token contracts and are not separate transactions but part of
the execution of a transaction interacting with the token contract.
Example:
>>> token_transfer = TokenTransfer(
... chainid=Network.Mainnet,
... block_number=Block(15537393),
... hash="0x123...",
... from_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
... to_address="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
... value=Decimal("1000000"), # 1 USDC (assuming 6 decimals)
... log_index=3,
... token="USDC",
... token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
... )
>>> token_transfer.token
'USDC'
>>> token_transfer.value
Decimal('1000000')
>>> token_transfer.log_index
3
"""

entry_type: ClassVar[Literal['token_transfer']] = 'token_transfer'
log_index: int
"""
The index of this transfer event within the transaction logs.
Used to uniquely identify this token Transfer event within the transaction.
"""

token: Optional[str]
"""
The identifier or symbol of the token being transferred, if known.
"""

token_address: str
"""
The contract address of the token being transferred.
"""

value: Decimal


LedgerEntry = Union[Transaction, InternalTransfer, TokenTransfer]
"""
Type alias representing any type of ledger entry (:class:`~structs.Transaction`, :class:`~structs.InternalTransfer`, or :class:`~structs.TokenTransfer`).
"""

_LE = TypeVar("_LE", Transaction, InternalTransfer, TokenTransfer)
"""
Type variable for generic operations on ledger entries. Can be :class:`~structs.Transaction`, :class:`~structs.InternalTransfer`, or :class:`~structs.TokenTransfer`.
"""

0 comments on commit 59b947f

Please sign in to comment.