From 478d939590cbdbc7c9179e0c2e0013e95059dc36 Mon Sep 17 00:00:00 2001 From: kirilaa Date: Fri, 28 Aug 2026 08:20:41 +0200 Subject: [PATCH] fix(gltest): align with layered genlayer-py lifecycle types genlayer-py dropped the public `TransactionStatus` enum when it layered the consumer lifecycle over the raw protocol status. gltest imports the protocol enum from the new surface and keeps `TransactionStatus` as an alias of it so downstream suites that already pass it to `wait_transaction_status` keep working; internal code uses the protocol name and `wait_until`. Success assertions read the state-discriminated `lifecycle` field and fall back to the flat status that pre-lifecycle receipts still carry, so gltest works against both SDK generations. --- gltest/assertions.py | 20 ++++++++++- gltest/contracts/contract.py | 8 ++--- gltest/contracts/contract_factory.py | 14 +++++--- gltest/contracts/contract_functions.py | 10 ++++-- gltest/contracts/wait.py | 17 +++++---- gltest/types.py | 17 ++++++++- tests/gltest/assertions/test_assertions.py | 41 ++++++++++++++++++++++ 7 files changed, 107 insertions(+), 20 deletions(-) diff --git a/gltest/assertions.py b/gltest/assertions.py index 14a66bd..5899d18 100644 --- a/gltest/assertions.py +++ b/gltest/assertions.py @@ -9,6 +9,7 @@ ACCEPTED_STATUSES = {"ACCEPTED", "FINALIZED", "5", "7"} +ACCEPTED_LIFECYCLE_OUTCOMES = {None, "accepted"} SUCCESS_RESULTS = {"FINISHED_WITH_RETURN", "1"} @@ -19,9 +20,26 @@ def _string_value(value: Any) -> Optional[str]: return str(enum_value) +def _accepted_lifecycle(result: GenLayerTransaction) -> Optional[bool]: + """Read the layered lifecycle, or None when the receipt has none.""" + lifecycle = result.get("lifecycle") + if not isinstance(lifecycle, dict): + return None + state = lifecycle.get("state") + if state == "decided": + return lifecycle.get("outcome") == "accepted" + if state == "finalized": + return lifecycle.get("outcome") in ACCEPTED_LIFECYCLE_OUTCOMES + return False + + def _has_accepted_status(result: GenLayerTransaction) -> bool: + accepted_lifecycle = _accepted_lifecycle(result) + if accepted_lifecycle is not None: + return accepted_lifecycle + # Receipts from a pre-lifecycle SDK still carry a flat status. status = _string_value(result.get("status_name", result.get("status"))) - return status in ACCEPTED_STATUSES + return status is not None and status.upper() in ACCEPTED_STATUSES def _leader_receipt(result: GenLayerTransaction) -> Optional[dict]: diff --git a/gltest/contracts/contract.py b/gltest/contracts/contract.py index de0996c..7378695 100644 --- a/gltest/contracts/contract.py +++ b/gltest/contracts/contract.py @@ -6,7 +6,7 @@ from gltest.types import ( CalldataEncodable, GenLayerTransaction, - TransactionStatus, + ProtocolTransactionStatus, TransactionHashVariant, TransactionContext, ) @@ -88,11 +88,11 @@ def transact_method( fees: Optional[Dict[str, Any]] = None, fee_value: Optional[int] = None, wait_until: Optional[Literal["decided", "finalized"]] = None, - wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_transaction_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, wait_interval: Optional[int] = None, wait_retries: Optional[int] = None, wait_triggered_transactions: bool = False, - wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_triggered_transactions_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, transaction_context: Optional[TransactionContext] = None, ): """ @@ -252,7 +252,7 @@ def appeal( self, tx_hash: str, value: int = 0, - wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_transaction_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, wait_until: Optional[Literal["decided", "finalized"]] = None, wait_interval: Optional[int] = None, wait_retries: Optional[int] = None, diff --git a/gltest/contracts/contract_factory.py b/gltest/contracts/contract_factory.py index c200dd3..77a4d84 100644 --- a/gltest/contracts/contract_factory.py +++ b/gltest/contracts/contract_factory.py @@ -19,7 +19,11 @@ from genlayer_py.types import SimConfig from .contract import Contract from gltest.logging import logger -from gltest.types import TransactionStatus, GenLayerTransaction, CalldataEncodable +from gltest.types import ( + ProtocolTransactionStatus, + GenLayerTransaction, + CalldataEncodable, +) from gltest.assertions import tx_execution_failed from gltest.exceptions import DeploymentError from gltest_cli.config.general import get_general_config @@ -130,9 +134,9 @@ def deploy( wait_until: Optional[Literal["decided", "finalized"]] = None, wait_interval: Optional[int] = None, wait_retries: Optional[int] = None, - wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_transaction_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, wait_triggered_transactions: bool = False, - wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_triggered_transactions_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, transaction_context: Optional[TransactionContext] = None, ) -> Contract: """ @@ -172,9 +176,9 @@ def deploy_contract_tx( wait_until: Optional[Literal["decided", "finalized"]] = None, wait_interval: Optional[int] = None, wait_retries: Optional[int] = None, - wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_transaction_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, wait_triggered_transactions: bool = False, - wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_triggered_transactions_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, transaction_context: Optional[TransactionContext] = None, ) -> GenLayerTransaction: """ diff --git a/gltest/contracts/contract_functions.py b/gltest/contracts/contract_functions.py index 5965087..e1ffe7d 100644 --- a/gltest/contracts/contract_functions.py +++ b/gltest/contracts/contract_functions.py @@ -1,6 +1,10 @@ from dataclasses import dataclass from typing import Callable, Optional, Dict, Any, Literal -from gltest.types import TransactionStatus, TransactionHashVariant, TransactionContext +from gltest.types import ( + ProtocolTransactionStatus, + TransactionHashVariant, + TransactionContext, +) @dataclass @@ -31,11 +35,11 @@ def transact( fees: Optional[Dict[str, Any]] = None, fee_value: Optional[int] = None, wait_until: Optional[Literal["decided", "finalized"]] = None, - wait_transaction_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_transaction_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, wait_interval: Optional[int] = None, wait_retries: Optional[int] = None, wait_triggered_transactions: bool = False, - wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED, + wait_triggered_transactions_status: ProtocolTransactionStatus = ProtocolTransactionStatus.ACCEPTED, transaction_context: Optional[TransactionContext] = None, ): """Executes a state-changing contract method through consensus. Returns the transaction receipt.""" diff --git a/gltest/contracts/wait.py b/gltest/contracts/wait.py index f80a76e..5674235 100644 --- a/gltest/contracts/wait.py +++ b/gltest/contracts/wait.py @@ -1,21 +1,26 @@ import inspect -from typing import Callable, Literal +from typing import Any, Callable, Literal -from gltest.types import TransactionStatus +from gltest.types import ProtocolTransactionStatus + + +def _status_text(status: Any) -> str: + """Normalize an enum member, its value, or a bare string to one form.""" + return str(getattr(status, "value", status)).upper() def wait_until_from_status( - status: TransactionStatus, + status: ProtocolTransactionStatus, ) -> Literal["decided", "finalized"]: - if status == TransactionStatus.FINALIZED: + if _status_text(status) == _status_text(ProtocolTransactionStatus.FINALIZED): return "finalized" return "decided" def _status_from_wait_until(wait_until: Literal["decided", "finalized"]): if wait_until == "finalized": - return TransactionStatus.FINALIZED - return TransactionStatus.ACCEPTED + return ProtocolTransactionStatus.FINALIZED + return ProtocolTransactionStatus.ACCEPTED def _accepts_var_kwargs(call: Callable) -> bool: diff --git a/gltest/types.py b/gltest/types.py index 694ebc0..ada6715 100644 --- a/gltest/types.py +++ b/gltest/types.py @@ -2,12 +2,27 @@ from genlayer_py.types import ( CalldataAddress, GenLayerTransaction, - TransactionStatus, CalldataEncodable, TransactionHashVariant, ) from typing import List, TypedDict, Dict, Any +try: + # genlayer-py layers the consumer lifecycle over the raw protocol status. + from genlayer_py.types.transactions import ProtocolTransactionStatus +except ImportError: # genlayer-py before the lifecycle layering + from genlayer_py.types import TransactionStatus as ProtocolTransactionStatus + +try: + from genlayer_py.types import TransactionLifecycle +except ImportError: # genlayer-py before the lifecycle layering + TransactionLifecycle = Dict[str, Any] + +# gltest keeps `TransactionStatus` as part of its own public API for the test +# suites that already pass it to `wait_transaction_status`. Internal code uses +# the protocol name; new code should express waits with `wait_until` instead. +TransactionStatus = ProtocolTransactionStatus + class MockedLLMResponse(TypedDict): """Maps prompts to responses""" diff --git a/tests/gltest/assertions/test_assertions.py b/tests/gltest/assertions/test_assertions.py index 9317a3b..9ed7a4d 100644 --- a/tests/gltest/assertions/test_assertions.py +++ b/tests/gltest/assertions/test_assertions.py @@ -115,6 +115,47 @@ def test_undetermined_transaction_is_not_successful(): assert tx_execution_failed(transaction) is True +def test_decided_lifecycle_transaction_is_successful(): + transaction = { + "lifecycle": {"state": "decided", "outcome": "accepted"}, + "tx_execution_result_name": "FINISHED_WITH_RETURN", + } + + assert tx_execution_succeeded(transaction) is True + assert tx_execution_failed(transaction) is False + + +def test_finalized_lifecycle_transaction_is_successful(): + transaction = { + "lifecycle": {"state": "finalized", "outcome": "accepted"}, + "consensus_data": {"leader_receipt": [{"execution_result": "SUCCESS"}]}, + } + + assert tx_execution_succeeded(transaction) is True + assert tx_execution_failed(transaction) is False + + +def test_undetermined_lifecycle_transaction_is_not_successful(): + transaction = { + "lifecycle": {"state": "decided", "outcome": "undetermined"}, + "tx_execution_result_name": "FINISHED_WITH_RETURN", + "consensus_data": {"leader_receipt": [{"execution_result": "SUCCESS"}]}, + } + + assert tx_execution_succeeded(transaction) is False + assert tx_execution_failed(transaction) is True + + +def test_processing_lifecycle_transaction_is_not_successful(): + transaction = { + "lifecycle": {"state": "processing", "phase": "revealing"}, + "tx_execution_result_name": "FINISHED_WITH_RETURN", + } + + assert tx_execution_succeeded(transaction) is False + assert tx_execution_failed(transaction) is True + + def test_error_transaction_is_not_successful(): transaction = { "status": "ACCEPTED",