From a17833838578988c806f9024698c8bedda6b29ff Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Thu, 20 Feb 2025 18:42:19 -0500 Subject: [PATCH] feat: add support for populate-resources See https://github.com/algorand/algorand-sdk-testing/pull/319 for the test scenario --- algosdk/atomic_transaction_composer.py | 53 +++++++++++++++++++++ algosdk/v2client/models/simulate_request.py | 3 ++ tests/integration.tags | 3 +- tests/steps/other_v2_steps.py | 45 +++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/algosdk/atomic_transaction_composer.py b/algosdk/atomic_transaction_composer.py index be5504ed..5e8f6c07 100644 --- a/algosdk/atomic_transaction_composer.py +++ b/algosdk/atomic_transaction_composer.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod import base64 import copy +from dataclasses import dataclass from enum import IntEnum from typing import ( Any, @@ -25,6 +26,18 @@ T = TypeVar("T") +@dataclass(kw_only=True) +class PopulatedResourceArrays: + """ + Contains the populated resource arrays when `populate_resources` is set to true on a simulate request + """ + + apps: list[int] + accounts: list[str] + assets: list[int] + boxes: list[tuple[int, bytes]] + + def populate_foreign_array( value_to_add: T, foreign_array: List[T], zero_value: Optional[T] = None ) -> int: @@ -322,6 +335,8 @@ def __init__( results: List[SimulateABIResult], eval_overrides: Optional[SimulateEvalOverrides] = None, exec_trace_config: Optional[models.SimulateTraceConfig] = None, + extra_resource_arrays: list[PopulatedResourceArrays] | None = None, + populated_resource_arrays: list[PopulatedResourceArrays] | None = None, ) -> None: self.version = version self.failure_message = failure_message @@ -331,6 +346,8 @@ def __init__( self.abi_results = results self.eval_overrides = eval_overrides self.exec_trace_config = exec_trace_config + self.extra_resource_arrays = extra_resource_arrays + self.populated_resource_arrays = populated_resource_arrays class AtomicTransactionComposer: @@ -823,6 +840,40 @@ def simulate( ) ) + populated_resource_arrays = [] + for txn in txn_group["txn-results"]: + if txn.get("populated-resource-arrays"): + populated_resource_arrays.append( + PopulatedResourceArrays( + apps=txn["populated-resource-arrays"].get("apps", []), + accounts=txn["populated-resource-arrays"].get( + "accounts", [] + ), + assets=txn["populated-resource-arrays"].get( + "assets", [] + ), + boxes=[ + (b["app"], base64.b64decode(b["name"])) + for b in txn["populated-resource-arrays"].get( + "boxes", [] + ) + ], + ) + ) + + extra_resource_arrays = [] + for arrays in txn_group.get("extra-resource-arrays", []): + extra_resource_arrays.append( + PopulatedResourceArrays( + apps=arrays.get("apps", []), + accounts=arrays.get("accounts", []), + assets=arrays.get("assets", []), + boxes=[ + (b["app"], base64.b64decode(b["name"])) + for b in arrays.get("boxes", []) + ], + ) + ) return SimulateAtomicTransactionResponse( version=simulation_result.get("version", 0), failure_message=txn_group.get("failure-message", ""), @@ -834,6 +885,8 @@ def simulate( simulation_result ), exec_trace_config=exec_trace_config, + extra_resource_arrays=extra_resource_arrays, + populated_resource_arrays=populated_resource_arrays, ) def execute( diff --git a/algosdk/v2client/models/simulate_request.py b/algosdk/v2client/models/simulate_request.py index 3b6b220b..7e1c4fb3 100644 --- a/algosdk/v2client/models/simulate_request.py +++ b/algosdk/v2client/models/simulate_request.py @@ -72,6 +72,7 @@ def __init__( allow_unnamed_resources: bool = False, extra_opcode_budget: int = 0, exec_trace_config: Optional[SimulateTraceConfig] = None, + populate_resources: bool = False, ) -> None: self.txn_groups = txn_groups self.round = round @@ -82,6 +83,7 @@ def __init__( self.exec_trace_config = ( exec_trace_config if exec_trace_config else SimulateTraceConfig() ) + self.populate_resources = populate_resources def dictify(self) -> Dict[str, Any]: return { @@ -94,4 +96,5 @@ def dictify(self) -> Dict[str, Any]: "allow-empty-signatures": self.allow_empty_signatures, "extra-opcode-budget": self.extra_opcode_budget, "exec-trace-config": self.exec_trace_config.dictify(), + "populate-resources": self.populate_resources, } diff --git a/tests/integration.tags b/tests/integration.tags index 54a97517..c36c0afe 100644 --- a/tests/integration.tags +++ b/tests/integration.tags @@ -18,4 +18,5 @@ @simulate.lift_log_limits @simulate.extra_opcode_budget @simulate.exec_trace_with_stack_scratch -@simulate.exec_trace_with_state_change_and_hash \ No newline at end of file +@simulate.exec_trace_with_state_change_and_hash +@simulate.populate_resources diff --git a/tests/steps/other_v2_steps.py b/tests/steps/other_v2_steps.py index 20f1a194..fe18ee7b 100644 --- a/tests/steps/other_v2_steps.py +++ b/tests/steps/other_v2_steps.py @@ -2036,3 +2036,48 @@ def get_ledger_state_delta_for_transaction_group(context, id): @when("we make a GetBlockTxids call against block number {round}") def get_block_txids_call(context, round): context.response = context.acl.get_block_txids(round) + + +@when('I set unnamed-resources "{value}"') +def step_impl(context, value): + context.simulate_request.allow_unnamed_resources = value == "true" + + +@when('I set populate-resources "{value}"') +def step_impl(context, value): + context.simulate_request.populate_resources = value == "true" + + +@then( + "the response should include populated-resource-arrays for the transaction" +) +def step_impl(context): + resp: SimulateAtomicTransactionResponse = ( + context.atomic_transaction_composer_return + ) + assert len(resp.populated_resource_arrays) == 1 + + resources = resp.populated_resource_arrays[0] + assert resources.apps == [10000, 20000, 30000] + assert resources.accounts == [ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", + "AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE3PRHE", + "AIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFFWAF4", + "AMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANVWEXNA", + ] + assert resources.boxes == [(context.current_application_id, b"box_key")] + assert resources.assets == [] + + +@then("the response should include extra-resource-arrays for the group") +def step_impl(context): + resp: SimulateAtomicTransactionResponse = ( + context.atomic_transaction_composer_return + ) + assert len(resp.extra_resource_arrays) == 1 + assert resp.extra_resource_arrays[0].apps == [40000] + assert resp.extra_resource_arrays[0].accounts == [ + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJVBPJXY" + ] + assert resp.extra_resource_arrays[0].assets == [10001] + assert resp.extra_resource_arrays[0].boxes == [(0, b"")]